/* * Copyright (c) 2009 Mark Liversedge (liversedge@gmail.com) * * 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 "ScatterWindow.h" #include "ScatterPlot.h" #include "GcOverlayWidget.h" #include "Athlete.h" #include "Context.h" #include "Colors.h" #include "HelpWhatsThis.h" #include #include void ScatterWindow::addStandardChannels(QComboBox *box) { box->addItem(tr("Power"), MODEL_POWER); box->addItem(tr("Cadence"), MODEL_CADENCE); box->addItem(tr("Heartrate"), MODEL_HEARTRATE); box->addItem(tr("Speed"), MODEL_SPEED); box->addItem(tr("Altitude"), MODEL_ALT); box->addItem(tr("Torque"), MODEL_TORQUE); box->addItem(tr("AEPF"), MODEL_AEPF); box->addItem(tr("CPV"), MODEL_CPV); box->addItem(tr("Time"), MODEL_TIME); box->addItem(tr("Distance"), MODEL_DISTANCE); box->addItem(tr("Headwind"), MODEL_HEADWIND); box->addItem(tr("Slope"), MODEL_SLOPE); box->addItem(tr("Temperature"), MODEL_TEMP); box->addItem(tr("L/R Balance"), MODEL_LRBALANCE); box->addItem(tr("L/R Torque Effectiveness"), MODEL_TE); box->addItem(tr("L/R Pedal Smoothness"), MODEL_PS); box->addItem(tr("Running Vertical Oscillation"), MODEL_RV); box->addItem(tr("Running Cadence"), MODEL_RCAD); box->addItem(tr("Running GCT"), MODEL_RGCT); box->addItem(tr("Gear Ratio"), MODEL_GEAR); box->addItem(tr("Muscle Oxygen"), MODEL_SMO2); box->addItem(tr("Haemoglobin Mass"), MODEL_THB); box->addItem(tr("Deoxygenated Hb"), MODEL_HHB); box->addItem(tr("Oxygenated Hb"), MODEL_O2HB); //box->addItem(tr("Interval"), MODEL_INTERVAL); //supported differently for now //box->addItem(tr("Latitude"), MODEL_LAT); //weird values make the plot ugly //box->addItem(tr("Longitude"), MODEL_LONG); //weird values make the plot ugly } void ScatterWindow::addrStandardChannels(QxtStringSpinBox *box) { QStringList list; list.append(tr("Power")); list.append(tr("Cadence")); list.append(tr("Heartrate")); list.append(tr("Speed")); list.append(tr("Altitude")); list.append(tr("Torque")); list.append(tr("AEPF")); list.append(tr("CPV")); list.append(tr("Time")); list.append(tr("Distance")); list.append(tr("Headwind")); list.append(tr("Slope")); list.append(tr("Temperature")); list.append(tr("L/R Balance")); list.append(tr("L/R Torque Effectiveness")); list.append(tr("L/R Pedal Smoothness")); list.append(tr("Running Vertical Oscillation")); list.append(tr("Running Cadence")); list.append(tr("Running GCT")); list.append(tr("Gear Ratio")); list.append(tr("Muscle Oxygen")); list.append(tr("Haemoglobin Mass")); list.append(tr("Deoxygenated Hb")); list.append(tr("Oxygenated Hb")); box->setStrings(list); } ScatterWindow::ScatterWindow(Context *context) : GcChartWindow(context), context(context), ride(NULL), stale(false), current(NULL) { // // reveal controls widget // // reveal controls QLabel *rxLabel = new QLabel(tr("X-Axis:"), this); rxSelector = new QxtStringSpinBox(); addrStandardChannels(rxSelector); QLabel *ryLabel = new QLabel(tr("Y-Axis:"), this); rySelector = new QxtStringSpinBox(); addrStandardChannels(rySelector); rIgnore = new QCheckBox(tr("Ignore Zero")); rIgnore->setChecked(true); rIgnore->hide(); rFrameInterval = new QCheckBox(tr("Frame intervals")); rFrameInterval->setChecked(true); // layout reveal controls QHBoxLayout *r = new QHBoxLayout; r->setContentsMargins(0,0,0,0); r->addStretch(); r->addWidget(rxLabel); r->addWidget(rxSelector); r->addWidget(ryLabel); r->addWidget(rySelector); r->addSpacing(5); r->addWidget(rIgnore); r->addWidget(rFrameInterval); r->addStretch(); setRevealLayout(r); QWidget *c = new QWidget; HelpWhatsThis *helpConfig = new HelpWhatsThis(c); c->setWhatsThis(helpConfig->getWhatsThisText(HelpWhatsThis::ChartRides_2D)); QFormLayout *cl = new QFormLayout(c); setControls(c); // the plot widget scatterPlot= new ScatterPlot(context); QVBoxLayout *vlayout = new QVBoxLayout; vlayout->addWidget(scatterPlot); HelpWhatsThis *help = new HelpWhatsThis(scatterPlot); scatterPlot->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::ChartRides_2D)); setChartLayout(vlayout); // labels xLabel = new QLabel(tr("X-Axis:"), this); xSelector = new QComboBox; addStandardChannels(xSelector); xSelector->setCurrentIndex(0); // power rxSelector->setValue(0); cl->addRow(xLabel, xSelector); yLabel = new QLabel(tr("Y-Axis:"), this); ySelector = new QComboBox; addStandardChannels(ySelector); ySelector->setCurrentIndex(2); // heartrate rySelector->setValue(2); cl->addRow(yLabel, ySelector); QLabel *smoothLabel = new QLabel(tr("Smooth"), this); smoothLineEdit = new QLineEdit(this); smoothLineEdit->setFixedWidth(40); smoothSlider = new QSlider(Qt::Horizontal, this); smoothSlider->setTickPosition(QSlider::TicksBelow); smoothSlider->setTickInterval(10); smoothSlider->setMinimum(1); smoothSlider->setMaximum(60); smoothLineEdit->setValidator(new QIntValidator(smoothSlider->minimum(), smoothSlider->maximum(), smoothLineEdit)); smoothSlider->setValue(0); QHBoxLayout *smoothLayout = new QHBoxLayout; smoothLayout->addWidget(smoothLineEdit); smoothLayout->addWidget(smoothSlider); cl->addRow(smoothLabel, smoothLayout); // selectors ignore = new QCheckBox(tr("Ignore Zero")); ignore->setChecked(true); cl->addRow(ignore); grid = new QCheckBox(tr("Show Grid")); grid->setChecked(true); cl->addRow(grid); frame = new QCheckBox(tr("Frame Intervals")); frame->setChecked(true); cl->addRow(frame); compareMode = new QComboBox(this); compareMode->addItem(tr("All intervals/activities")); compareMode->addItem(tr("First intervals/activities on X-axis")); compareMode->addItem(tr("First intervals/activities on Y-axis")); cl->addRow(new QLabel(tr("Compare mode")), compareMode); compareMode->setCurrentIndex(0); trendLine = new QComboBox(this); trendLine->addItem(tr("No")); trendLine->addItem(tr("Auto")); trendLine->addItem(tr("Linear")); cl->addRow(new QLabel(tr("Trend line")), trendLine); trendLine->setCurrentIndex(0); // the model helper -- showing model parameters etc QWidget *helper = new QWidget(this); helper->setAutoFillBackground(true); addHelper(QString(tr("Trend")), helper); helperWidget()->hide(); // now connect up the widgets //connect(main, SIGNAL(rideSelected()), this, SLOT(rideSelected())); connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideSelected())); connect(context, SIGNAL(rideChanged(RideItem*)), this, SLOT(forceReplot())); connect(context, SIGNAL(intervalSelected()), this, SLOT(intervalSelected())); connect(context, SIGNAL(intervalsChanged()), this, SLOT(intervalSelected())); connect(xSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(xSelectorChanged(int))); connect(ySelector, SIGNAL(currentIndexChanged(int)), this, SLOT(ySelectorChanged(int))); connect(rxSelector, SIGNAL(valueChanged(int)), this, SLOT(rxSelectorChanged(int))); connect(rySelector, SIGNAL(valueChanged(int)), this, SLOT(rySelectorChanged(int))); connect(grid, SIGNAL(stateChanged(int)), this, SLOT(setGrid())); //connect(legend, SIGNAL(stateChanged(int)), this, SLOT(setLegend())); connect(frame, SIGNAL(stateChanged(int)), this, SLOT(setFrame())); connect(ignore, SIGNAL(stateChanged(int)), this, SLOT(setIgnore())); connect(rFrameInterval, SIGNAL(stateChanged(int)), this, SLOT(setrFrame())); connect(rIgnore, SIGNAL(stateChanged(int)), this, SLOT(setrIgnore())); connect(compareMode, SIGNAL(currentIndexChanged(int)), this, SLOT(setCompareMode(int))); connect(trendLine, SIGNAL(currentIndexChanged(int)), this, SLOT(setTrendLine(int))); connect(smoothSlider, SIGNAL(valueChanged(int)), this, SLOT(setSmoothingFromSlider())); connect(smoothLineEdit, SIGNAL(editingFinished()), this, SLOT(setSmoothingFromLineEdit())); connect(context, SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32))); // comparing things connect(context, SIGNAL(compareIntervalsStateChanged(bool)), this, SLOT(compareChanged())); connect(context, SIGNAL(compareIntervalsChanged()), this, SLOT(compareChanged())); // set colors configChanged(CONFIG_APPEARANCE); } void ScatterWindow::configChanged(qint32) { setProperty("color", GColor(CPLOTBACKGROUND)); QPalette palette; palette.setBrush(QPalette::Window, QBrush(GColor(CPLOTBACKGROUND))); palette.setColor(QPalette::WindowText, GColor(CPLOTMARKER)); palette.setColor(QPalette::Text, GColor(CPLOTMARKER)); setPalette(palette); } void ScatterWindow::forceReplot() { stale=true; rideSelected(); } void ScatterWindow::rideSelected() { if (!amVisible()) return; ride = myRideItem; if (ride == current && !stale) return; if (!ride || !ride->ride() || !ride->ride()->dataPoints().count()) { current = NULL; setIsBlank(true); return; } else setIsBlank(false); current = ride; stale = false; setData(); } void ScatterWindow::setGrid() { settings.gridlines = grid->isChecked(); setData(); } void ScatterWindow::setFrame() { settings.frame = frame->isChecked(); rFrameInterval->setChecked(frame->isChecked()); setData(); } void ScatterWindow::setrFrame() { settings.frame = rFrameInterval->isChecked(); frame->setChecked(rFrameInterval->isChecked()); setData(); } void ScatterWindow::setIgnore() { settings.ignore = ignore->isChecked(); rIgnore->setChecked(ignore->isChecked()); setData(); } void ScatterWindow::setrIgnore() { settings.ignore = rIgnore->isChecked(); ignore->setChecked(rIgnore->isChecked()); setData(); } void ScatterWindow::setCompareMode(int value) { compareMode->setCurrentIndex(value); settings.compareMode = value; setData(); } void ScatterWindow::setTrendLine(int value) { trendLine->setCurrentIndex(value); settings.trendLine = value; // need a helper any more ? if (value > 0) { helperWidget()->hide(); //show(); } else { helperWidget()->hide(); } setData(); } void ScatterWindow::setSmoothingFromSlider() { smoothLineEdit->setText(QString("%1").arg(smoothSlider->value())); settings.smoothing = smoothSlider->value(); setData(); } void ScatterWindow::setSmoothingFromLineEdit() { int value = smoothLineEdit->text().toInt(); smoothSlider->setValue(value); settings.smoothing = value; setData(); } void ScatterWindow::intervalSelected() { if (!amVisible()) return; setData(); } void ScatterWindow::xSelectorChanged(int value) { rxSelector->setValue(value); setData(); } void ScatterWindow::rxSelectorChanged(int value) { xSelector->setCurrentIndex(value); setData(); } void ScatterWindow::ySelectorChanged(int value) { rySelector->setValue(value); setData(); } void ScatterWindow::rySelectorChanged(int value) { ySelector->setCurrentIndex(value); setData(); } void ScatterWindow::setData() { settings.ride = ride; settings.x = xSelector->itemData(xSelector->currentIndex()).toInt(); settings.y = ySelector->itemData(ySelector->currentIndex()).toInt(); settings.ignore = ignore->isChecked(); settings.gridlines = grid->isChecked(); settings.frame = frame->isChecked(); settings.compareMode = compareMode->currentIndex(); settings.trendLine = trendLine->currentIndex(); settings.smoothing = smoothSlider->value(); /* Not a blank state ? Just no data and we can change series ? if ((setting.x == MODEL_POWER || setting.y == MODEL_POWER ) && !ride->ride()->isDataPresent(RideFile::watts)) setIsBlank(true); else if ((setting.x == MODEL_CADENCE || setting.y == MODEL_CADENCE ) && !ride->ride()->isDataPresent(RideFile::cad)) setIsBlank(true); else if ((setting.x == MODEL_HEARTRATE || setting.y == MODEL_HEARTRATE ) && !ride->ride()->isDataPresent(RideFile::hr)) setIsBlank(true); else if ((setting.x == MODEL_SPEED || setting.y == MODEL_SPEED ) && !ride->ride()->isDataPresent(RideFile::kph)) setIsBlank(true); else setIsBlank(false); */ // any intervals to plot? if (ride) settings.intervals = ride->intervalsSelected(); else settings.intervals.clear(); scatterPlot->setData(&settings); } void ScatterWindow::compareChanged() { /* no stale mode ? if (!amVisible()) { return; }*/ setData(); repaint(); } bool ScatterWindow::event(QEvent *event) { // nasty nasty nasty hack to move widgets as soon as the widget geometry // is set properly by the layout system, by default the width is 100 and // we wait for it to be set properly then put our helper widget on the RHS if (event->type() == QEvent::Resize && geometry().width() != 100) { // put somewhere nice on first show if (firstShow) { firstShow = false; helperWidget()->move(mainWidget()->geometry().width()-275, 50); } // if off the screen move on screen if (helperWidget()->geometry().x() > geometry().width()) { helperWidget()->move(mainWidget()->geometry().width()-275, 50); } } return QWidget::event(event); }