mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 16:39:57 +00:00
The controls in the sidebar are inconsistent; the interval selector has no spacing or border for a clean look. This patch updates LTMTool and TrainTool to be more efficient with the limited space. A more extensive update is still required to unify all the chart controls look and feel and get rid of the clunky layouts with ugly wide buttons.
1058 lines
34 KiB
C++
1058 lines
34 KiB
C++
/*
|
|
* Copyright (c) 2010 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 "LTMTool.h"
|
|
#include "MainWindow.h"
|
|
#include "Settings.h"
|
|
#include "Units.h"
|
|
#include <assert.h>
|
|
#include <QApplication>
|
|
#include <QtGui>
|
|
|
|
// seasons support
|
|
#include "Season.h"
|
|
#include "SeasonParser.h"
|
|
#include <QXmlInputSource>
|
|
#include <QXmlSimpleReader>
|
|
|
|
// metadata support
|
|
#include "RideMetadata.h"
|
|
#include "SpecialFields.h"
|
|
|
|
LTMTool::LTMTool(MainWindow *parent, const QDir &home, bool multi) : QWidget(parent), home(home), main(parent), active(false)
|
|
{
|
|
setStyleSheet("QFrame { FrameStyle = QFrame::NoFrame };"
|
|
"QWidget { background = Qt::white; border:0 px; margin: 2px; };");
|
|
|
|
// get application settings
|
|
useMetricUnits = appsettings->value(this, GC_UNIT).toString() == "Metric";
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
|
mainLayout->setContentsMargins(0,0,0,0);
|
|
mainLayout->setSpacing(0);
|
|
setContentsMargins(0,0,0,0);
|
|
|
|
dateRangeTree = new QTreeWidget;
|
|
dateRangeTree->setFrameStyle(QFrame::NoFrame);
|
|
dateRangeTree->setColumnCount(1);
|
|
dateRangeTree->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
dateRangeTree->header()->hide();
|
|
//dateRangeTree->setAlternatingRowColors (true);
|
|
dateRangeTree->setIndentation(5);
|
|
allDateRanges = new QTreeWidgetItem(dateRangeTree, ROOT_TYPE);
|
|
allDateRanges->setText(0, tr("Date Range"));
|
|
|
|
dateRangeTree->expandItem(allDateRanges);
|
|
dateRangeTree->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
seasons = parent->seasons;
|
|
resetSeasons(); // reset the season list
|
|
|
|
metricTree = new QTreeWidget;
|
|
metricTree->setColumnCount(1);
|
|
if (multi)
|
|
metricTree->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
else
|
|
metricTree->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
metricTree->header()->hide();
|
|
metricTree->setFrameStyle(QFrame::NoFrame);
|
|
//metricTree->setAlternatingRowColors (true);
|
|
metricTree->setIndentation(5);
|
|
allMetrics = new QTreeWidgetItem(metricTree, ROOT_TYPE);
|
|
allMetrics->setText(0, tr("Metric"));
|
|
metricTree->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
// initialise the metrics catalogue and user selector
|
|
const RideMetricFactory &factory = RideMetricFactory::instance();
|
|
for (int i = 0; i < factory.metricCount(); ++i) {
|
|
|
|
// metrics catalogue and settings
|
|
MetricDetail adds;
|
|
QColor cHSV;
|
|
|
|
adds.symbol = factory.metricName(i);
|
|
adds.metric = factory.rideMetric(factory.metricName(i));
|
|
qsrand(QTime::currentTime().msec());
|
|
cHSV.setHsv((i%6)*(255/(factory.metricCount()/5)), 255, 255);
|
|
adds.penColor = cHSV.convertTo(QColor::Rgb);
|
|
adds.curveStyle = curveStyle(factory.metricType(i));
|
|
adds.symbolStyle = symbolStyle(factory.metricType(i));
|
|
adds.smooth = false;
|
|
adds.trend = false;
|
|
adds.topN = 5; // show top 5 by default always
|
|
QTextEdit processHTML(adds.metric->name()); // process html encoding of(TM)
|
|
adds.name = processHTML.toPlainText();
|
|
|
|
// set default for the user overiddable fields
|
|
adds.uname = adds.name;
|
|
adds.uunits = adds.metric->units(useMetricUnits);
|
|
|
|
// default units to metric name if it is blank
|
|
if (adds.uunits == "") adds.uunits = adds.name;
|
|
metrics.append(adds);
|
|
}
|
|
|
|
//
|
|
// Add PM metrics, which are calculated over the metric dataset
|
|
//
|
|
|
|
// SKIBA LTS
|
|
MetricDetail skibaLTS;
|
|
skibaLTS.type = METRIC_PM;
|
|
skibaLTS.symbol = "skiba_lts";
|
|
skibaLTS.metric = NULL; // not a factory metric
|
|
skibaLTS.penColor = QColor(Qt::blue);
|
|
skibaLTS.curveStyle = QwtPlotCurve::Lines;
|
|
skibaLTS.symbolStyle = QwtSymbol::NoSymbol;
|
|
skibaLTS.smooth = false;
|
|
skibaLTS.trend = false;
|
|
skibaLTS.topN = 5;
|
|
skibaLTS.uname = skibaLTS.name = "Skiba Long Term Stress";
|
|
skibaLTS.uunits = "Stress";
|
|
metrics.append(skibaLTS);
|
|
|
|
MetricDetail skibaSTS;
|
|
skibaSTS.type = METRIC_PM;
|
|
skibaSTS.symbol = "skiba_sts";
|
|
skibaSTS.metric = NULL; // not a factory metric
|
|
skibaSTS.penColor = QColor(Qt::magenta);
|
|
skibaSTS.curveStyle = QwtPlotCurve::Lines;
|
|
skibaSTS.symbolStyle = QwtSymbol::NoSymbol;
|
|
skibaSTS.smooth = false;
|
|
skibaSTS.trend = false;
|
|
skibaSTS.topN = 5;
|
|
skibaSTS.uname = skibaSTS.name = "Skiba Short Term Stress";
|
|
skibaSTS.uunits = "Stress";
|
|
metrics.append(skibaSTS);
|
|
|
|
MetricDetail skibaSB;
|
|
skibaSB.type = METRIC_PM;
|
|
skibaSB.symbol = "skiba_sb";
|
|
skibaSB.metric = NULL; // not a factory metric
|
|
skibaSB.penColor = QColor(Qt::yellow);
|
|
skibaSB.curveStyle = QwtPlotCurve::Steps;
|
|
skibaSB.symbolStyle = QwtSymbol::NoSymbol;
|
|
skibaSB.smooth = false;
|
|
skibaSB.trend = false;
|
|
skibaSB.topN = 1;
|
|
skibaSB.uname = skibaSB.name = "Skiba Stress Balance";
|
|
skibaSB.uunits = "Stress Balance";
|
|
metrics.append(skibaSB);
|
|
|
|
MetricDetail skibaSTR;
|
|
skibaSTR.type = METRIC_PM;
|
|
skibaSTR.symbol = "skiba_sr";
|
|
skibaSTR.metric = NULL; // not a factory metric
|
|
skibaSTR.penColor = QColor(Qt::darkGreen);
|
|
skibaSTR.curveStyle = QwtPlotCurve::Steps;
|
|
skibaSTR.symbolStyle = QwtSymbol::NoSymbol;
|
|
skibaSTR.smooth = false;
|
|
skibaSTR.trend = false;
|
|
skibaSTR.topN = 1;
|
|
skibaSTR.uname = skibaSTR.name = "Skiba STS Ramp";
|
|
skibaSTR.uunits = "Ramp";
|
|
metrics.append(skibaSTR);
|
|
|
|
MetricDetail skibaLTR;
|
|
skibaLTR.type = METRIC_PM;
|
|
skibaLTR.symbol = "skiba_lr";
|
|
skibaLTR.metric = NULL; // not a factory metric
|
|
skibaLTR.penColor = QColor(Qt::darkBlue);
|
|
skibaLTR.curveStyle = QwtPlotCurve::Steps;
|
|
skibaLTR.symbolStyle = QwtSymbol::NoSymbol;
|
|
skibaLTR.smooth = false;
|
|
skibaLTR.trend = false;
|
|
skibaLTR.topN = 1;
|
|
skibaLTR.uname = skibaLTR.name = "Skiba LTS Ramp";
|
|
skibaLTR.uunits = "Ramp";
|
|
metrics.append(skibaLTR);
|
|
|
|
// DANIELS LTS
|
|
MetricDetail danielsLTS;
|
|
danielsLTS.type = METRIC_PM;
|
|
danielsLTS.symbol = "daniels_lts";
|
|
danielsLTS.metric = NULL; // not a factory metric
|
|
danielsLTS.penColor = QColor(Qt::blue);
|
|
danielsLTS.curveStyle = QwtPlotCurve::Lines;
|
|
danielsLTS.symbolStyle = QwtSymbol::NoSymbol;
|
|
danielsLTS.smooth = false;
|
|
danielsLTS.trend = false;
|
|
danielsLTS.topN = 5;
|
|
danielsLTS.uname = danielsLTS.name = "Daniels Long Term Stress";
|
|
danielsLTS.uunits = "Stress";
|
|
metrics.append(danielsLTS);
|
|
|
|
MetricDetail danielsSTS;
|
|
danielsSTS.type = METRIC_PM;
|
|
danielsSTS.symbol = "daniels_sts";
|
|
danielsSTS.metric = NULL; // not a factory metric
|
|
danielsSTS.penColor = QColor(Qt::magenta);
|
|
danielsSTS.curveStyle = QwtPlotCurve::Lines;
|
|
danielsSTS.symbolStyle = QwtSymbol::NoSymbol;
|
|
danielsSTS.smooth = false;
|
|
danielsSTS.trend = false;
|
|
danielsSTS.topN = 5;
|
|
danielsSTS.uname = danielsSTS.name = "Daniels Short Term Stress";
|
|
danielsSTS.uunits = "Stress";
|
|
metrics.append(danielsSTS);
|
|
|
|
MetricDetail danielsSB;
|
|
danielsSB.type = METRIC_PM;
|
|
danielsSB.symbol = "daniels_sb";
|
|
danielsSB.metric = NULL; // not a factory metric
|
|
danielsSB.penColor = QColor(Qt::yellow);
|
|
danielsSB.curveStyle = QwtPlotCurve::Steps;
|
|
danielsSB.symbolStyle = QwtSymbol::NoSymbol;
|
|
danielsSB.smooth = false;
|
|
danielsSB.trend = false;
|
|
danielsSB.topN = 1;
|
|
danielsSB.uname = danielsSB.name = "Daniels Stress Balance";
|
|
danielsSB.uunits = "Stress Balance";
|
|
metrics.append(danielsSB);
|
|
|
|
MetricDetail danielsSTR;
|
|
danielsSTR.type = METRIC_PM;
|
|
danielsSTR.symbol = "daniels_sr";
|
|
danielsSTR.metric = NULL; // not a factory metric
|
|
danielsSTR.penColor = QColor(Qt::darkGreen);
|
|
danielsSTR.curveStyle = QwtPlotCurve::Steps;
|
|
danielsSTR.symbolStyle = QwtSymbol::NoSymbol;
|
|
danielsSTR.smooth = false;
|
|
danielsSTR.trend = false;
|
|
danielsSTR.topN = 1;
|
|
danielsSTR.uname = danielsSTR.name = "Daniels STS Ramp";
|
|
danielsSTR.uunits = "Ramp";
|
|
metrics.append(danielsSTR);
|
|
|
|
MetricDetail danielsLTR;
|
|
danielsLTR.type = METRIC_PM;
|
|
danielsLTR.symbol = "daniels_lr";
|
|
danielsLTR.metric = NULL; // not a factory metric
|
|
danielsLTR.penColor = QColor(Qt::darkBlue);
|
|
danielsLTR.curveStyle = QwtPlotCurve::Steps;
|
|
danielsLTR.symbolStyle = QwtSymbol::NoSymbol;
|
|
danielsLTR.smooth = false;
|
|
danielsLTR.trend = false;
|
|
danielsLTR.topN = 1;
|
|
danielsLTR.uname = danielsLTR.name = "Daniels LTS Ramp";
|
|
danielsLTR.uunits = "Ramp";
|
|
metrics.append(danielsLTR);
|
|
|
|
// COGGAN LTS
|
|
MetricDetail cogganCTL;
|
|
cogganCTL.type = METRIC_PM;
|
|
cogganCTL.symbol = "coggan_ctl";
|
|
cogganCTL.metric = NULL; // not a factory metric
|
|
cogganCTL.penColor = QColor(Qt::blue);
|
|
cogganCTL.curveStyle = QwtPlotCurve::Lines;
|
|
cogganCTL.symbolStyle = QwtSymbol::NoSymbol;
|
|
cogganCTL.smooth = false;
|
|
cogganCTL.trend = false;
|
|
cogganCTL.topN = 5;
|
|
cogganCTL.uname = cogganCTL.name = "Coggan Chronic Training Load";
|
|
cogganCTL.uunits = "CTL";
|
|
metrics.append(cogganCTL);
|
|
|
|
MetricDetail cogganATL;
|
|
cogganATL.type = METRIC_PM;
|
|
cogganATL.symbol = "coggan_atl";
|
|
cogganATL.metric = NULL; // not a factory metric
|
|
cogganATL.penColor = QColor(Qt::magenta);
|
|
cogganATL.curveStyle = QwtPlotCurve::Lines;
|
|
cogganATL.symbolStyle = QwtSymbol::NoSymbol;
|
|
cogganATL.smooth = false;
|
|
cogganATL.trend = false;
|
|
cogganATL.topN = 5;
|
|
cogganATL.uname = cogganATL.name = "Coggan Acute Training Load";
|
|
cogganATL.uunits = "ATL";
|
|
metrics.append(cogganATL);
|
|
|
|
MetricDetail cogganTSB;
|
|
cogganTSB.type = METRIC_PM;
|
|
cogganTSB.symbol = "coggan_tsb";
|
|
cogganTSB.metric = NULL; // not a factory metric
|
|
cogganTSB.penColor = QColor(Qt::yellow);
|
|
cogganTSB.curveStyle = QwtPlotCurve::Steps;
|
|
cogganTSB.symbolStyle = QwtSymbol::NoSymbol;
|
|
cogganTSB.smooth = false;
|
|
cogganTSB.trend = false;
|
|
cogganTSB.topN = 1;
|
|
cogganTSB.uname = cogganTSB.name = "Coggan Training Stress Balance";
|
|
cogganTSB.uunits = "TSB";
|
|
metrics.append(cogganTSB);
|
|
|
|
MetricDetail cogganSTR;
|
|
cogganSTR.type = METRIC_PM;
|
|
cogganSTR.symbol = "coggan_sr";
|
|
cogganSTR.metric = NULL; // not a factory metric
|
|
cogganSTR.penColor = QColor(Qt::darkGreen);
|
|
cogganSTR.curveStyle = QwtPlotCurve::Steps;
|
|
cogganSTR.symbolStyle = QwtSymbol::NoSymbol;
|
|
cogganSTR.smooth = false;
|
|
cogganSTR.trend = false;
|
|
cogganSTR.topN = 1;
|
|
cogganSTR.uname = cogganSTR.name = "Coggan STS Ramp";
|
|
cogganSTR.uunits = "Ramp";
|
|
metrics.append(cogganSTR);
|
|
|
|
MetricDetail cogganLTR;
|
|
cogganLTR.type = METRIC_PM;
|
|
cogganLTR.symbol = "coggan_lr";
|
|
cogganLTR.metric = NULL; // not a factory metric
|
|
cogganLTR.penColor = QColor(Qt::darkBlue);
|
|
cogganLTR.curveStyle = QwtPlotCurve::Steps;
|
|
cogganLTR.symbolStyle = QwtSymbol::NoSymbol;
|
|
cogganLTR.smooth = false;
|
|
cogganLTR.trend = false;
|
|
cogganLTR.topN = 1;
|
|
cogganLTR.uname = cogganLTR.name = "Coggan LTS Ramp";
|
|
cogganLTR.uunits = "Ramp";
|
|
metrics.append(cogganLTR);
|
|
|
|
// TRIMP LTS
|
|
MetricDetail trimpLTS;
|
|
trimpLTS.type = METRIC_PM;
|
|
trimpLTS.symbol = "trimp_lts";
|
|
trimpLTS.metric = NULL; // not a factory metric
|
|
trimpLTS.penColor = QColor(Qt::blue);
|
|
trimpLTS.curveStyle = QwtPlotCurve::Lines;
|
|
trimpLTS.symbolStyle = QwtSymbol::NoSymbol;
|
|
trimpLTS.smooth = false;
|
|
trimpLTS.trend = false;
|
|
trimpLTS.topN = 5;
|
|
trimpLTS.uname = trimpLTS.name = "TRIMP Long Term Stress";
|
|
trimpLTS.uunits = "Stress";
|
|
metrics.append(trimpLTS);
|
|
|
|
MetricDetail trimpSTS;
|
|
trimpSTS.type = METRIC_PM;
|
|
trimpSTS.symbol = "trimp_sts";
|
|
trimpSTS.metric = NULL; // not a factory metric
|
|
trimpSTS.penColor = QColor(Qt::magenta);
|
|
trimpSTS.curveStyle = QwtPlotCurve::Lines;
|
|
trimpSTS.symbolStyle = QwtSymbol::NoSymbol;
|
|
trimpSTS.smooth = false;
|
|
trimpSTS.trend = false;
|
|
trimpSTS.topN = 5;
|
|
trimpSTS.uname = trimpSTS.name = "TRIMP Short Term Stress";
|
|
trimpSTS.uunits = "Stress";
|
|
metrics.append(trimpSTS);
|
|
|
|
MetricDetail trimpSB;
|
|
trimpSB.type = METRIC_PM;
|
|
trimpSB.symbol = "trimp_sb";
|
|
trimpSB.metric = NULL; // not a factory metric
|
|
trimpSB.penColor = QColor(Qt::yellow);
|
|
trimpSB.curveStyle = QwtPlotCurve::Steps;
|
|
trimpSB.symbolStyle = QwtSymbol::NoSymbol;
|
|
trimpSB.smooth = false;
|
|
trimpSB.trend = false;
|
|
trimpSB.topN = 1;
|
|
trimpSB.uname = trimpSB.name = "TRIMP Stress Balance";
|
|
trimpSB.uunits = "Stress Balance";
|
|
metrics.append(trimpSB);
|
|
|
|
MetricDetail trimpSTR;
|
|
trimpSTR.type = METRIC_PM;
|
|
trimpSTR.symbol = "trimp_sr";
|
|
trimpSTR.metric = NULL; // not a factory metric
|
|
trimpSTR.penColor = QColor(Qt::darkGreen);
|
|
trimpSTR.curveStyle = QwtPlotCurve::Steps;
|
|
trimpSTR.symbolStyle = QwtSymbol::NoSymbol;
|
|
trimpSTR.smooth = false;
|
|
trimpSTR.trend = false;
|
|
trimpSTR.topN = 1;
|
|
trimpSTR.uname = trimpSTR.name = "TRIMP STS Ramp";
|
|
trimpSTR.uunits = "Ramp";
|
|
metrics.append(trimpSTR);
|
|
|
|
MetricDetail trimpLTR;
|
|
trimpLTR.type = METRIC_PM;
|
|
trimpLTR.symbol = "trimp_lr";
|
|
trimpLTR.metric = NULL; // not a factory metric
|
|
trimpLTR.penColor = QColor(Qt::darkBlue);
|
|
trimpLTR.curveStyle = QwtPlotCurve::Steps;
|
|
trimpLTR.symbolStyle = QwtSymbol::NoSymbol;
|
|
trimpLTR.smooth = false;
|
|
trimpLTR.trend = false;
|
|
trimpLTR.topN = 1;
|
|
trimpLTR.uname = trimpLTR.name = "TRIMP LTS Ramp";
|
|
trimpLTR.uunits = "Ramp";
|
|
metrics.append(trimpLTR);
|
|
|
|
// metadata metrics
|
|
SpecialFields sp;
|
|
foreach (FieldDefinition field, main->rideMetadata()->getFields()) {
|
|
if (!sp.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
|
|
MetricDetail metametric;
|
|
metametric.type = METRIC_META;
|
|
QString underscored = field.name;
|
|
metametric.symbol = underscored.replace(" ", "_"); //XXX other special chars!!!
|
|
metametric.metric = NULL; // not a factory metric
|
|
metametric.penColor = QColor(Qt::blue);
|
|
metametric.curveStyle = QwtPlotCurve::Lines;
|
|
metametric.symbolStyle = QwtSymbol::NoSymbol;
|
|
metametric.smooth = false;
|
|
metametric.trend = false;
|
|
metametric.topN = 5;
|
|
metametric.uname = metametric.name = field.name;
|
|
metametric.uunits = "";
|
|
metrics.append(metametric);
|
|
}
|
|
}
|
|
|
|
// measures
|
|
QList<FieldDefinition> measureDefinitions;
|
|
QList<KeywordDefinition> keywordDefinitions; //NOTE: not used in measures.xml
|
|
QString filename = main->home.absolutePath()+"/measures.xml";
|
|
if (!QFile(filename).exists()) filename = ":/xml/measures.xml";
|
|
RideMetadata::readXML(filename, keywordDefinitions, measureDefinitions);
|
|
|
|
foreach (FieldDefinition field, measureDefinitions) {
|
|
if (!sp.isMetric(field.name) && (field.type == 3 || field.type == 4)) {
|
|
MetricDetail measure;
|
|
measure.type = METRIC_MEASURE;
|
|
QString underscored = field.name;
|
|
measure.symbol = field.name; // we don't bother with '_' for measures
|
|
measure.metric = NULL; // not a factory metric
|
|
measure.penColor = QColor(Qt::blue);
|
|
measure.curveStyle = QwtPlotCurve::Lines;
|
|
measure.symbolStyle = QwtSymbol::NoSymbol;
|
|
measure.smooth = false;
|
|
measure.trend = false;
|
|
measure.topN = 5;
|
|
measure.uname = measure.name = field.name;
|
|
measure.uunits = "";
|
|
metrics.append(measure);
|
|
}
|
|
}
|
|
|
|
// sort the list
|
|
qSort(metrics);
|
|
|
|
foreach(MetricDetail metric, metrics) {
|
|
QTreeWidgetItem *add;
|
|
add = new QTreeWidgetItem(allMetrics, METRIC_TYPE);
|
|
add->setText(0, metric.name);
|
|
}
|
|
metricTree->expandItem(allMetrics);
|
|
|
|
configChanged(); // will reset the metric tree
|
|
|
|
ltmSplitter = new QSplitter;
|
|
ltmSplitter->setHandleWidth(1);
|
|
ltmSplitter->setFrameStyle(QFrame::NoFrame);
|
|
ltmSplitter->setContentsMargins(0,0,0,0);
|
|
ltmSplitter->setOrientation(Qt::Vertical);
|
|
|
|
mainLayout->addWidget(ltmSplitter);
|
|
ltmSplitter->addWidget(dateRangeTree);
|
|
ltmSplitter->setCollapsible(0, true);
|
|
ltmSplitter->addWidget(metricTree);
|
|
ltmSplitter->setCollapsible(1, true);
|
|
|
|
connect(dateRangeTree,SIGNAL(itemSelectionChanged()),
|
|
this, SLOT(dateRangeTreeWidgetSelectionChanged()));
|
|
connect(metricTree,SIGNAL(itemSelectionChanged()),
|
|
this, SLOT(metricTreeWidgetSelectionChanged()));
|
|
connect(main, SIGNAL(configChanged()),
|
|
this, SLOT(configChanged()));
|
|
connect(dateRangeTree,SIGNAL(customContextMenuRequested(const QPoint &)),
|
|
this, SLOT(dateRangePopup(const QPoint &)));
|
|
connect(metricTree,SIGNAL(customContextMenuRequested(const QPoint &)),
|
|
this, SLOT(metricTreePopup(const QPoint &)));
|
|
connect(dateRangeTree,SIGNAL(itemChanged(QTreeWidgetItem *,int)),
|
|
this, SLOT(dateRangeChanged(QTreeWidgetItem*, int)));
|
|
connect(seasons, SIGNAL(seasonsChanged()), this, SLOT(resetSeasons()));
|
|
}
|
|
|
|
void
|
|
LTMTool::selectDateRange(int index)
|
|
{
|
|
allDateRanges->child(index)->setSelected(true);
|
|
}
|
|
|
|
QwtPlotCurve::CurveStyle
|
|
LTMTool::curveStyle(RideMetric::MetricType type)
|
|
{
|
|
switch (type) {
|
|
|
|
case RideMetric::Average : return QwtPlotCurve::Lines;
|
|
case RideMetric::Total : return QwtPlotCurve::Steps;
|
|
case RideMetric::Peak : return QwtPlotCurve::Lines;
|
|
default : return QwtPlotCurve::Lines;
|
|
|
|
}
|
|
}
|
|
|
|
QwtSymbol::Style
|
|
LTMTool::symbolStyle(RideMetric::MetricType type)
|
|
{
|
|
switch (type) {
|
|
|
|
case RideMetric::Average : return QwtSymbol::Ellipse;
|
|
case RideMetric::Total : return QwtSymbol::Ellipse;
|
|
case RideMetric::Peak : return QwtSymbol::Rect;
|
|
default : return QwtSymbol::XCross;
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMTool::configChanged()
|
|
{
|
|
}
|
|
|
|
// get/set date range
|
|
QString
|
|
LTMTool::_dateRange() const
|
|
{
|
|
if (dateRangeTree->selectedItems().isEmpty()) return QString("");
|
|
else {
|
|
QTreeWidgetItem *which = dateRangeTree->selectedItems().first();
|
|
return seasons->seasons[allDateRanges->indexOfChild(which)].id().toString();
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMTool::setDateRange(QString s)
|
|
{
|
|
// clear current selection
|
|
dateRangeTree->clearSelection();
|
|
|
|
QUuid find(s);
|
|
|
|
for(int i=0; i<seasons->seasons.count(); i++) {
|
|
if (seasons->seasons[i].id() == find) {
|
|
allDateRanges->child(i)->setSelected(true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Selections Made
|
|
*----------------------------------------------------------------------*/
|
|
|
|
void
|
|
LTMTool::dateRangeTreeWidgetSelectionChanged()
|
|
{
|
|
if (active == true) return;
|
|
|
|
if (dateRangeTree->selectedItems().isEmpty()) dateRange = NULL;
|
|
else {
|
|
QTreeWidgetItem *which = dateRangeTree->selectedItems().first();
|
|
if (which != allDateRanges) {
|
|
dateRange = &seasons->seasons.at(allDateRanges->indexOfChild(which));
|
|
} else {
|
|
dateRange = NULL;
|
|
}
|
|
}
|
|
dateRangeSelected(dateRange);
|
|
}
|
|
|
|
void
|
|
LTMTool::metricTreeWidgetSelectionChanged()
|
|
{
|
|
metricSelected();
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Metric settings
|
|
*--------------------------------------------------------------------*/
|
|
QString
|
|
LTMTool::metricName(QTreeWidgetItem *item)
|
|
{
|
|
int idx = allMetrics->indexOfChild(item);
|
|
if (idx >= 0) return metrics[idx].name;
|
|
else return tr("Unknown Metric");
|
|
}
|
|
|
|
QString
|
|
LTMTool::metricSymbol(QTreeWidgetItem *item)
|
|
{
|
|
int idx = allMetrics->indexOfChild(item);
|
|
if (idx >= 0) return metrics[idx].symbol;
|
|
else return tr("Unknown Metric");
|
|
}
|
|
|
|
MetricDetail
|
|
LTMTool::metricDetails(QTreeWidgetItem *item)
|
|
{
|
|
MetricDetail empty;
|
|
int idx = allMetrics->indexOfChild(item);
|
|
if (idx >= 0) return metrics[idx];
|
|
else return empty;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
LTMTool::metricTreePopup(QPoint pos)
|
|
{
|
|
QTreeWidgetItem *item = metricTree->itemAt(pos);
|
|
if (item != NULL && item->type() != ROOT_TYPE) {
|
|
|
|
// save context
|
|
activeMetric = item;
|
|
|
|
// create context menu
|
|
QMenu menu(metricTree);
|
|
QAction *color = new QAction(tr("Pick Color"), metricTree);
|
|
QAction *edit = new QAction(tr("Settings"), metricTree);
|
|
menu.addAction(color);
|
|
menu.addAction(edit);
|
|
|
|
// connect menu to functions
|
|
connect(color, SIGNAL(triggered(void)), this, SLOT(colorPicker(void)));
|
|
connect(edit, SIGNAL(triggered(void)), this, SLOT(editMetric(void)));
|
|
|
|
// execute the menu
|
|
menu.exec(metricTree->mapToGlobal(pos));
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMTool::editMetric()
|
|
{
|
|
int index = allMetrics->indexOfChild(activeMetric);
|
|
EditMetricDetailDialog dialog(main, &metrics[index]);
|
|
|
|
if (dialog.exec()) {
|
|
// notify of change
|
|
metricSelected();
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMTool::colorPicker()
|
|
{
|
|
int index = allMetrics->indexOfChild(activeMetric);
|
|
QColorDialog picker(main);
|
|
picker.setCurrentColor(metrics[index].penColor);
|
|
QColor color = picker.getColor();
|
|
|
|
// if we got a good color use it and notify others
|
|
if (color.isValid()) {
|
|
metrics[index].penColor = color;
|
|
metricSelected();
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMTool::selectMetric(QString symbol)
|
|
{
|
|
for (int i=0; i<metrics.count(); i++) {
|
|
if (metrics[i].symbol == symbol) {
|
|
allMetrics->child(i)->setSelected(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMTool::applySettings(LTMSettings *settings)
|
|
{
|
|
disconnect(metricTree,SIGNAL(itemSelectionChanged()), this, SLOT(metricTreeWidgetSelectionChanged()));
|
|
metricTree->clearSelection(); // de-select everything
|
|
foreach (MetricDetail metricDetail, settings->metrics) {
|
|
// get index for the symbol
|
|
for (int i=0; i<metrics.count(); i++) {
|
|
|
|
if (metrics[i].symbol == metricDetail.symbol) {
|
|
|
|
// rather than copy each member one by one
|
|
// we save the ridemetric pointer and metric type
|
|
// copy across them all then re-instate the saved point
|
|
RideMetric *saved = (RideMetric*)metrics[i].metric;
|
|
int type = metrics[i].type;
|
|
|
|
metrics[i] = metricDetail;
|
|
metrics[i].metric = saved;
|
|
metrics[i].type = type;
|
|
|
|
// units may need to be adjusted if
|
|
// usemetricUnits changed since charts.xml was
|
|
// written
|
|
if (saved && saved->conversion() != 1.0 &&
|
|
metrics[i].uunits.contains(saved->units(!useMetricUnits)))
|
|
metrics[i].uunits.replace(saved->units(!useMetricUnits), saved->units(useMetricUnits));
|
|
// select it on the tool
|
|
allMetrics->child(i)->setSelected(true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
connect(metricTree,SIGNAL(itemSelectionChanged()), this, SLOT(metricTreeWidgetSelectionChanged()));
|
|
metricTreeWidgetSelectionChanged();
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* EDIT METRIC DETAIL DIALOG
|
|
*--------------------------------------------------------------------*/
|
|
EditMetricDetailDialog::EditMetricDetailDialog(MainWindow *mainWindow, MetricDetail *metricDetail) :
|
|
QDialog(mainWindow, Qt::Dialog), mainWindow(mainWindow), metricDetail(metricDetail)
|
|
{
|
|
setWindowTitle(tr("Settings"));
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
|
|
|
// Metric Name
|
|
mainLayout->addSpacing(5);
|
|
QLabel *metricName = new QLabel(metricDetail->name, this);
|
|
metricName->setAlignment(Qt::AlignHCenter);
|
|
QFont def;
|
|
def.setBold(true);
|
|
metricName->setFont(def);
|
|
mainLayout->addWidget(metricName);
|
|
mainLayout->addSpacing(5);
|
|
|
|
// Grid
|
|
QGridLayout *grid = new QGridLayout;
|
|
|
|
QLabel *name = new QLabel("Name");
|
|
QLabel *units = new QLabel("Axis Label / Units");
|
|
userName = new QLineEdit(this);
|
|
userName->setText(metricDetail->uname);
|
|
userUnits = new QLineEdit(this);
|
|
userUnits->setText(metricDetail->uunits);
|
|
|
|
QLabel *filterlabel = new QLabel(tr("Filter"));
|
|
filter = new QComboBox(this); // no filter / include / exclude
|
|
filter->addItem(tr("No filter"));
|
|
filter->addItem(tr("Include"));
|
|
filter->addItem(tr("Exclude"));
|
|
|
|
QLabel *fromlabel = new QLabel(tr("From"));
|
|
from = new QDoubleSpinBox(this);
|
|
from->setMinimum(-9999999.99);
|
|
from->setMaximum(9999999.99);
|
|
|
|
QLabel *tolabel = new QLabel(tr("To"));
|
|
to = new QDoubleSpinBox(this);
|
|
to->setMinimum(-9999999.99);
|
|
to->setMaximum(9999999.99);
|
|
|
|
showOnPlot = new QCheckBox(tr("Show on plot"), this);
|
|
|
|
QLabel *style = new QLabel("Curve");
|
|
curveStyle = new QComboBox(this);
|
|
curveStyle->addItem("Bar", QwtPlotCurve::Steps);
|
|
curveStyle->addItem("Line", QwtPlotCurve::Lines);
|
|
curveStyle->addItem("Sticks", QwtPlotCurve::Sticks);
|
|
curveStyle->addItem("Dots", QwtPlotCurve::Dots);
|
|
curveStyle->setCurrentIndex(curveStyle->findData(metricDetail->curveStyle));
|
|
|
|
QLabel *stackLabel = new QLabel("Stack");
|
|
stack = new QCheckBox("", this);
|
|
stack->setChecked(metricDetail->stack);
|
|
|
|
|
|
QLabel *symbol = new QLabel("Symbol");
|
|
curveSymbol = new QComboBox(this);
|
|
curveSymbol->addItem("None", QwtSymbol::NoSymbol);
|
|
curveSymbol->addItem("Circle", QwtSymbol::Ellipse);
|
|
curveSymbol->addItem("Square", QwtSymbol::Rect);
|
|
curveSymbol->addItem("Diamond", QwtSymbol::Diamond);
|
|
curveSymbol->addItem("Triangle", QwtSymbol::Triangle);
|
|
curveSymbol->addItem("Cross", QwtSymbol::XCross);
|
|
curveSymbol->addItem("Hexagon", QwtSymbol::Hexagon);
|
|
curveSymbol->addItem("Star", QwtSymbol::Star1);
|
|
curveSymbol->setCurrentIndex(curveSymbol->findData(metricDetail->symbolStyle));
|
|
|
|
QLabel *color = new QLabel("Color");
|
|
curveColor = new QPushButton(this);
|
|
|
|
// color background...
|
|
penColor = metricDetail->penColor;
|
|
setButtonIcon(penColor);
|
|
|
|
QLabel *topN = new QLabel("Highlight Best");
|
|
showBest = new QDoubleSpinBox(this);
|
|
showBest->setDecimals(0);
|
|
showBest->setMinimum(0);
|
|
showBest->setMaximum(999);
|
|
showBest->setSingleStep(1.0);
|
|
showBest->setValue(metricDetail->topN);
|
|
|
|
QLabel *outN = new QLabel("Highlight Outliers");
|
|
showOut = new QDoubleSpinBox(this);
|
|
showOut->setDecimals(0);
|
|
showOut->setMinimum(0);
|
|
showOut->setMaximum(999);
|
|
showOut->setSingleStep(1.0);
|
|
showOut->setValue(metricDetail->topOut);
|
|
|
|
QLabel *baseline = new QLabel("Baseline");
|
|
baseLine = new QDoubleSpinBox(this);
|
|
baseLine->setDecimals(0);
|
|
baseLine->setMinimum(-999999);
|
|
baseLine->setMaximum(999999);
|
|
baseLine->setSingleStep(1.0);
|
|
baseLine->setValue(metricDetail->baseline);
|
|
|
|
curveSmooth = new QCheckBox("Smooth Curve", this);
|
|
curveSmooth->setChecked(metricDetail->smooth);
|
|
|
|
curveTrend = new QCheckBox("Trend Line", this);
|
|
curveTrend->setChecked(metricDetail->trend);
|
|
|
|
// add to grid
|
|
grid->addWidget(name, 0,0);
|
|
grid->addWidget(userName, 0,1);
|
|
grid->addWidget(units, 1,0);
|
|
grid->addWidget(userUnits, 1,1);
|
|
|
|
|
|
grid->addWidget(filterlabel, 2,0);
|
|
grid->addWidget(filter, 2,1);
|
|
grid->addWidget(fromlabel, 3,0);
|
|
grid->addWidget(from, 3,1);
|
|
grid->addWidget(tolabel, 4,0);
|
|
grid->addWidget(to, 4,1);
|
|
grid->addWidget(showOnPlot, 5,1);
|
|
|
|
grid->addWidget(style, 6,0);
|
|
grid->addWidget(curveStyle, 6,1);
|
|
grid->addWidget(symbol, 7,0);
|
|
grid->addWidget(curveSymbol, 7,1);
|
|
grid->addWidget(stackLabel, 8, 0);
|
|
grid->addWidget(stack, 8, 1);
|
|
grid->addWidget(color, 9,0);
|
|
grid->addWidget(curveColor, 9,1);
|
|
grid->addWidget(topN, 10,0);
|
|
grid->addWidget(showBest, 10,1);
|
|
grid->addWidget(outN, 11,0);
|
|
grid->addWidget(showOut, 11,1);
|
|
grid->addWidget(baseline, 12, 0);
|
|
grid->addWidget(baseLine, 12,1);
|
|
grid->addWidget(curveSmooth, 13,1);
|
|
grid->addWidget(curveTrend, 14,1);
|
|
|
|
mainLayout->addLayout(grid);
|
|
mainLayout->addStretch();
|
|
|
|
// Buttons
|
|
QHBoxLayout *buttonLayout = new QHBoxLayout;
|
|
buttonLayout->addStretch();
|
|
applyButton = new QPushButton(tr("&OK"), this);
|
|
cancelButton = new QPushButton(tr("&Cancel"), this);
|
|
buttonLayout->addWidget(cancelButton);
|
|
buttonLayout->addWidget(applyButton);
|
|
mainLayout->addLayout(buttonLayout);
|
|
|
|
// connect up slots
|
|
connect(applyButton, SIGNAL(clicked()), this, SLOT(applyClicked()));
|
|
connect(cancelButton, SIGNAL(clicked()), this, SLOT(cancelClicked()));
|
|
connect(curveColor, SIGNAL(clicked()), this, SLOT(colorClicked()));
|
|
}
|
|
|
|
// uh. i hate enums when you need to modify from ints
|
|
// this is fugly and prone to error. Tied directly to the
|
|
// combo box above. all better solutions gratefully received
|
|
// but wanna get this code running for now
|
|
static QwtPlotCurve::CurveStyle styleMap[] = { QwtPlotCurve::Steps, QwtPlotCurve::Lines,
|
|
QwtPlotCurve::Sticks, QwtPlotCurve::Dots };
|
|
static QwtSymbol::Style symbolMap[] = { QwtSymbol::NoSymbol, QwtSymbol::Ellipse, QwtSymbol::Rect,
|
|
QwtSymbol::Diamond, QwtSymbol::Triangle, QwtSymbol::XCross,
|
|
QwtSymbol::Hexagon, QwtSymbol::Star1 };
|
|
void
|
|
EditMetricDetailDialog::applyClicked()
|
|
{
|
|
// get the values back
|
|
metricDetail->smooth = curveSmooth->isChecked();
|
|
metricDetail->trend = curveTrend->isChecked();
|
|
metricDetail->topN = showBest->value();
|
|
metricDetail->topOut = showOut->value();
|
|
metricDetail->baseline = baseLine->value();
|
|
metricDetail->curveStyle = styleMap[curveStyle->currentIndex()];
|
|
metricDetail->symbolStyle = symbolMap[curveSymbol->currentIndex()];
|
|
metricDetail->penColor = penColor;
|
|
metricDetail->uname = userName->text();
|
|
metricDetail->uunits = userUnits->text();
|
|
metricDetail->stack = stack->isChecked();
|
|
accept();
|
|
}
|
|
void
|
|
EditMetricDetailDialog::cancelClicked()
|
|
{
|
|
reject();
|
|
}
|
|
|
|
void
|
|
EditMetricDetailDialog::colorClicked()
|
|
{
|
|
QColorDialog picker(mainWindow);
|
|
picker.setCurrentColor(penColor);
|
|
QColor color = picker.getColor();
|
|
|
|
if (color.isValid()) {
|
|
setButtonIcon(penColor=color);
|
|
}
|
|
}
|
|
|
|
void
|
|
EditMetricDetailDialog::setButtonIcon(QColor color)
|
|
{
|
|
|
|
// create an icon
|
|
QPixmap pix(24, 24);
|
|
QPainter painter(&pix);
|
|
if (color.isValid()) {
|
|
painter.setPen(Qt::gray);
|
|
painter.setBrush(QBrush(color));
|
|
painter.drawRect(0, 0, 24, 24);
|
|
}
|
|
QIcon icon;
|
|
icon.addPixmap(pix);
|
|
curveColor->setIcon(icon);
|
|
curveColor->setContentsMargins(2,2,2,2);
|
|
curveColor->setFixedWidth(34);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------
|
|
* Seasons stuff
|
|
*--------------------------------------------------------------------*/
|
|
|
|
void
|
|
LTMTool::resetSeasons()
|
|
{
|
|
if (active) return;
|
|
|
|
QString now = _dateRange(); // remeber now
|
|
|
|
active = true;
|
|
int i;
|
|
for (i=allDateRanges->childCount(); i > 0; i--) {
|
|
delete allDateRanges->takeChild(0);
|
|
}
|
|
for (i=0; i <seasons->seasons.count(); i++) {
|
|
Season season = seasons->seasons.at(i);
|
|
QTreeWidgetItem *add = new QTreeWidgetItem(allDateRanges, season.getType());
|
|
add->setText(0, season.getName());
|
|
}
|
|
setDateRange(now); // reselect now
|
|
active = false;
|
|
}
|
|
|
|
int
|
|
LTMTool::newSeason(QString name, QDate start, QDate end, int type)
|
|
{
|
|
seasons->newSeason(name, start, end, type);
|
|
|
|
QTreeWidgetItem *item = new QTreeWidgetItem(USER_DATE);
|
|
item->setText(0, name);
|
|
allDateRanges->insertChild(0, item);
|
|
return 0; // always add at the top
|
|
}
|
|
|
|
void
|
|
LTMTool::updateSeason(int index, QString name, QDate start, QDate end, int type)
|
|
{
|
|
seasons->updateSeason(index, name, start, end, type);
|
|
allDateRanges->child(index)->setText(0, name);
|
|
}
|
|
|
|
void
|
|
LTMTool::dateRangePopup(QPoint pos)
|
|
{
|
|
QTreeWidgetItem *item = dateRangeTree->itemAt(pos);
|
|
if (item != NULL && item->type() != ROOT_TYPE && item->type() != SYS_DATE) {
|
|
|
|
// out of bounds or not user defined
|
|
int index = allDateRanges->indexOfChild(item);
|
|
if (index == -1 || index >= seasons->seasons.count()
|
|
|| seasons->seasons[index].getType() == Season::temporary)
|
|
return;
|
|
|
|
// save context
|
|
activeDateRange = item;
|
|
|
|
// create context menu
|
|
QMenu menu(dateRangeTree);
|
|
QAction *rename = new QAction(tr("Rename range"), dateRangeTree);
|
|
QAction *edit = new QAction(tr("Edit details"), dateRangeTree);
|
|
QAction *del = new QAction(tr("Delete range"), dateRangeTree);
|
|
menu.addAction(rename);
|
|
menu.addAction(edit);
|
|
menu.addAction(del);
|
|
|
|
// connect menu to functions
|
|
connect(rename, SIGNAL(triggered(void)), this, SLOT(renameRange(void)));
|
|
connect(edit, SIGNAL(triggered(void)), this, SLOT(editRange(void)));
|
|
connect(del, SIGNAL(triggered(void)), this, SLOT(deleteRange(void)));
|
|
|
|
// execute the menu
|
|
menu.exec(dateRangeTree->mapToGlobal(pos));
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMTool::renameRange()
|
|
{
|
|
// go edit the name
|
|
activeDateRange->setFlags(activeDateRange->flags() | Qt::ItemIsEditable);
|
|
dateRangeTree->editItem(activeDateRange, 0);
|
|
}
|
|
|
|
void
|
|
LTMTool::dateRangeChanged(QTreeWidgetItem*item, int)
|
|
{
|
|
if (item != activeDateRange || active == true) return;
|
|
|
|
int index = allDateRanges->indexOfChild(item);
|
|
seasons->seasons[index].setName(item->text(0));
|
|
|
|
// save changes away
|
|
active = true;
|
|
seasons->writeSeasons();
|
|
active = false;
|
|
|
|
// signal date selected changed
|
|
//dateRangeSelected(&seasons->seasons[index]);
|
|
}
|
|
|
|
void
|
|
LTMTool::editRange()
|
|
{
|
|
// throw up modal dialog box to edit all the season
|
|
// fields.
|
|
int index = allDateRanges->indexOfChild(activeDateRange);
|
|
EditSeasonDialog dialog(main, &seasons->seasons[index]);
|
|
|
|
if (dialog.exec()) {
|
|
// update name
|
|
activeDateRange->setText(0, seasons->seasons[index].getName());
|
|
|
|
// save changes away
|
|
active = true;
|
|
seasons->writeSeasons();
|
|
active = false;
|
|
|
|
// signal its changed!
|
|
dateRangeSelected(&seasons->seasons[index]);
|
|
}
|
|
}
|
|
|
|
void
|
|
LTMTool::deleteRange()
|
|
{
|
|
// now delete!
|
|
int index = allDateRanges->indexOfChild(activeDateRange);
|
|
delete allDateRanges->takeChild(index);
|
|
seasons->deleteSeason(index);
|
|
}
|