mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 16:18:42 +00:00
UI Nits: Daily/Weekly/Monthly Summary
You can now add the summary chart to the diary view to get a summary of the date range currently being summarised on that view. Once the Home view has its own sidebar that selects date ranges you will be able to add it there too and summarise seasons etc.
This commit is contained in:
@@ -62,8 +62,15 @@ class DBAccess
|
||||
|
||||
// Query Records
|
||||
QList<SummaryMetrics> getAllMetricsFor(QDateTime start, QDateTime end);
|
||||
QList<SummaryMetrics> getAllMetricsFor(DateRange dr) {
|
||||
return getAllMetricsFor(QDateTime(dr.from,QTime(0,0,0)), QDateTime(dr.to, QTime(23,59,59)));
|
||||
}
|
||||
|
||||
bool getRide(QString filename, SummaryMetrics &metrics, QColor&color);
|
||||
QList<SummaryMetrics> getAllMeasuresFor(QDateTime start, QDateTime end);
|
||||
QList<SummaryMetrics> getAllMeasuresFor(DateRange dr) {
|
||||
return getAllMeasuresFor(QDateTime(dr.from,QTime(0,0,0)), QDateTime(dr.to, QTime(23,59,59)));
|
||||
}
|
||||
|
||||
SummaryMetrics getRideMetrics(QString filename); // for a filename
|
||||
|
||||
|
||||
@@ -640,7 +640,7 @@ GcCalendar::setSummary()
|
||||
|
||||
const RideMetric *metric = RideMetricFactory::instance().rideMetric(metricname);
|
||||
|
||||
QString value = getAggregated(metricname, results);
|
||||
QString value = SummaryMetrics::getAggregated(metricname, results, useMetricUnits);
|
||||
|
||||
|
||||
// don't show units for time values
|
||||
@@ -670,72 +670,15 @@ GcCalendar::setSummary()
|
||||
// set webview contents
|
||||
summary->page()->mainFrame()->setHtml(summaryText);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
QString GcCalendar::getAggregated(QString name, QList<SummaryMetrics> &results)
|
||||
{
|
||||
// get the metric details, so we can convert etc
|
||||
const RideMetric *metric = RideMetricFactory::instance().rideMetric(name);
|
||||
if (!metric) return QString("%1 unknown").arg(name);
|
||||
|
||||
// do we need to convert from metric to imperial?
|
||||
bool useMetricUnits = (appsettings->value(this, GC_UNIT).toString() == "Metric");
|
||||
|
||||
// what we will return
|
||||
double rvalue = 0;
|
||||
double rcount = 0; // using double to avoid rounding issues with int when dividing
|
||||
|
||||
// loop through and aggregate
|
||||
foreach (SummaryMetrics rideMetrics, results) {
|
||||
|
||||
// get this value
|
||||
double value = rideMetrics.getForSymbol(name);
|
||||
double count = rideMetrics.getForSymbol("workout_time"); // for averaging
|
||||
|
||||
// tell everyone the date range changed
|
||||
QString name;
|
||||
if (summarySelect->currentIndex() == 0) name = "Day of " + from.toString("dddd MMMM d");
|
||||
else if (summarySelect->currentIndex() == 1) name = QString("Week Commencing %1").arg(from.toString("dddd MMMM d"));
|
||||
else if (summarySelect->currentIndex() == 2) name = from.toString("MMMM yyyy");
|
||||
|
||||
// check values are bounded, just in case
|
||||
if (isnan(value) || isinf(value)) value = 0;
|
||||
emit dateRangeChanged(DateRange(from, to, name));
|
||||
|
||||
// imperial / metric conversion
|
||||
if (useMetricUnits == false) {
|
||||
value *= metric->conversion();
|
||||
value += metric->conversionSum();
|
||||
}
|
||||
|
||||
switch (metric->type()) {
|
||||
case RideMetric::Total:
|
||||
rvalue += value;
|
||||
break;
|
||||
case RideMetric::Average:
|
||||
{
|
||||
// average should be calculated taking into account
|
||||
// the duration of the ride, otherwise high value but
|
||||
// short rides will skew the overall average
|
||||
rvalue += value*count;
|
||||
rcount += count;
|
||||
break;
|
||||
}
|
||||
case RideMetric::Peak:
|
||||
{
|
||||
if (value > rvalue) rvalue = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now compute the average
|
||||
if (metric->type() == RideMetric::Average) {
|
||||
if (rcount) rvalue = rvalue / rcount;
|
||||
}
|
||||
|
||||
|
||||
// Format appropriately
|
||||
QString result;
|
||||
if (metric->units(useMetricUnits) == "seconds") result = time_to_string(rvalue);
|
||||
else result = QString("%1").arg(rvalue, 0, 'f', metric->precision());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <QWebView>
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "TimeUtils.h"
|
||||
#include "GcCalendarModel.h"
|
||||
#include "RideItem.h"
|
||||
#include "RideNavigator.h"
|
||||
@@ -82,9 +83,11 @@ class GcCalendar : public QWidget // not a GcWindow - belongs on sidebar
|
||||
void setSummary(); // set the summary at the bottom
|
||||
|
||||
// summary metrics aggregator -- refactor later
|
||||
QString getAggregated(QString name, QList<SummaryMetrics> &results);
|
||||
void splitterMoved(int pos, int index);
|
||||
|
||||
signals:
|
||||
void dateRangeChanged(DateRange);
|
||||
|
||||
protected:
|
||||
MainWindow *main;
|
||||
RideItem *_ride;
|
||||
|
||||
@@ -67,7 +67,7 @@ GcWindowRegistry GcWindows[] = {
|
||||
{ VIEW_HOME, "Performance Manager",GcWindowTypes::PerformanceManager },
|
||||
{ VIEW_HOME, "Collection TreeMap",GcWindowTypes::TreeMap },
|
||||
{ VIEW_HOME, "Weekly Summary",GcWindowTypes::WeeklySummary },
|
||||
{ VIEW_ANALYSIS, "Summary",GcWindowTypes::RideSummary },
|
||||
{ VIEW_ANALYSIS, "Activity Summary",GcWindowTypes::RideSummary },
|
||||
{ VIEW_ANALYSIS, "Details",GcWindowTypes::MetadataWindow },
|
||||
{ VIEW_ANALYSIS, "Summary and Details",GcWindowTypes::Summary },
|
||||
{ VIEW_ANALYSIS, "Editor",GcWindowTypes::RideEditor },
|
||||
@@ -83,6 +83,7 @@ GcWindowRegistry GcWindows[] = {
|
||||
{ VIEW_ANALYSIS, "Aerolab Chung Analysis",GcWindowTypes::Aerolab },
|
||||
{ VIEW_DIARY, "Calendar",GcWindowTypes::Diary },
|
||||
{ VIEW_DIARY, "Navigator", GcWindowTypes::ActivityNavigator },
|
||||
{ VIEW_DIARY, "Summary", GcWindowTypes::DateRangeSummary },
|
||||
{ VIEW_TRAIN, "Telemetry",GcWindowTypes::DialWindow },
|
||||
{ VIEW_TRAIN, "Workout",GcWindowTypes::WorkoutPlot },
|
||||
{ VIEW_TRAIN, "Realtime",GcWindowTypes::RealtimePlot },
|
||||
@@ -119,7 +120,8 @@ GcWindowRegistry::newGcWindow(GcWinID id, MainWindow *main) //XXX mainWindow wil
|
||||
case GcWindowTypes::PfPv: returning = new PfPvWindow(main); break;
|
||||
case GcWindowTypes::HrPw: returning = new HrPwWindow(main); break;
|
||||
case GcWindowTypes::RideEditor: returning = new RideEditor(main); break;
|
||||
case GcWindowTypes::RideSummary: returning = new RideSummaryWindow(main); break;
|
||||
case GcWindowTypes::RideSummary: returning = new RideSummaryWindow(main, true); break;
|
||||
case GcWindowTypes::DateRangeSummary: returning = new RideSummaryWindow(main, false); break;
|
||||
case GcWindowTypes::Scatter: returning = new ScatterWindow(main, main->home); break;
|
||||
case GcWindowTypes::Summary: returning = new SummaryWindow(main); break;
|
||||
case GcWindowTypes::TreeMap: returning = new TreeMapWindow(main, main->useMetricUnits, main->home); break;
|
||||
|
||||
@@ -57,6 +57,7 @@ enum gcwinid {
|
||||
RealtimeControls = 29,
|
||||
ActivityNavigator = 30,
|
||||
SpinScanPlot = 31,
|
||||
DateRangeSummary = 32
|
||||
};
|
||||
};
|
||||
typedef enum GcWindowTypes::gcwinid GcWinID;
|
||||
|
||||
@@ -90,6 +90,7 @@ void GcWindow::setRideItem(RideItem* x)
|
||||
void GcWindow::setDateRange(DateRange dr)
|
||||
{
|
||||
_dr = dr;
|
||||
emit dateRangeChanged(_dr);
|
||||
}
|
||||
|
||||
DateRange GcWindow::dateRange() const
|
||||
@@ -154,6 +155,7 @@ GcWindow::GcWindow()
|
||||
qRegisterMetaType<RideItem*>("ride");
|
||||
qRegisterMetaType<GcWinID>("type");
|
||||
qRegisterMetaType<QColor>("color");
|
||||
qRegisterMetaType<DateRange>("dateRange");
|
||||
setControls(NULL);
|
||||
setRideItem(NULL);
|
||||
setTitle("");
|
||||
@@ -185,6 +187,7 @@ GcWindow::GcWindow(QWidget *parent) : QFrame(parent), dragState(None) {
|
||||
qRegisterMetaType<RideItem*>("ride");
|
||||
qRegisterMetaType<GcWinID>("type");
|
||||
qRegisterMetaType<QColor>("color");
|
||||
qRegisterMetaType<DateRange>("dateRange");
|
||||
setParent(parent);
|
||||
setControls(NULL);
|
||||
setRideItem(NULL);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#define G_OBJECT Q_PROPERTY(QString instanceName READ instanceName WRITE setInstanceName)
|
||||
#define setInstanceName(x) setProperty("instanceName", x)
|
||||
#define myRideItem property("ride").value<RideItem*>()
|
||||
#define myDateRange property("dateRange").value<DateRange>()
|
||||
|
||||
#include <QString>
|
||||
#include <QMenu>
|
||||
|
||||
@@ -202,6 +202,7 @@ HomeWindow::HomeWindow(MainWindow *mainWindow, QString name, QString /* windowti
|
||||
styleChanged(2);
|
||||
|
||||
connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideSelected()));
|
||||
connect(this, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateRangeChanged(DateRange)));
|
||||
connect(mainWindow, SIGNAL(configChanged()), this, SLOT(configChanged()));
|
||||
connect(tabbed, SIGNAL(currentChanged(int)), this, SLOT(tabSelected(int)));
|
||||
connect(tabbed, SIGNAL(tabCloseRequested(int)), this, SLOT(removeChart(int)));
|
||||
@@ -359,6 +360,27 @@ HomeWindow::rideSelected()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HomeWindow::dateRangeChanged(DateRange dr)
|
||||
{
|
||||
if (amVisible()) {
|
||||
|
||||
for (int i=0; i < charts.count(); i++) {
|
||||
|
||||
// show if its not a tab
|
||||
if (currentStyle) charts[i]->show(); // keep tabs hidden, show the rest
|
||||
|
||||
// if we are tabbed AND its the current tab then mimic tabselected
|
||||
// to force the tabwidget to refresh AND set its ride property (see below)
|
||||
//otherwise just go ahead and notify it of a new ride
|
||||
if (!currentStyle && charts.count() && i==tabbed->currentIndex())
|
||||
tabSelected(tabbed->currentIndex());
|
||||
else
|
||||
charts[i]->setProperty("dateRange", property("dateRange"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HomeWindow::tabSelected(int index)
|
||||
{
|
||||
@@ -367,6 +389,7 @@ HomeWindow::tabSelected(int index)
|
||||
if (index >= 0) {
|
||||
charts[index]->show();
|
||||
charts[index]->setProperty("ride", property("ride"));
|
||||
charts[index]->setProperty("dateRange", property("dateRange"));
|
||||
controlStack->setCurrentIndex(index);
|
||||
titleEdit->setText(charts[index]->property("title").toString());
|
||||
}
|
||||
@@ -430,6 +453,7 @@ HomeWindow::styleChanged(int id)
|
||||
tabbed->addTab(charts[i], charts[i]->property("title").toString());
|
||||
charts[i]->setContentsMargins(0,25,0,0);
|
||||
charts[i]->setResizable(false); // we need to show on tab selection!
|
||||
charts[i]->setProperty("dateRange", property("dateRange"));
|
||||
charts[i]->hide(); // we need to show on tab selection!
|
||||
break;
|
||||
case 1 : // they are lists in a GridLayout
|
||||
@@ -437,6 +461,7 @@ HomeWindow::styleChanged(int id)
|
||||
charts[i]->setContentsMargins(0,25,0,0);
|
||||
charts[i]->setResizable(false); // we need to show on tab selection!
|
||||
charts[i]->show();
|
||||
charts[i]->setProperty("dateRange", property("dateRange"));
|
||||
charts[i]->setProperty("ride", property("ride"));
|
||||
break;
|
||||
case 2 : // thet are in a FlowLayout
|
||||
@@ -444,6 +469,7 @@ HomeWindow::styleChanged(int id)
|
||||
charts[i]->setContentsMargins(0,15,0,0);
|
||||
charts[i]->setResizable(true); // we need to show on tab selection!
|
||||
charts[i]->show();
|
||||
charts[i]->setProperty("dateRange", property("dateRange"));
|
||||
charts[i]->setProperty("ride", property("ride"));
|
||||
default:
|
||||
break;
|
||||
@@ -571,6 +597,7 @@ HomeWindow::addChart(GcWindow* newone)
|
||||
|
||||
RideItem *notconst = (RideItem*)mainWindow->currentRideItem();
|
||||
newone->setProperty("ride", QVariant::fromValue<RideItem*>(notconst));
|
||||
newone->setProperty("dateRange", property("dateRange"));
|
||||
|
||||
// add to tabs
|
||||
switch (currentStyle) {
|
||||
@@ -1219,6 +1246,7 @@ GcWindowDialog::GcWindowDialog(GcWinID type, MainWindow *mainWindow) : mainWindo
|
||||
|
||||
RideItem *notconst = (RideItem*)mainWindow->currentRideItem();
|
||||
win->setProperty("ride", QVariant::fromValue<RideItem*>(notconst));
|
||||
win->setProperty("dateRange", property("dateRange"));
|
||||
|
||||
layout->setStretch(0, 100);
|
||||
layout->setStretch(1, 50);
|
||||
|
||||
@@ -55,6 +55,7 @@ class HomeWindow : public GcWindow
|
||||
|
||||
// GC signals
|
||||
void rideSelected();
|
||||
void dateRangeChanged(DateRange);
|
||||
void configChanged();
|
||||
|
||||
// QT Widget events and signals
|
||||
|
||||
@@ -517,10 +517,6 @@ MainWindow::MainWindow(const QDir &home) :
|
||||
chartTool = new GcWindowTool(this);
|
||||
#endif
|
||||
|
||||
// RIGHT SIDEBAR
|
||||
gcCalendar = new GcCalendar(this);
|
||||
//gcCalendar->setStyleSheet("background: #B3B4BA;");
|
||||
//gcCalendar->setStyleSheet("background: white;");
|
||||
|
||||
// TOOLBOX
|
||||
toolBox = new QToolBox(this);
|
||||
@@ -584,6 +580,10 @@ MainWindow::MainWindow(const QDir &home) :
|
||||
|
||||
// POPULATE TOOLBOX
|
||||
|
||||
// do controllers after home windows -- they need their first signals caught
|
||||
gcCalendar = new GcCalendar(this);
|
||||
connect(gcCalendar, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateRangeChanged(DateRange)));
|
||||
|
||||
toolBox->addItem(intervalSplitter, QIcon(":images/activity.png"), "Activity History");
|
||||
toolBox->addItem(gcCalendar, QIcon(":images/toolbar/main/diary.png"), "Calendar");
|
||||
toolBox->addItem(trainTool->controls(), QIcon(":images/library.png"), "Workout Library");
|
||||
@@ -1037,6 +1037,15 @@ MainWindow::rideTreeWidgetSelectionChanged()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::dateRangeChanged(DateRange dr)
|
||||
{
|
||||
// we got signalled date range changed, tell the current view
|
||||
// when we have multiple sidebars that change date we need to connect
|
||||
// them up individually.... i.e. LTM....
|
||||
diaryWindow->setProperty("dateRange", QVariant::fromValue<DateRange>(dr));
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::enableSaveButton()
|
||||
{
|
||||
@@ -1326,6 +1335,7 @@ MainWindow::selectDiary()
|
||||
analButtons->hide();
|
||||
trainTool->getToolbarButtons()->hide();
|
||||
toolBox->setCurrentIndex(1);
|
||||
gcCalendar->refresh(); // get that signal with the date range...
|
||||
setStyle();
|
||||
}
|
||||
|
||||
|
||||
@@ -323,6 +323,8 @@ class MainWindow : public QMainWindow
|
||||
void toggleFullScreen();
|
||||
#endif
|
||||
|
||||
void dateRangeChanged(DateRange);
|
||||
|
||||
protected:
|
||||
|
||||
static QString notesFileName(QString rideFileName);
|
||||
|
||||
@@ -306,6 +306,12 @@ MetricAggregator::writeAsCSV(QString filename)
|
||||
file.close();
|
||||
}
|
||||
|
||||
QList<SummaryMetrics>
|
||||
MetricAggregator::getAllMetricsFor(DateRange dr)
|
||||
{
|
||||
return getAllMetricsFor(QDateTime(dr.from, QTime(0,0,0)), QDateTime(dr.to, QTime(23,59,59)));
|
||||
}
|
||||
|
||||
QList<SummaryMetrics>
|
||||
MetricAggregator::getAllMetricsFor(QDateTime start, QDateTime end)
|
||||
{
|
||||
@@ -349,6 +355,12 @@ MetricAggregator::getAllMetricsFor(QString filename)
|
||||
return results;
|
||||
}
|
||||
|
||||
QList<SummaryMetrics>
|
||||
MetricAggregator::getAllMeasuresFor(DateRange dr)
|
||||
{
|
||||
return getAllMeasuresFor(QDateTime(dr.from, QTime(0,0,0)), QDateTime(dr.to, QTime(23,59,59)));
|
||||
}
|
||||
|
||||
QList<SummaryMetrics>
|
||||
MetricAggregator::getAllMeasuresFor(QDateTime start, QDateTime end)
|
||||
{
|
||||
|
||||
@@ -48,7 +48,9 @@ class MetricAggregator : public QObject
|
||||
DBAccess *db() { return dbaccess; }
|
||||
SummaryMetrics getAllMetricsFor(QString filename); // for a single ride
|
||||
QList<SummaryMetrics> getAllMetricsFor(QDateTime start, QDateTime end);
|
||||
QList<SummaryMetrics> getAllMetricsFor(DateRange);
|
||||
QList<SummaryMetrics> getAllMeasuresFor(QDateTime start, QDateTime end);
|
||||
QList<SummaryMetrics> getAllMeasuresFor(DateRange);
|
||||
SummaryMetrics getRideMetrics(QString filename);
|
||||
void writeAsCSV(QString filename); // export all...
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
RideSummaryWindow::RideSummaryWindow(MainWindow *mainWindow) :
|
||||
GcWindow(mainWindow), mainWindow(mainWindow)
|
||||
RideSummaryWindow::RideSummaryWindow(MainWindow *mainWindow, bool ridesummary) :
|
||||
GcWindow(mainWindow), mainWindow(mainWindow), ridesummary(ridesummary)
|
||||
{
|
||||
setInstanceName("Ride Summary Window");
|
||||
setControls(NULL);
|
||||
@@ -54,9 +54,15 @@ RideSummaryWindow::RideSummaryWindow(MainWindow *mainWindow) :
|
||||
|
||||
vlayout->addWidget(rideSummary);
|
||||
|
||||
connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideItemChanged()));
|
||||
connect(mainWindow, SIGNAL(zonesChanged()), this, SLOT(refresh()));
|
||||
connect(mainWindow, SIGNAL(intervalsChanged()), this, SLOT(refresh()));
|
||||
if (ridesummary) {
|
||||
connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideItemChanged()));
|
||||
connect(mainWindow, SIGNAL(zonesChanged()), this, SLOT(refresh()));
|
||||
connect(mainWindow, SIGNAL(intervalsChanged()), this, SLOT(refresh()));
|
||||
} else {
|
||||
connect(this, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateRangeChanged(DateRange)));
|
||||
connect(mainWindow, SIGNAL(rideAdded(RideItem*)), this, SLOT(refresh()));
|
||||
connect(mainWindow, SIGNAL(rideDeleted(RideItem*)), this, SLOT(refresh()));
|
||||
}
|
||||
setLayout(vlayout);
|
||||
}
|
||||
|
||||
@@ -93,15 +99,27 @@ RideSummaryWindow::refresh()
|
||||
{
|
||||
// XXX: activeTab is never equaly to RideSummaryWindow right now because
|
||||
// it's wrapped in the summarySplitter in MainWindow.
|
||||
if (!myRideItem) {
|
||||
if (ridesummary && !myRideItem) {
|
||||
rideSummary->page()->mainFrame()->setHtml("");
|
||||
return;
|
||||
}
|
||||
RideItem *rideItem = myRideItem;
|
||||
setSubTitle(rideItem->dateTime.toString(tr("dddd MMMM d, yyyy, h:mm AP")));
|
||||
|
||||
if (ridesummary) {
|
||||
RideItem *rideItem = myRideItem;
|
||||
setSubTitle(rideItem->dateTime.toString(tr("dddd MMMM d, yyyy, h:mm AP")));
|
||||
} else {
|
||||
|
||||
if (myDateRange.name != "") setSubTitle(myDateRange.name);
|
||||
else {
|
||||
setSubTitle(myDateRange.from.toString("dddd MMMM d") +
|
||||
" - " +
|
||||
myDateRange.to.toString("dddd MMMM d"));
|
||||
}
|
||||
}
|
||||
rideSummary->page()->mainFrame()->setHtml(htmlSummary());
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
RideSummaryWindow::htmlSummary() const
|
||||
{
|
||||
@@ -109,9 +127,11 @@ RideSummaryWindow::htmlSummary() const
|
||||
|
||||
RideItem *rideItem = myRideItem;
|
||||
RideFile *ride = rideItem->ride();
|
||||
QVariant unit = appsettings->value(this, GC_UNIT);
|
||||
bool useMetricUnits = (unit.toString() == "Metric");
|
||||
|
||||
// ridefile read errors?
|
||||
if (!ride) {
|
||||
// ride summary and there were ridefile read errors?
|
||||
if (ridesummary && !ride) {
|
||||
summary = tr("<p>Couldn't read file \"");
|
||||
summary += rideItem->fileName + "\":";
|
||||
QListIterator<QString> i(rideItem->errors());
|
||||
@@ -120,58 +140,48 @@ RideSummaryWindow::htmlSummary() const
|
||||
return summary;
|
||||
}
|
||||
|
||||
summary = ("<center><p><h3>" + tr("Device Type: ") + ride->deviceType() + "</h3>");
|
||||
// always centered
|
||||
summary = "<center>";
|
||||
|
||||
QVariant unit = appsettings->value(this, GC_UNIT);
|
||||
// device summary for ride summary, otherwise how many activities?
|
||||
if (ridesummary) summary += ("<p><h3>" + tr("Device Type: ") + ride->deviceType() + "</h3><p>");
|
||||
else summary += ("<p><h3>" +
|
||||
QString("%1").arg(data.count()) + (data.count() == 1 ? tr(" activity") : tr(" activities")) +
|
||||
"</h3><p>");
|
||||
|
||||
summary += "<p>";
|
||||
|
||||
bool metricUnits = (unit.toString() == "Metric");
|
||||
|
||||
QStringList columnNames = QStringList() << "Totals" <<"Averages" <<"Maximums" <<"Metrics*";
|
||||
QStringList totalColumn = QStringList()
|
||||
// All the metrics we will display
|
||||
static const QStringList columnNames = QStringList() << "Totals" <<"Averages" <<"Maximums" <<"Metrics*";
|
||||
static const QStringList totalColumn = QStringList()
|
||||
<< "workout_time"
|
||||
<< "time_riding"
|
||||
<< "total_distance"
|
||||
<< "total_work"
|
||||
<< "elevation_gain";
|
||||
|
||||
QStringList averageColumn = QStringList()
|
||||
static QStringList averageColumn = QStringList() // not const as modified below..
|
||||
<< "average_speed"
|
||||
<< "average_power"
|
||||
<< "average_hr"
|
||||
<< "average_cad";
|
||||
|
||||
// show temp if it is available
|
||||
if (ride->areDataPresent()->temp || ride->getTag("Temperature", "-") != "-") averageColumn << "average_temp";
|
||||
|
||||
QStringList maximumColumn = QStringList()
|
||||
static QStringList maximumColumn = QStringList() // not const as modified below..
|
||||
<< "max_speed"
|
||||
<< "max_power"
|
||||
<< "max_heartrate"
|
||||
<< "max_cadence";
|
||||
|
||||
// show temp if it is available
|
||||
if (ride->areDataPresent()->temp || ride->getTag("Temperature", "-") != "-") maximumColumn << "max_temp";
|
||||
// show average and max temp if it is available (in ride summary mode)
|
||||
if (ridesummary && (ride->areDataPresent()->temp || ride->getTag("Temperature", "-") != "-")) {
|
||||
averageColumn << "average_temp";
|
||||
maximumColumn << "max_temp";
|
||||
}
|
||||
|
||||
// users determine the metrics to display
|
||||
QString s = appsettings->value(this, GC_SETTINGS_SUMMARY_METRICS, GC_SETTINGS_SUMMARY_METRICS_DEFAULT).toString();
|
||||
|
||||
// in case they were set tand then unset
|
||||
if (s == "") s = GC_SETTINGS_SUMMARY_METRICS_DEFAULT;
|
||||
QStringList metricColumn = s.split(",");
|
||||
|
||||
/* ORIGINAL HARDCODED (AND NOW DEFAULT METRICS)
|
||||
QStringList metricColumn = QStringList()
|
||||
<< "skiba_xpower"
|
||||
<< "skiba_relative_intensity"
|
||||
<< "skiba_bike_score"
|
||||
<< "daniels_points"
|
||||
<< "daniels_equivalent_power"
|
||||
<< "trimp_points"
|
||||
<< "aerobic_decoupling";
|
||||
*/
|
||||
|
||||
QStringList timeInZones = QStringList()
|
||||
static const QStringList timeInZones = QStringList()
|
||||
<< "time_in_zone_L1"
|
||||
<< "time_in_zone_L2"
|
||||
<< "time_in_zone_L3"
|
||||
@@ -183,7 +193,7 @@ RideSummaryWindow::htmlSummary() const
|
||||
<< "time_in_zone_L9"
|
||||
<< "time_in_zone_L10";
|
||||
|
||||
QStringList timeInZonesHR = QStringList()
|
||||
static const QStringList timeInZonesHR = QStringList()
|
||||
<< "time_in_zone_H1"
|
||||
<< "time_in_zone_H2"
|
||||
<< "time_in_zone_H3"
|
||||
@@ -195,35 +205,41 @@ RideSummaryWindow::htmlSummary() const
|
||||
|
||||
// Use pre-computed and saved metric values if the ride has not
|
||||
// been edited. Otherwise we need to re-compute every time.
|
||||
// this is only for ride summary, when showing for a date range
|
||||
// we already have a summary metrics array
|
||||
SummaryMetrics metrics;
|
||||
RideMetricFactory &factory = RideMetricFactory::instance();
|
||||
if (rideItem->isDirty()) {
|
||||
// make a list if the metrics we want computed
|
||||
// instead of calculating them all, just do the
|
||||
// ones we display
|
||||
QStringList worklist;
|
||||
worklist += totalColumn;
|
||||
worklist += averageColumn;
|
||||
worklist += maximumColumn;
|
||||
worklist += metricColumn;
|
||||
worklist += timeInZones;
|
||||
worklist += timeInZonesHR;
|
||||
|
||||
// go calculate them then...
|
||||
QHash<QString, RideMetricPtr> computed = RideMetric::computeMetrics(mainWindow, ride, mainWindow->zones(), mainWindow->hrZones(), worklist);
|
||||
for(int i = 0; i < worklist.count(); ++i) {
|
||||
if (worklist[i] != "") {
|
||||
RideMetricPtr m = computed.value(worklist[i]);
|
||||
if (m) metrics.setForSymbol(worklist[i], m->value(true));
|
||||
else metrics.setForSymbol(worklist[i], 0.00);
|
||||
if (ridesummary) {
|
||||
if (rideItem->isDirty()) {
|
||||
// make a list if the metrics we want computed
|
||||
// instead of calculating them all, just do the
|
||||
// ones we display
|
||||
QStringList worklist;
|
||||
worklist += totalColumn;
|
||||
worklist += averageColumn;
|
||||
worklist += maximumColumn;
|
||||
worklist += metricColumn;
|
||||
worklist += timeInZones;
|
||||
worklist += timeInZonesHR;
|
||||
|
||||
// go calculate them then...
|
||||
QHash<QString, RideMetricPtr> computed = RideMetric::computeMetrics(mainWindow, ride, mainWindow->zones(), mainWindow->hrZones(), worklist);
|
||||
for(int i = 0; i < worklist.count(); ++i) {
|
||||
if (worklist[i] != "") {
|
||||
RideMetricPtr m = computed.value(worklist[i]);
|
||||
if (m) metrics.setForSymbol(worklist[i], m->value(true));
|
||||
else metrics.setForSymbol(worklist[i], 0.00);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else {
|
||||
|
||||
// just use the metricDB versions, nice 'n fast
|
||||
metrics = mainWindow->metricDB->getRideMetrics(rideItem->fileName);
|
||||
// just use the metricDB versions, nice 'n fast
|
||||
metrics = mainWindow->metricDB->getRideMetrics(rideItem->fileName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// 3 top columns - total, average, maximums and metrics for entire ride
|
||||
//
|
||||
@@ -257,19 +273,28 @@ RideSummaryWindow::htmlSummary() const
|
||||
s = s.arg(m->name().replace(QRegExp(tr("^(Average|Max) ")), ""));
|
||||
|
||||
// Add units (if needed) and value (with right precision)
|
||||
if (m->units(metricUnits) == "seconds") {
|
||||
if (m->units(useMetricUnits) == "seconds") {
|
||||
s = s.arg(""); // no units
|
||||
s = s.arg(time_to_string(metrics.getForSymbol(symbol)));
|
||||
|
||||
// get the value - from metrics or from data array
|
||||
if (ridesummary) s = s.arg(time_to_string(metrics.getForSymbol(symbol)));
|
||||
else s = s.arg(SummaryMetrics::getAggregated(symbol, data, useMetricUnits));
|
||||
|
||||
} else {
|
||||
if (m->units(metricUnits) != "") s = s.arg(" (" + m->units(metricUnits) + ")");
|
||||
if (m->units(useMetricUnits) != "") s = s.arg(" (" + m->units(useMetricUnits) + ")");
|
||||
else s = s.arg("");
|
||||
|
||||
// temperature is a special case, if it is not present fall back to metadata tag
|
||||
// if that is not present then just display '-'
|
||||
if ((symbol == "average_temp" || symbol == "max_temp") && metrics.getForSymbol(symbol) == RideFile::noTemp)
|
||||
s = s.arg(ride->getTag("Temperature", "-"));
|
||||
else
|
||||
s = s.arg(metrics.getForSymbol(symbol) * (metricUnits ? 1 : m->conversion()) + (metricUnits ? 0 : m->conversionSum()), 0, 'f', m->precision());
|
||||
else {
|
||||
|
||||
// get the value - from metrics or from data array
|
||||
if (ridesummary) s = s.arg(metrics.getForSymbol(symbol) * (useMetricUnits ? 1 : m->conversion())
|
||||
+ (useMetricUnits ? 0 : m->conversionSum()), 0, 'f', m->precision());
|
||||
else s = s.arg(SummaryMetrics::getAggregated(symbol, data, useMetricUnits));
|
||||
}
|
||||
}
|
||||
|
||||
summary += s;
|
||||
@@ -283,10 +308,14 @@ RideSummaryWindow::htmlSummary() const
|
||||
//
|
||||
if (rideItem->numZones() > 0) {
|
||||
QVector<double> time_in_zone(rideItem->numZones());
|
||||
for (int i = 0; i < rideItem->numZones(); ++i)
|
||||
time_in_zone[i] = metrics.getForSymbol(timeInZones[i]);
|
||||
for (int i = 0; i < rideItem->numZones(); ++i) {
|
||||
|
||||
// if using metrics or data
|
||||
if (ridesummary) time_in_zone[i] = metrics.getForSymbol(timeInZones[i]);
|
||||
else time_in_zone[i] = SummaryMetrics::getAggregated(timeInZones[i], data, useMetricUnits, true).toDouble();
|
||||
}
|
||||
summary += tr("<h3>Power Zones</h3>");
|
||||
summary += mainWindow->zones()->summarize(rideItem->zoneRange(), time_in_zone);
|
||||
summary += mainWindow->zones()->summarize(rideItem->zoneRange(), time_in_zone); //XXX aggregating?
|
||||
}
|
||||
|
||||
//
|
||||
@@ -294,84 +323,95 @@ RideSummaryWindow::htmlSummary() const
|
||||
//
|
||||
if (rideItem->numHrZones() > 0) {
|
||||
QVector<double> time_in_zone(rideItem->numHrZones());
|
||||
for (int i = 0; i < rideItem->numHrZones(); ++i)
|
||||
time_in_zone[i] = metrics.getForSymbol(timeInZonesHR[i]);
|
||||
for (int i = 0; i < rideItem->numHrZones(); ++i) {
|
||||
|
||||
// if using metrics or data
|
||||
if (ridesummary) time_in_zone[i] = metrics.getForSymbol(timeInZonesHR[i]);
|
||||
else time_in_zone[i] = SummaryMetrics::getAggregated(timeInZonesHR[i], data, useMetricUnits, true).toDouble();
|
||||
}
|
||||
|
||||
summary += tr("<h3>Heart Rate Zones</h3>");
|
||||
summary += mainWindow->hrZones()->summarize(rideItem->hrZoneRange(), time_in_zone);
|
||||
summary += mainWindow->hrZones()->summarize(rideItem->hrZoneRange(), time_in_zone); //XXX aggregating
|
||||
}
|
||||
|
||||
//
|
||||
// Interval Summary (recalculated on every refresh since they are not cached at present)
|
||||
//
|
||||
if (ride->intervals().size() > 0) {
|
||||
bool firstRow = true;
|
||||
QString s;
|
||||
if (appsettings->contains(GC_SETTINGS_INTERVAL_METRICS))
|
||||
s = appsettings->value(this, GC_SETTINGS_INTERVAL_METRICS).toString();
|
||||
else
|
||||
s = GC_SETTINGS_INTERVAL_METRICS_DEFAULT;
|
||||
QStringList intervalMetrics = s.split(",");
|
||||
summary += "<p><h3>"+tr("Intervals")+"</h3>\n<p>\n";
|
||||
summary += "<table align=\"center\" width=\"90%\" ";
|
||||
summary += "cellspacing=0 border=0>";
|
||||
bool even = false;
|
||||
foreach (RideFileInterval interval, ride->intervals()) {
|
||||
RideFile f(ride->startTime(), ride->recIntSecs());
|
||||
for (int i = ride->intervalBegin(interval); i < ride->dataPoints().size(); ++i) {
|
||||
const RideFilePoint *p = ride->dataPoints()[i];
|
||||
if (p->secs >= interval.stop)
|
||||
break;
|
||||
f.appendPoint(p->secs, p->cad, p->hr, p->km, p->kph, p->nm,
|
||||
p->watts, p->alt, p->lon, p->lat, p->headwind,
|
||||
p->slope, p->temp, p->lrbalance, 0);
|
||||
}
|
||||
if (f.dataPoints().size() == 0) {
|
||||
// Interval empty, do not compute any metrics
|
||||
continue;
|
||||
}
|
||||
// Only get interval summary for a ride summary
|
||||
if (ridesummary) {
|
||||
|
||||
QHash<QString,RideMetricPtr> metrics =
|
||||
RideMetric::computeMetrics(mainWindow, &f, mainWindow->zones(), mainWindow->hrZones(), intervalMetrics);
|
||||
if (firstRow) {
|
||||
summary += "<tr>";
|
||||
summary += "<td align=\"center\" valign=\"bottom\">Interval Name</td>";
|
||||
//
|
||||
// Interval Summary (recalculated on every refresh since they are not cached at present)
|
||||
//
|
||||
if (ride->intervals().size() > 0) {
|
||||
bool firstRow = true;
|
||||
QString s;
|
||||
if (appsettings->contains(GC_SETTINGS_INTERVAL_METRICS))
|
||||
s = appsettings->value(this, GC_SETTINGS_INTERVAL_METRICS).toString();
|
||||
else
|
||||
s = GC_SETTINGS_INTERVAL_METRICS_DEFAULT;
|
||||
QStringList intervalMetrics = s.split(",");
|
||||
summary += "<p><h3>"+tr("Intervals")+"</h3>\n<p>\n";
|
||||
summary += "<table align=\"center\" width=\"90%\" ";
|
||||
summary += "cellspacing=0 border=0>";
|
||||
bool even = false;
|
||||
foreach (RideFileInterval interval, ride->intervals()) {
|
||||
RideFile f(ride->startTime(), ride->recIntSecs());
|
||||
for (int i = ride->intervalBegin(interval); i < ride->dataPoints().size(); ++i) {
|
||||
const RideFilePoint *p = ride->dataPoints()[i];
|
||||
if (p->secs >= interval.stop)
|
||||
break;
|
||||
f.appendPoint(p->secs, p->cad, p->hr, p->km, p->kph, p->nm,
|
||||
p->watts, p->alt, p->lon, p->lat, p->headwind,
|
||||
p->slope, p->temp, p->lrbalance, 0);
|
||||
}
|
||||
if (f.dataPoints().size() == 0) {
|
||||
// Interval empty, do not compute any metrics
|
||||
continue;
|
||||
}
|
||||
|
||||
QHash<QString,RideMetricPtr> metrics =
|
||||
RideMetric::computeMetrics(mainWindow, &f, mainWindow->zones(), mainWindow->hrZones(), intervalMetrics);
|
||||
if (firstRow) {
|
||||
summary += "<tr>";
|
||||
summary += "<td align=\"center\" valign=\"bottom\">Interval Name</td>";
|
||||
foreach (QString symbol, intervalMetrics) {
|
||||
RideMetricPtr m = metrics.value(symbol);
|
||||
if (!m) continue;
|
||||
summary += "<td align=\"center\" valign=\"bottom\">" + m->name();
|
||||
if (m->units(useMetricUnits) == "seconds")
|
||||
; // don't do anything
|
||||
else if (m->units(useMetricUnits).size() > 0)
|
||||
summary += " (" + m->units(useMetricUnits) + ")";
|
||||
summary += "</td>";
|
||||
}
|
||||
summary += "</tr>";
|
||||
firstRow = false;
|
||||
}
|
||||
if (even)
|
||||
summary += "<tr>";
|
||||
else {
|
||||
QColor color = QApplication::palette().alternateBase().color();
|
||||
color = QColor::fromHsv(color.hue(), color.saturation() * 2, color.value());
|
||||
summary += "<tr bgcolor='" + color.name() + "'>";
|
||||
}
|
||||
even = !even;
|
||||
summary += "<td align=\"center\">" + interval.name + "</td>";
|
||||
foreach (QString symbol, intervalMetrics) {
|
||||
RideMetricPtr m = metrics.value(symbol);
|
||||
if (!m) continue;
|
||||
summary += "<td align=\"center\" valign=\"bottom\">" + m->name();
|
||||
if (m->units(metricUnits) == "seconds")
|
||||
; // don't do anything
|
||||
else if (m->units(metricUnits).size() > 0)
|
||||
summary += " (" + m->units(metricUnits) + ")";
|
||||
summary += "</td>";
|
||||
QString s("<td align=\"center\">%1</td>");
|
||||
if (m->units(useMetricUnits) == "seconds")
|
||||
summary += s.arg(time_to_string(m->value(useMetricUnits)));
|
||||
else
|
||||
summary += s.arg(m->value(useMetricUnits), 0, 'f', m->precision());
|
||||
}
|
||||
summary += "</tr>";
|
||||
firstRow = false;
|
||||
}
|
||||
if (even)
|
||||
summary += "<tr>";
|
||||
else {
|
||||
QColor color = QApplication::palette().alternateBase().color();
|
||||
color = QColor::fromHsv(color.hue(), color.saturation() * 2, color.value());
|
||||
summary += "<tr bgcolor='" + color.name() + "'>";
|
||||
}
|
||||
even = !even;
|
||||
summary += "<td align=\"center\">" + interval.name + "</td>";
|
||||
foreach (QString symbol, intervalMetrics) {
|
||||
RideMetricPtr m = metrics.value(symbol);
|
||||
if (!m) continue;
|
||||
QString s("<td align=\"center\">%1</td>");
|
||||
if (m->units(metricUnits) == "seconds")
|
||||
summary += s.arg(time_to_string(m->value(metricUnits)));
|
||||
else
|
||||
summary += s.arg(m->value(metricUnits), 0, 'f', m->precision());
|
||||
}
|
||||
summary += "</tr>";
|
||||
summary += "</table>";
|
||||
}
|
||||
summary += "</table>";
|
||||
}
|
||||
|
||||
if (!rideItem->errors().empty()) {
|
||||
// sumarise errors reading file if it was a ride summary
|
||||
if (ridesummary && !rideItem->errors().empty()) {
|
||||
|
||||
summary += tr("<p><h2>Errors reading file:</h2><ul>");
|
||||
QStringListIterator i(rideItem->errors());
|
||||
while(i.hasNext())
|
||||
@@ -389,3 +429,14 @@ RideSummaryWindow::htmlSummary() const
|
||||
return summary;
|
||||
}
|
||||
|
||||
void RideSummaryWindow::dateRangeChanged(DateRange dr)
|
||||
{
|
||||
if (!amVisible()) return;
|
||||
|
||||
// range didnt change ignore it...
|
||||
if (dr.from == current.from && dr.to == current.to) return;
|
||||
else current = dr;
|
||||
|
||||
data = mainWindow->metricDB->getAllMetricsFor(myDateRange);
|
||||
refresh();
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include <QWebView>
|
||||
#include <QWebFrame>
|
||||
|
||||
#include "SummaryMetrics.h"
|
||||
|
||||
|
||||
class RideSummaryWindow : public GcWindow
|
||||
{
|
||||
@@ -34,12 +36,14 @@ class RideSummaryWindow : public GcWindow
|
||||
|
||||
public:
|
||||
|
||||
RideSummaryWindow(MainWindow *parent);
|
||||
// two modes - summarise ride or summarise date range
|
||||
RideSummaryWindow(MainWindow *parent, bool ridesummary = true);
|
||||
|
||||
protected slots:
|
||||
|
||||
void refresh();
|
||||
void rideSelected();
|
||||
void dateRangeChanged(DateRange);
|
||||
void rideItemChanged();
|
||||
void metadataChanged();
|
||||
|
||||
@@ -51,6 +55,10 @@ class RideSummaryWindow : public GcWindow
|
||||
QWebView *rideSummary;
|
||||
|
||||
RideItem *_connected;
|
||||
bool ridesummary; // do we summarise ride or daterange?
|
||||
|
||||
QList<SummaryMetrics> data; // when in date range mode
|
||||
DateRange current;
|
||||
};
|
||||
|
||||
#endif // _GC_RideSummaryWindow_h
|
||||
|
||||
@@ -99,3 +99,68 @@ SummaryMetrics::getUnitsForSymbol(QString symbol, bool UseMetric) const
|
||||
if (m) return m->units(UseMetric);
|
||||
else return QString("units");
|
||||
}
|
||||
|
||||
QString SummaryMetrics::getAggregated(QString name, const QList<SummaryMetrics> &results, bool useMetricUnits, bool nofmt)
|
||||
{
|
||||
// get the metric details, so we can convert etc
|
||||
const RideMetric *metric = RideMetricFactory::instance().rideMetric(name);
|
||||
if (!metric) return QString("%1 unknown").arg(name);
|
||||
|
||||
// what we will return
|
||||
double rvalue = 0;
|
||||
double rcount = 0; // using double to avoid rounding issues with int when dividing
|
||||
|
||||
// loop through and aggregate
|
||||
foreach (SummaryMetrics rideMetrics, results) {
|
||||
|
||||
// get this value
|
||||
double value = rideMetrics.getForSymbol(name);
|
||||
double count = rideMetrics.getForSymbol("workout_time"); // for averaging
|
||||
|
||||
|
||||
// check values are bounded, just in case
|
||||
if (isnan(value) || isinf(value)) value = 0;
|
||||
|
||||
// imperial / metric conversion
|
||||
if (useMetricUnits == false) {
|
||||
value *= metric->conversion();
|
||||
value += metric->conversionSum();
|
||||
}
|
||||
|
||||
switch (metric->type()) {
|
||||
case RideMetric::Total:
|
||||
rvalue += value;
|
||||
break;
|
||||
case RideMetric::Average:
|
||||
{
|
||||
// average should be calculated taking into account
|
||||
// the duration of the ride, otherwise high value but
|
||||
// short rides will skew the overall average
|
||||
rvalue += value*count;
|
||||
rcount += count;
|
||||
break;
|
||||
}
|
||||
case RideMetric::Peak:
|
||||
{
|
||||
if (value > rvalue) rvalue = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now compute the average
|
||||
if (metric->type() == RideMetric::Average) {
|
||||
if (rcount) rvalue = rvalue / rcount;
|
||||
}
|
||||
|
||||
|
||||
// Format appropriately
|
||||
QString result;
|
||||
if (metric->units(useMetricUnits) == "seconds") {
|
||||
if (nofmt) result = QString("%1").arg(rvalue);
|
||||
else result = time_to_string(rvalue);
|
||||
|
||||
} else result = QString("%1").arg(rvalue, 0, 'f', metric->precision());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,9 @@ class SummaryMetrics
|
||||
// get unit string to use for this symbol
|
||||
QString getUnitsForSymbol(QString symbol, bool UseMetric) const;
|
||||
|
||||
// when passed a list of summary metrics and a name return aggregated value as a string
|
||||
static QString getAggregated(QString name, const QList<SummaryMetrics> &results, bool useMetricUnits, bool nofmt = false);
|
||||
|
||||
QMap<QString, double> &values() { return value; }
|
||||
QMap<QString, QString> &texts() { return text; }
|
||||
|
||||
|
||||
@@ -97,22 +97,25 @@ QDateTime convertToLocalTime(QString timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
DateRange::DateRange(QDate from, QDate to) : QObject()
|
||||
DateRange::DateRange(QDate from, QDate to, QString name) : QObject()
|
||||
{
|
||||
this->from=from;
|
||||
this->to=to;
|
||||
this->name=name;
|
||||
}
|
||||
|
||||
DateRange::DateRange(const DateRange &other) : QObject()
|
||||
{
|
||||
from=other.from;
|
||||
to=other.to;
|
||||
name=other.name;
|
||||
}
|
||||
|
||||
DateRange& DateRange::operator=(const DateRange &other)
|
||||
{
|
||||
from=other.from;
|
||||
to=other.to;
|
||||
name=other.name;
|
||||
emit changed(from, to);
|
||||
|
||||
return *this;
|
||||
|
||||
@@ -38,9 +38,10 @@ class DateRange : QObject
|
||||
|
||||
public:
|
||||
DateRange(const DateRange& other);
|
||||
DateRange(QDate from = QDate(), QDate to = QDate());
|
||||
DateRange(QDate from = QDate(), QDate to = QDate(), QString name ="");
|
||||
DateRange& operator=(const DateRange &);
|
||||
QDate from, to;
|
||||
QString name;
|
||||
|
||||
signals:
|
||||
void changed(QDate from, QDate to);
|
||||
|
||||
Reference in New Issue
Block a user