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).
This commit is contained in:
Mark Liversedge
2013-12-29 13:00:46 +00:00
parent 99712de70f
commit bd37801225
10 changed files with 276 additions and 16 deletions

24
src/CompareDateRange.cpp Normal file
View File

@@ -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 <QColor>

47
src/CompareDateRange.h Normal file
View File

@@ -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 <QColor>
#include <QDate>
#include <QString>
class CompareDateRange
{
public:
CompareDateRange() : context(NULL), days(0), sourceContext(NULL), checked(false) {}
Context *context;
QString name;
QColor color;
QList<SummaryMetrics> metrics, measures;
QDate start, end;
int days;
Context *sourceContext;
bool checked;
bool isChecked() const { return checked; }
void setChecked(bool x) { checked=x; }
};
#endif

View File

@@ -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<QString, RideMetricPtr> computed = RideMetric::computeMetrics(context, x.data, context->athlete->zones(), context->athlete->hrZones(), worklist);
QHash<QString, RideMetricPtr> 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; i<list.count(); i++) {
if (i < 2) {
table->horizontalHeader()->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<CompareDateRange> newOnes;
// lets get the basic data
stream >> count;
for (int i=0; i<count; i++) {
CompareDateRange add;
add.checked = true; // UPDATE COMPARE INTERVAL
add.context = context; // UPDATE COMPARE INTERVAL
add.sourceContext = sourceContext; // UPDATE COMPARE INTERVAL
stream >> 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();
}
}
}

View File

@@ -25,6 +25,7 @@ Context::Context(MainWindow *mainWindow)
ride = NULL;
workout = NULL;
isfiltered = ishomefiltered = false;
isCompareIntervals = isCompareDateRanges = true;
}
const RideFile *

View File

@@ -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<CompareInterval> compareIntervals;
bool isCompareDateRanges;
QList<CompareDateRange> 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

View File

@@ -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;

View File

@@ -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

View File

@@ -105,7 +105,7 @@ Tab::~Tab()
RideNavigator *Tab::rideNavigator()
{
analysisView->rideNavigator();
return analysisView->rideNavigator();
}
void

View File

@@ -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<ViewSplitter*>("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();

View File

@@ -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 \