Files
GoldenCheetah/src/AllPlotWindow.cpp
Mark Liversedge bfc0d1575a IntervalItem::name removed
The class member IntervalItem::name is redundant since it is a duplicate
of the text() member of the base class QTreeWidgetItem. By removing it
we both simplify the code and remove the need to keep name and text in
sync when renaming and creating intervals.

As a result the itemChanged signal that was connected for renames and
then disconnected when the items are cleared can be connected once
and no disconnect is neccessary.

This connect/disconnect oddity was originally to avoid a SEGV that
resulted from accessing text() whilst the QTreeWidgetItem was being
destroyed. The code for removing intervals when a new ride is selected
no longer destroys and recreats MainWindow::allIntervals (which was also an
artefact of the original code to avoid a SEGV).
2009-12-13 13:07:17 -05:00

430 lines
14 KiB
C++

/*
* 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 "MainWindow.h"
#include "AllPlotWindow.h"
#include "AllPlot.h"
#include "MainWindow.h"
#include "RideFile.h"
#include "RideItem.h"
#include "IntervalItem.h"
#include "TimeUtils.h"
#include "Settings.h"
#include "Units.h" // for MILES_PER_KM
#include <qwt_plot_panner.h>
#include <qwt_plot_zoomer.h>
#include <qwt_plot_picker.h>
#include <qwt_plot_marker.h>
AllPlotWindow::AllPlotWindow(MainWindow *mainWindow) :
QWidget(mainWindow), mainWindow(mainWindow)
{
QVBoxLayout *vlayout = new QVBoxLayout;
QHBoxLayout *showLayout = new QHBoxLayout;
QLabel *showLabel = new QLabel("Show:", this);
showLayout->addWidget(showLabel);
QCheckBox *showGrid = new QCheckBox("Grid", this);
showGrid->setCheckState(Qt::Checked);
showLayout->addWidget(showGrid);
showHr = new QCheckBox("Heart Rate", this);
showHr->setCheckState(Qt::Checked);
showLayout->addWidget(showHr);
showSpeed = new QCheckBox("Speed", this);
showSpeed->setCheckState(Qt::Checked);
showLayout->addWidget(showSpeed);
showCad = new QCheckBox("Cadence", this);
showCad->setCheckState(Qt::Checked);
showLayout->addWidget(showCad);
showAlt = new QCheckBox("Altitude", this);
showAlt->setCheckState(Qt::Checked);
showLayout->addWidget(showAlt);
showPower = new QComboBox();
showPower->addItem(tr("Power + shade"));
showPower->addItem(tr("Power - shade"));
showPower->addItem(tr("No Power"));
showLayout->addWidget(showPower);
QHBoxLayout *smoothLayout = new QHBoxLayout;
QComboBox *comboDistance = new QComboBox();
comboDistance->addItem(tr("X Axis Shows Time"));
comboDistance->addItem(tr("X Axis Shows Distance"));
smoothLayout->addWidget(comboDistance);
QLabel *smoothLabel = new QLabel(tr("Smoothing (secs)"), this);
smoothLineEdit = new QLineEdit(this);
smoothLineEdit->setFixedWidth(40);
smoothLayout->addWidget(smoothLabel);
smoothLayout->addWidget(smoothLineEdit);
smoothSlider = new QSlider(Qt::Horizontal);
smoothSlider->setTickPosition(QSlider::TicksBelow);
smoothSlider->setTickInterval(10);
smoothSlider->setMinimum(2);
smoothSlider->setMaximum(600);
smoothLineEdit->setValidator(new QIntValidator(smoothSlider->minimum(),
smoothSlider->maximum(),
smoothLineEdit));
smoothLayout->addWidget(smoothSlider);
allPlot = new AllPlot(this, mainWindow);
smoothSlider->setValue(allPlot->smoothing());
smoothLineEdit->setText(QString("%1").arg(allPlot->smoothing()));
allZoomer = new QwtPlotZoomer(allPlot->canvas());
allZoomer->setRubberBand(QwtPicker::RectRubberBand);
allZoomer->setRubberBandPen(QColor(Qt::black));
allZoomer->setSelectionFlags(QwtPicker::DragSelection
| QwtPicker::CornerToCorner);
allZoomer->setTrackerMode(QwtPicker::AlwaysOff);
allZoomer->setEnabled(true);
// TODO: Hack for OS X one-button mouse
// allZoomer->initMousePattern(1);
// RightButton: zoom out by 1
// Ctrl+RightButton: zoom out to full size
allZoomer->setMousePattern(QwtEventPattern::MouseSelect2,
Qt::RightButton, Qt::ControlModifier);
allZoomer->setMousePattern(QwtEventPattern::MouseSelect3,
Qt::RightButton);
allPanner = new QwtPlotPanner(allPlot->canvas());
allPanner->setMouseButton(Qt::MidButton);
// TODO: zoomer doesn't interact well with automatic axis resizing
// Interval selection
allPicker = new QwtPlotPicker(QwtPlot::xBottom, QwtPlot::yLeft,
QwtPicker::RectSelection | QwtPicker::CornerToCorner|QwtPicker::DragSelection,
QwtPicker::VLineRubberBand,
QwtPicker::ActiveOnly, allPlot->canvas());
allPicker->setRubberBandPen(QColor(Qt::blue));
allPicker->setRubberBand(QwtPicker::CrossRubberBand);
allPicker->setTrackerPen(QColor(Qt::blue));
// now select rectangles
allPicker->setSelectionFlags(QwtPicker::PointSelection | QwtPicker::RectSelection | QwtPicker::DragSelection);
allPicker->setRubberBand(QwtPicker::VLineRubberBand);
allPicker->setMousePattern(QwtEventPattern::MouseSelect1,
Qt::LeftButton, Qt::ShiftModifier);
connect(allPicker, SIGNAL(moved(const QPoint &)),
SLOT(plotPickerMoved(const QPoint &)));
connect(allPicker, SIGNAL(appended(const QPoint &)),
SLOT(plotPickerSelected(const QPoint &)));
allMarker1 = new QwtPlotMarker();
allMarker1->setLineStyle(QwtPlotMarker::VLine);
allMarker1->attach(allPlot);
allMarker1->setLabelAlignment(Qt::AlignTop|Qt::AlignRight);
allMarker2 = new QwtPlotMarker();
allMarker2->setLineStyle(QwtPlotMarker::VLine);
allMarker2->attach(allPlot);
allMarker3 = new QwtPlotMarker();
allMarker3->setLineStyle(QwtPlotMarker::VLine);
allMarker3->attach(allPlot);
vlayout->addWidget(allPlot);
vlayout->addLayout(showLayout);
vlayout->addLayout(smoothLayout);
setLayout(vlayout);
connect(showPower, SIGNAL(currentIndexChanged(int)),
allPlot, SLOT(showPower(int)));
connect(showHr, SIGNAL(stateChanged(int)),
allPlot, SLOT(showHr(int)));
connect(showSpeed, SIGNAL(stateChanged(int)),
allPlot, SLOT(showSpeed(int)));
connect(showCad, SIGNAL(stateChanged(int)),
allPlot, SLOT(showCad(int)));
connect(showAlt, SIGNAL(stateChanged(int)),
allPlot, SLOT(showAlt(int)));
connect(showGrid, SIGNAL(stateChanged(int)),
allPlot, SLOT(showGrid(int)));
connect(comboDistance, SIGNAL(currentIndexChanged(int)),
allPlot, SLOT(setByDistance(int)));
connect(smoothSlider, SIGNAL(valueChanged(int)),
this, SLOT(setSmoothingFromSlider()));
connect(smoothLineEdit, SIGNAL(editingFinished()),
this, SLOT(setSmoothingFromLineEdit()));
connect(mainWindow, SIGNAL(rideSelected()), this, SLOT(rideSelected()));
connect(mainWindow, SIGNAL(zonesChanged()), this, SLOT(zonesChanged()));
connect(mainWindow, SIGNAL(intervalsChanged()), this, SLOT(intervalsChanged()));
connect(mainWindow, SIGNAL(intervalSelected()), this, SLOT(intervalSelected()));
}
void
AllPlotWindow::rideSelected()
{
RideItem *ride = mainWindow->rideItem();
if (!ride)
return;
clearSelection(); // clear any ride interval selection data
setAllPlotWidgets(ride);
allPlot->setData(ride);
allZoomer->setZoomBase();
}
void
AllPlotWindow::zonesChanged()
{
allPlot->refreshZoneLabels();
allPlot->replot();
}
void
AllPlotWindow::intervalsChanged()
{
allPlot->refreshIntervalMarkers();
allPlot->replot();
}
void
AllPlotWindow::intervalSelected()
{
hideSelection();
allPlot->replot();
}
void
AllPlotWindow::setSmoothingFromSlider()
{
if (allPlot->smoothing() != smoothSlider->value()) {
allPlot->setSmoothing(smoothSlider->value());
smoothLineEdit->setText(QString("%1").arg(allPlot->smoothing()));
}
}
void
AllPlotWindow::setSmoothingFromLineEdit()
{
int value = smoothLineEdit->text().toInt();
if (value != allPlot->smoothing()) {
allPlot->setSmoothing(value);
smoothSlider->setValue(value);
}
}
void
AllPlotWindow::setAllPlotWidgets(RideItem *ride)
{
if (ride->ride && ride->ride->deviceType() != QString("Manual CSV")) {
const RideFileDataPresent *dataPresent = ride->ride->areDataPresent();
showPower->setEnabled(dataPresent->watts);
showHr->setEnabled(dataPresent->hr);
showSpeed->setEnabled(dataPresent->kph);
showCad->setEnabled(dataPresent->cad);
showAlt->setEnabled(dataPresent->alt);
}
else {
showPower->setEnabled(false);
showHr->setEnabled(false);
showSpeed->setEnabled(false);
showCad->setEnabled(false);
showAlt->setEnabled(false);
}
}
void
AllPlotWindow::zoomInterval(IntervalItem *which)
{
QwtDoubleRect rect;
if (!allPlot->byDistance()) {
rect.setLeft(which->start/60);
rect.setRight(which->stop/60);
} else {
rect.setLeft(which->startKM);
rect.setRight(which->stopKM);
}
rect.setTop(allPlot->wattsCurve->maxYValue()*1.1);
rect.setBottom(0);
allZoomer->zoom(rect);
}
void
AllPlotWindow::plotPickerSelected(const QPoint &pos)
{
// set start of selection in xunits (minutes assumed for now)
setStartSelection(allPlot->invTransform(QwtPlot::xBottom, pos.x()));
}
void
AllPlotWindow::plotPickerMoved(const QPoint &pos)
{
QString name = QString("Selection #%1").arg(selection);
// set end of selection in xunits (minutes assumed for now)
setEndSelection(allPlot->invTransform(QwtPlot::xBottom, pos.x()), true, name);
}
void
AllPlotWindow::setStartSelection(double xValue)
{
selection++;
if (!allMarker1->isVisible() || allMarker1->xValue() != xValue) {
allMarker1->hide();
allMarker2->hide();
allMarker3->hide();
allMarker1->setValue(xValue, allPlot->byDistance() ? 0 : 100);
allMarker1->show();
}
}
void
AllPlotWindow::setEndSelection(double xValue, bool newInterval, QString name)
{
bool useMetricUnits = allPlot->useMetricUnits;
if (!allMarker2->isVisible() || allMarker2->xValue() != xValue) {
allMarker2->setValue(xValue, allPlot->byDistance() ? 0 : 100);
allMarker2->show();
double x1, x2; // time or distance depending upon mode selected
// swap to low-high if neccessary
if (allMarker1->xValue()>allMarker2->xValue()){
x2 = allMarker1->xValue();
x1 = allMarker2->xValue();
} else {
x1 = allMarker1->xValue();
x2 = allMarker2->xValue();
}
double lwidth=allPlot->transform(QwtPlot::xBottom, x2)-allPlot->transform(QwtPlot::xBottom, x1);
allMarker3->setValue((x2-x1)/2+x1, 100);
QColor marker_color = QColor(Qt::blue);
marker_color.setAlpha(64);
allMarker3->setLinePen(QPen(QBrush(marker_color), lwidth, Qt::SolidLine)) ;
//allMarker3->setZ(-1000.0);
allMarker3->show();
RideFile tmpRide = RideFile();
QTreeWidgetItem *which = mainWindow->rideItem();
RideItem *ride = (RideItem*)which;
double distance1 = -1;
double distance2 = -1;
double duration1 = -1;
double duration2 = -1;
double secsMoving = 0;
double wattsTotal = 0;
double bpmTotal = 0;
int arrayLength = 0;
// if we are in distance mode then x1 and x2 are distances
// we need to make sure they are in KM for the rest of this
// code.
if (allPlot->byDistance() && useMetricUnits == false) {
x1 *= KM_PER_MILE;
x2 *= KM_PER_MILE;
}
foreach (const RideFilePoint *point, ride->ride->dataPoints()) {
if ((allPlot->byDistance()==true && point->km>=x1 && point->km<x2) ||
(allPlot->byDistance()==false && point->secs/60>=x1 && point->secs/60<x2)) {
if (distance1 == -1) distance1 = point->km;
distance2 = point->km;
if (duration1 == -1) duration1 = point->secs;
duration2 = point->secs;
if (point->kph > 0.0)
secsMoving += ride->ride->recIntSecs();
wattsTotal += point->watts;
bpmTotal += point->hr;
++arrayLength;
}
}
QString s("\n%1%2 %3 %4\n%5%6 %7%8 %9%10");
s = s.arg(useMetricUnits ? distance2-distance1 : (distance2-distance1)*MILES_PER_KM, 0, 'f', 1);
s = s.arg((useMetricUnits? "km":"m"));
s = s.arg(time_to_string(duration2-duration1));
if (duration2-duration1-secsMoving>1)
s = s.arg("("+time_to_string(secsMoving)+")");
else
s = s.arg("");
s = s.arg((useMetricUnits ? 1 : MILES_PER_KM) * (distance2-distance1)/secsMoving*3600, 0, 'f', 1);
s = s.arg((useMetricUnits? "km/h":"m/h"));
if (wattsTotal>0) {
s = s.arg(wattsTotal/arrayLength, 0, 'f', 1);
s = s.arg("W");
}
else{
s = s.arg("");
s = s.arg("");
}
if (bpmTotal>0) {
s = s.arg(bpmTotal/arrayLength, 0, 'f', 0);
s = s.arg("bpm");
}
else {
s = s.arg("");
s = s.arg("");
}
allMarker1->setLabel(s);
if (newInterval) {
QTreeWidgetItem *allIntervals = mainWindow->mutableIntervalItems();
int count = allIntervals->childCount();
// are we adjusting an existing interval? - if so delete it and readd it
if (count > 0) {
IntervalItem *bottom = (IntervalItem *) allIntervals->child(count-1);
if (bottom->text(0) == name) delete allIntervals->takeChild(count-1);
}
QTreeWidgetItem *last = new IntervalItem(ride->ride, name, duration1, duration2, distance1, distance2);
allIntervals->addChild(last);
// now update the RideFileIntervals and all the plots etc
mainWindow->updateRideFileIntervals();
}
}
}
void
AllPlotWindow::clearSelection()
{
selection = 0;
hideSelection();
}
void
AllPlotWindow::hideSelection()
{
allMarker1->setVisible(false);
allMarker2->setVisible(false);
allMarker3->setVisible(false);
allPlot->replot();
}