From bd37801225fb0e1fed7f8ba84bdc8e2ba7afb9be Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Sun, 29 Dec 2013 13:00:46 +0000 Subject: [PATCH] DateRange Compare Pane .. now can drag and drop any date range onto the home view compare pane to compatre different seasons, and even across athletes. .. we setup the metrics and measures in the context so the charts don't have to, but we still need to reference the source context for bests and ridefilecache data NOTE: We need a mechanism for 'locking' source tabs/athletes when they are part of a compare to avoid crashing when a context is deleted whilst we are comparing NOTE: We till need a way to remove entries from the compare pane, possibly call it when an athlete is closed too (see note above). --- src/CompareDateRange.cpp | 24 +++++ src/CompareDateRange.h | 47 ++++++++++ src/ComparePane.cpp | 186 +++++++++++++++++++++++++++++++++++++-- src/Context.cpp | 1 + src/Context.h | 9 ++ src/HomeWindow.cpp | 4 +- src/Season.cpp | 12 ++- src/Tab.cpp | 2 +- src/TabView.h | 5 +- src/src.pro | 2 + 10 files changed, 276 insertions(+), 16 deletions(-) create mode 100644 src/CompareDateRange.cpp create mode 100644 src/CompareDateRange.h diff --git a/src/CompareDateRange.cpp b/src/CompareDateRange.cpp new file mode 100644 index 000000000..e82b910ca --- /dev/null +++ b/src/CompareDateRange.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2014 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 "CompareDateRange.h" + +#include "Context.h" +#include "Season.h" +#include "SummaryMetrics.h" +#include diff --git a/src/CompareDateRange.h b/src/CompareDateRange.h new file mode 100644 index 000000000..706a410cc --- /dev/null +++ b/src/CompareDateRange.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2014 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 + */ + +#ifndef _GC_CompareDateRange_h +#define _GC_CompareDateRange_h + +class Context; +#include "SummaryMetrics.h" + +#include +#include +#include + +class CompareDateRange +{ + public: + CompareDateRange() : context(NULL), days(0), sourceContext(NULL), checked(false) {} + + Context *context; + QString name; + QColor color; + QList metrics, measures; + QDate start, end; + int days; + Context *sourceContext; + bool checked; + + bool isChecked() const { return checked; } + void setChecked(bool x) { checked=x; } +}; + +#endif diff --git a/src/ComparePane.cpp b/src/ComparePane.cpp index 7d52ed7ef..b415b350d 100644 --- a/src/ComparePane.cpp +++ b/src/ComparePane.cpp @@ -21,6 +21,7 @@ #include "RideFile.h" #include "RideMetric.h" #include "SummaryMetrics.h" +#include "MetricAggregator.h" #include "ColorButton.h" #include "TimeUtils.h" #include "Units.h" @@ -46,10 +47,12 @@ static bool initStandardColors() standardColors << QColor(Qt::darkGreen); standardColors << QColor(Qt::darkBlue); standardColors << QColor(Qt::darkMagenta); + + return true; } static bool init = initStandardColors(); -ComparePane::ComparePane(Context *context, QWidget *parent, CompareMode mode) : context(context), mode_(mode), QWidget(parent) +ComparePane::ComparePane(Context *context, QWidget *parent, CompareMode mode) : QWidget(parent), context(context), mode_(mode) { QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0,0,0,0); @@ -152,7 +155,9 @@ ComparePane::refreshTable() // compute the metrics for this ride SummaryMetrics metrics; - QHash computed = RideMetric::computeMetrics(context, x.data, context->athlete->zones(), context->athlete->hrZones(), worklist); + QHash computed = RideMetric::computeMetrics(context, x.data, + context->athlete->zones(), context->athlete->hrZones(), worklist); + for(int i = 0; i < worklist.count(); ++i) { if (worklist[i] != "") { RideMetricPtr m = computed.value(worklist[i]); @@ -248,7 +253,132 @@ ComparePane::refreshTable() } else { //SEASONS - //XXX seasons not written yet + // STEP ONE : SET THE TABLE HEADINGS + + // clear current contents + table->clear(); + table->setRowCount(0); + + // metric summary + QStringList always; + always << "workout_time" << "total_distance"; + QString s = appsettings->value(this, GC_SETTINGS_SUMMARY_METRICS, GC_SETTINGS_SUMMARY_METRICS_DEFAULT).toString(); + if (s == "") s = GC_SETTINGS_SUMMARY_METRICS_DEFAULT; + QStringList metricColumns = always + s.split(","); // always showm metrics plus user defined summary metrics + + // called after config is updated typically + QStringList list; + list << "" // checkbox + << "" // color + << "Athlete" + << "From" + << "To"; + + QStringList worklist; // metrics to compute + RideMetricFactory &factory = RideMetricFactory::instance(); + + foreach(QString metric, metricColumns) { + + // get the metric name + const RideMetric *m = factory.rideMetric(metric); + if (m) { + worklist << metric; + QString units; + if (m->units(context->athlete->useMetricUnits) != "seconds") units = m->units(context->athlete->useMetricUnits); + if (units != "") list << QString("%1 (%2)").arg(m->name()).arg(units); + else list << QString("%1").arg(m->name()); + } + } + + list << "Date Range"; + + table->setColumnCount(list.count()); + table->setHorizontalHeaderLabels(list); + table->setSortingEnabled(true); + table->verticalHeader()->hide(); + table->setShowGrid(false); + table->setSelectionMode(QAbstractItemView::MultiSelection); + table->setSelectionBehavior(QAbstractItemView::SelectRows); + + // STEP TWO : CLEAR AND RE-ADD TO REFLECT CHANGES + + table->setRowCount(context->compareDateRanges.count()); + int counter = 0; + foreach(CompareDateRange x, context->compareDateRanges) { + + // First few cols always the same + // check - color - athlete - date - time + // now create a row on the compare pane + + // Checkbox + QCheckBox *check = new QCheckBox(this); + check->setChecked(x.checked); + table->setCellWidget(counter, 0, check); + connect(check, SIGNAL(stateChanged(int)), this, SLOT(intervalButtonsChanged())); + + // Color Button + ColorButton *colorButton = new ColorButton(this, "Color", x.color); + table->setCellWidget(counter, 1, colorButton); + connect(colorButton, SIGNAL(colorChosen(QColor)), this, SLOT(intervalButtonsChanged())); + + // athlete + QTableWidgetItem *t = new QTableWidgetItem; + t->setText(x.sourceContext->athlete->cyclist); + t->setFlags(t->flags() & (~Qt::ItemIsEditable)); + table->setItem(counter, 2, t); + + // date from + t = new QTableWidgetItem; + t->setText(x.start.toString("dd MMM, yyyy")); + t->setFlags(t->flags() & (~Qt::ItemIsEditable)); + table->setItem(counter, 3, t); + + // date to + t = new QTableWidgetItem; + t->setText(x.end.toString("dd MMM, yyyy")); + t->setFlags(t->flags() & (~Qt::ItemIsEditable)); + table->setItem(counter, 4, t); + + // metrics + for(int i = 0; i < worklist.count(); ++i) { + + QString value = SummaryMetrics::getAggregated(x.sourceContext, worklist[i], + x.metrics, QStringList(), false, context->athlete->useMetricUnits); + + // add to the table + t = new QTableWidgetItem; + t->setText(value); + t->setFlags(t->flags() & (~Qt::ItemIsEditable)); + table->setItem(counter, i + 5, t); + } + + // Date Range name + t = new QTableWidgetItem; + t->setText(x.name); + t->setFlags(t->flags() & (~Qt::ItemIsEditable)); + table->setItem(counter, worklist.count() + 5, t); + + // align center + for (int i=3; i<(worklist.count()+5); i++) + table->item(counter,i)->setTextAlignment(Qt::AlignVCenter | Qt::AlignHCenter); + + table->setRowHeight(counter, 23); + counter++; + } + + table->resizeColumnsToContents(); // set columns to fit +#if QT_VERSION > 0x050000 // fix the first two if we can + for (int i=0; ihorizontalHeader()->setSectionResizeMode(i, QHeaderView::Fixed); + } else { + table->horizontalHeader()->setSectionResizeMode(i, QHeaderView::Interactive); + } + } +#else + table->horizontalHeader()->setResizeMode(QHeaderView::Interactive); +#endif + table->horizontalHeader()->setStretchLastSection(true); } } @@ -283,7 +413,7 @@ ComparePane::dragEnterEvent(QDragEnterEvent *event) } void -ComparePane::dragLeaveEvent(QDragLeaveEvent *event) +ComparePane::dragLeaveEvent(QDragLeaveEvent *) { // we might consider hiding on this? } @@ -348,7 +478,7 @@ ComparePane::dropEvent(QDropEvent *event) // construct a ridefile for the interval - //XXX RideFile *data; + // RideFile *data; add.data = new RideFile(ride->startTime(), ride->recIntSecs()); add.data->context = context; @@ -404,7 +534,51 @@ ComparePane::dropEvent(QDropEvent *event) } else { // SEASONS - //XXX not written yet! + int count; + + QList newOnes; + + // lets get the basic data + stream >> count; + for (int i=0; i> add.name; + stream >> add.start; + stream >> add.end; + stream >> add.days; + + // get summary metrics for the season + // FROM THE SOURCE CONTEXT + // WE DON'T FETCH BESTS -- THEY NEED TO BE DONE AS NEEDED + add.metrics = sourceContext->athlete->metricDB->getAllMetricsFor(QDateTime(add.start, QTime()),QDateTime(add.end, QTime())); + add.measures = sourceContext->athlete->metricDB->getAllMeasuresFor(QDateTime(add.start, QTime()),QDateTime(add.end, QTime())); + + // just use standard colors and cycle round + // we will of course repeat, but the user can + // just edit them using the button + add.color = standardColors.at((i + context->compareDateRanges.count()) % standardColors.count()); + + // even empty date ranges are valid + newOnes << add; + + } + // how many we get ? + if (newOnes.count()) { + + context->compareDateRanges.append(newOnes); + + // refresh the table to reflect the new list + refreshTable(); + + // let all the charts know + context->notifyCompareDateRangesChanged(); + } } } diff --git a/src/Context.cpp b/src/Context.cpp index a4554023f..a240eab84 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -25,6 +25,7 @@ Context::Context(MainWindow *mainWindow) ride = NULL; workout = NULL; isfiltered = ishomefiltered = false; + isCompareIntervals = isCompareDateRanges = true; } const RideFile * diff --git a/src/Context.h b/src/Context.h index ca6c9ca4e..9f9f8bb17 100644 --- a/src/Context.h +++ b/src/Context.h @@ -23,6 +23,7 @@ #include "RealtimeData.h" // for class RealtimeData #include "SpecialFields.h" // for class RealtimeData #include "CompareInterval.h" // what intervals are being compared? +#include "CompareDateRange.h" // what intervals are being compared? class RideFile; class RideItem; @@ -74,6 +75,9 @@ class Context : public QObject bool isCompareIntervals; QList compareIntervals; + bool isCompareDateRanges; + QList compareDateRanges; + // ********************************************* // APPLICATION EVENTS // ********************************************* @@ -117,6 +121,9 @@ class Context : public QObject void notifyCompareIntervals(bool state) { isCompareIntervals = state; emit compareIntervalsStateChanged(state); } void notifyCompareIntervalsChanged() { emit compareIntervalsChanged(); } + void notifyCompareDateRanges(bool state) { isCompareDateRanges = state; emit compareDateRangesStateChanged(state); } + void notifyCompareDateRangesChanged() { emit compareDateRangesChanged(); } + signals: // global filter changed @@ -152,5 +159,7 @@ class Context : public QObject // comparing things void compareIntervalsStateChanged(bool); void compareIntervalsChanged(); + void compareDateRangesStateChanged(bool); + void compareDateRangesChanged(); }; #endif // _GC_Context_h diff --git a/src/HomeWindow.cpp b/src/HomeWindow.cpp index 6d20aed49..3363f2309 100644 --- a/src/HomeWindow.cpp +++ b/src/HomeWindow.cpp @@ -413,7 +413,7 @@ HomeWindow::styleChanged(int id) } void -HomeWindow::dragEnterEvent(QDragEnterEvent *event) +HomeWindow::dragEnterEvent(QDragEnterEvent *) { #if 0 // drah and drop chart no longer part of the UX if (event->mimeData()->formats().contains("application/x-qabstractitemmodeldatalist")) { @@ -447,7 +447,7 @@ HomeWindow::appendChart(GcWinID id) } void -HomeWindow::dropEvent(QDropEvent *event) +HomeWindow::dropEvent(QDropEvent *) { #if 0 // drah and drop chart no longer part of the UX QStandardItemModel model; diff --git a/src/Season.cpp b/src/Season.cpp index efe024ac6..26352d395 100644 --- a/src/Season.cpp +++ b/src/Season.cpp @@ -408,11 +408,15 @@ SeasonTreeView::dropEvent(QDropEvent* event) int idx1 = invisibleRootItem()->indexOfChild(item); int idx2 = indexAt(event->pos()).row(); - // finalise drop event - QTreeWidget::dropEvent(event); + // don't move temp 'system generated' date ranges! + if (context->athlete->seasons->seasons[idx1].type != Season::temporary) { - // emit the itemMoved signal - Q_EMIT itemMoved(item, idx1, idx2); + // finalise drop event + QTreeWidget::dropEvent(event); + + // emit the itemMoved signal + Q_EMIT itemMoved(item, idx1, idx2); + } } QStringList diff --git a/src/Tab.cpp b/src/Tab.cpp index aff91821c..3d3ef5a93 100644 --- a/src/Tab.cpp +++ b/src/Tab.cpp @@ -105,7 +105,7 @@ Tab::~Tab() RideNavigator *Tab::rideNavigator() { - analysisView->rideNavigator(); + return analysisView->rideNavigator(); } void diff --git a/src/TabView.h b/src/TabView.h index 3faac46cc..6511dfce7 100644 --- a/src/TabView.h +++ b/src/TabView.h @@ -137,8 +137,7 @@ class ViewSplitter : public QSplitter public: ViewSplitter(Qt::Orientation orientation, QString name, TabView *parent=0) : - orientation(orientation), name(name), tabView(parent), showForDrag(false), - QSplitter(orientation, parent) { + QSplitter(orientation, parent), orientation(orientation), name(name), tabView(parent), showForDrag(false) { setAcceptDrops(true); qRegisterMetaType("hpos"); } @@ -163,7 +162,7 @@ protected: } } - virtual void dragLeaveEvent(QDragLeaveEvent *event) { + virtual void dragLeaveEvent(QDragLeaveEvent *) { int X = this->mapFromGlobal(QCursor::pos()).x(); int Y = this->mapFromGlobal(QCursor::pos()).y(); diff --git a/src/src.pro b/src/src.pro index 7b33efbd6..7f6811d84 100644 --- a/src/src.pro +++ b/src/src.pro @@ -277,6 +277,7 @@ HEADERS += \ Colors.h \ ColorButton.h \ CommPort.h \ + CompareDateRange.h \ CompareInterval.h \ ComparePane.h \ Computrainer.h \ @@ -468,6 +469,7 @@ SOURCES += \ Colors.cpp \ ColorButton.cpp \ CommPort.cpp \ + CompareDateRange.cpp \ CompareInterval.cpp \ ComparePane.cpp \ Computrainer.cpp \