diff --git a/src/Charts/CPPlot.cpp b/src/Charts/CPPlot.cpp index 42d7b0118..c14befde5 100644 --- a/src/Charts/CPPlot.cpp +++ b/src/Charts/CPPlot.cpp @@ -53,6 +53,7 @@ #include "LTMCanvasPicker.h" #include "TimeUtils.h" #include "Units.h" +#include "Perspective.h" #include "LTMTrend.h" @@ -1074,6 +1075,7 @@ CPPlot::plotTests(RideItem *rideitem) fs.addFilter(parent->searchBox->isFiltered(), SearchFilterBox::matches(context, parent->searchBox->filter())); // chart settings fs.addFilter(context->isfiltered, context->filters); fs.addFilter(context->ishomefiltered, context->homeFilters); + fs.addFilter(parent->myPerspective->isFiltered(), parent->myPerspective->filterlist(DateRange(startDate,endDate))); Specification spec; spec.setFilterSet(fs); spec.setDateRange(DateRange(startDate, endDate)); @@ -1198,7 +1200,18 @@ CPPlot::plotBests(RideItem *rideItem) // do we need to get the cache ? if (bestsCache == NULL) { - bestsCache = new RideFileCache(context, startDate, endDate, isFiltered, files, rangemode, rideItem); + // isFiltered and files are filters from CriticalPowerWindow's filter setting + // we also need to take into account the perspective filter if on trends so + // its easier to just aggregate the filter list here (bit hacky but works ok) + // but only if rangemode (aka on trends) + if (rangemode) { + bestsCache = new RideFileCache(context, startDate, endDate, + isFiltered||parent->myPerspective->isFiltered(), + files + (parent->myPerspective->isFiltered() ? parent->myPerspective->filterlist(DateRange(startDate,endDate)) : QStringList()), + rangemode, rideItem); + } else { + bestsCache = new RideFileCache(context, startDate, endDate, isFiltered, files, rangemode, rideItem); + } } // how much we got ? @@ -1758,6 +1771,7 @@ CPPlot::plotEfforts() FilterSet fs; // apply filters when selecting intervals fs.addFilter(context->isfiltered, context->filters); fs.addFilter(context->ishomefiltered, context->homeFilters); + fs.addFilter(parent->myPerspective->isFiltered(), parent->myPerspective->filterlist(DateRange(startDate,endDate))); Specification spec; spec.setFilterSet(fs); spec.setDateRange(DateRange(startDate, endDate)); @@ -2265,6 +2279,17 @@ CPPlot::exportBests(QString filename) f.close(); } +// perspective filter changed, we need to replot with new bests +void +CPPlot::perspectiveFilterChanged() +{ + if (bestsCache) { + delete bestsCache; + bestsCache = NULL; + } + clearCurves(); +} + // no filter void CPPlot::clearFilter() diff --git a/src/Charts/CPPlot.h b/src/Charts/CPPlot.h index 4476a405a..888b5e079 100644 --- a/src/Charts/CPPlot.h +++ b/src/Charts/CPPlot.h @@ -112,6 +112,7 @@ class CPPlot : public QwtPlot void pointHover(QwtPlotCurve *curve, int index); // filter being applied + void perspectiveFilterChanged(); void clearFilter(); void setFilter(QStringList); diff --git a/src/Charts/CriticalPowerWindow.cpp b/src/Charts/CriticalPowerWindow.cpp index c33ad17c5..d92c4f5a2 100644 --- a/src/Charts/CriticalPowerWindow.cpp +++ b/src/Charts/CriticalPowerWindow.cpp @@ -518,6 +518,10 @@ CriticalPowerWindow::CriticalPowerWindow(Context *context, bool rangemode) : // Compare connect(context, SIGNAL(compareDateRangesStateChanged(bool)), SLOT(forceReplot())); connect(context, SIGNAL(compareDateRangesChanged()), SLOT(forceReplot())); + + connect(this, SIGNAL(perspectiveFilterChanged(QString)), this, SLOT(perspectiveFilterChanged())); + connect(this, SIGNAL(perspectiveChanged(Perspective*)), this, SLOT(perspectiveFilterChanged())); + } else { // when working on a ride we can select intervals! connect(cComboSeason, SIGNAL(currentIndexChanged(int)), this, SLOT(seasonSelected(int))); @@ -528,6 +532,7 @@ CriticalPowerWindow::CriticalPowerWindow(Context *context, bool rangemode) : // Compare connect(context, SIGNAL(compareIntervalsStateChanged(bool)), SLOT(forceReplot())); connect(context, SIGNAL(compareIntervalsChanged()), SLOT(forceReplot())); + } connect(seriesCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setSeries(int))); @@ -1752,6 +1757,7 @@ CriticalPowerWindow::dateRangeChanged(DateRange dateRange) fs.addFilter(searchBox->isFiltered(), SearchFilterBox::matches(context, filter())); fs.addFilter(context->isfiltered, context->filters); fs.addFilter(context->ishomefiltered, context->homeFilters); + fs.addFilter(myPerspective->isFiltered(), myPerspective->filterlist(dateRange)); int nActivities, nRides, nRuns, nSwims; QString sport; context->athlete->rideCache->getRideTypeCounts( @@ -1809,6 +1815,14 @@ void CriticalPowerWindow::seasonSelected(int iSeason) cpPlot->setRide(currentRide); } +void CriticalPowerWindow::perspectiveFilterChanged() +{ + if (rangemode) { + cpPlot->perspectiveFilterChanged(); + forceReplot(); + } +} + void CriticalPowerWindow::filterChanged() { cpPlot->setRide(currentRide); diff --git a/src/Charts/CriticalPowerWindow.h b/src/Charts/CriticalPowerWindow.h index d9e6b88ab..23e47332d 100644 --- a/src/Charts/CriticalPowerWindow.h +++ b/src/Charts/CriticalPowerWindow.h @@ -26,6 +26,7 @@ #include "Season.h" #include "RideFile.h" #include "SearchFilterBox.h" +#include "Perspective.h" #include "qxtstringspinbox.h" #include @@ -124,7 +125,7 @@ class CriticalPowerWindow : public GcChartWindow void setVariant(int x); // filter - bool isFiltered() const { return (searchBox->isFiltered() || context->ishomefiltered || context->isfiltered); } + bool isFiltered() const { return (searchBox->isFiltered() || myPerspective->isFiltered() || context->ishomefiltered || context->isfiltered); } QString filter() const { return searchBox->filter(); } void setFilter(QString x) { searchBox->setFilter(x); } @@ -262,6 +263,7 @@ class CriticalPowerWindow : public GcChartWindow void resetSeasons(); void filterChanged(); void dateRangeChanged(DateRange); + void perspectiveFilterChanged(); void useCustomRange(DateRange); void useStandardRange(); diff --git a/src/Charts/GoldenCheetah.h b/src/Charts/GoldenCheetah.h index 86eea8361..6c67a5d37 100644 --- a/src/Charts/GoldenCheetah.h +++ b/src/Charts/GoldenCheetah.h @@ -20,12 +20,14 @@ #define _GC_GoldenCheetah_h class GcWindow; class Context; +class Perspective; #define G_OBJECT //#define G_OBJECT Q_PROPERTY(QString instanceName READ instanceName WRITE setInstanceName) //#define setInstanceName(x) setProperty("instanceName", x) #define myRideItem property("ride").value() #define myDateRange property("dateRange").value() +#define myPerspective property("perspective").value() #include #include @@ -45,6 +47,7 @@ class Context; class RideItem; class GcOverlayWidget; +class Perspective; class GcWindow : public QFrame @@ -122,6 +125,8 @@ signals: void widthFactorChanged(double); void colorChanged(QColor); void dateRangeChanged(DateRange); + void perspectiveFilterChanged(QString); + void perspectiveChanged(Perspective*); void resizing(GcWindow*); void moving(GcWindow*); void resized(GcWindow*); // finished resizing @@ -159,6 +164,9 @@ public: void setDateRange(DateRange); DateRange dateRange() const; + void notifyPerspectiveFilterChanged(QString x) { emit perspectiveFilterChanged(x); } + void notifyPerspectiveChanged(Perspective *x) { emit perspectiveChanged(x); } + void setWidthFactor(double); double widthFactor() const; diff --git a/src/Charts/HistogramWindow.cpp b/src/Charts/HistogramWindow.cpp index 43af3fcf2..1513f361a 100644 --- a/src/Charts/HistogramWindow.cpp +++ b/src/Charts/HistogramWindow.cpp @@ -21,6 +21,7 @@ #include "Specification.h" #include "HelpWhatsThis.h" #include "Utils.h" +#include "Perspective.h" // predefined deltas for each series static const double wattsDelta = 1.0; @@ -314,6 +315,10 @@ HistogramWindow::HistogramWindow(Context *context, bool rangemode) : GcChartWind connect(distMetricTree, SIGNAL(itemSelectionChanged()), this, SLOT(treeSelectionChanged())); connect(totalMetricTree, SIGNAL(itemSelectionChanged()), this, SLOT(treeSelectionChanged())); + // perspective filter changed + connect(this, SIGNAL(perspectiveFilterChanged(QString)), this, SLOT(perspectiveFilterChanged())); + connect(this, SIGNAL(perspectiveChanged(Perspective*)), this, SLOT(perspectiveFilterChanged())); + // replot when background refresh is progressing connect(context, SIGNAL(refreshUpdate(QDate)), this, SLOT(refreshUpdate(QDate))); @@ -1008,7 +1013,11 @@ HistogramWindow::updateChart() // plotting a data series, so refresh the ridefilecache - source = new RideFileCache(context, use.from, use.to, isfiltered, files, rangemode); + if (rangemode) { + source = new RideFileCache(context, use.from, use.to, isfiltered||myPerspective->isFiltered(), + files + myPerspective->filterlist(use), rangemode); + } else source = new RideFileCache(context, use.from, use.to, isfiltered, files, rangemode); + cfrom = use.from; cto = use.to; stale = false; @@ -1043,6 +1052,7 @@ HistogramWindow::updateChart() fs.addFilter(isfiltered, files); fs.addFilter(context->isfiltered, context->filters); fs.addFilter(context->ishomefiltered, context->homeFilters); + fs.addFilter(myPerspective->isFiltered(), myPerspective->filterlist(use)); // setData using the summary metrics -- always reset since filters may // have changed, or perhaps the bin width... @@ -1085,6 +1095,14 @@ HistogramWindow::updateChart() } // if stale } +void +HistogramWindow::perspectiveFilterChanged() +{ + stale = true; + updateChart(); + repaint(); +} + void HistogramWindow::clearFilter() { diff --git a/src/Charts/HistogramWindow.h b/src/Charts/HistogramWindow.h index 53a51c5ab..426da748f 100644 --- a/src/Charts/HistogramWindow.h +++ b/src/Charts/HistogramWindow.h @@ -153,6 +153,7 @@ class HistogramWindow : public GcChartWindow void zonesChanged(); void clearFilter(); void setFilter(QStringList files); + void perspectiveFilterChanged(); // date settings void useCustomRange(DateRange); diff --git a/src/Charts/LTMWindow.cpp b/src/Charts/LTMWindow.cpp index 8eccdf805..93bced624 100644 --- a/src/Charts/LTMWindow.cpp +++ b/src/Charts/LTMWindow.cpp @@ -302,6 +302,8 @@ LTMWindow::LTMWindow(Context *context) : connect(this, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateRangeChanged(DateRange))); connect(this, SIGNAL(styleChanged(int)), this, SLOT(styleChanged(int))); connect(ltmTool, SIGNAL(filterChanged()), this, SLOT(filterChanged())); + connect(this, SIGNAL(perspectiveFilterChanged(QString)), this, SLOT(filterChanged())); + connect(this, SIGNAL(perspectiveChanged(Perspective*)), this, SLOT(filterChanged())); connect(context, SIGNAL(homeFilterChanged()), this, SLOT(filterChanged())); connect(ltmTool->groupBy, SIGNAL(currentIndexChanged(int)), this, SLOT(groupBySelected(int))); connect(rGroupBy, SIGNAL(valueChanged(int)), this, SLOT(rGroupBySelected(int))); @@ -635,6 +637,7 @@ LTMWindow::presetSelected(int index) fs.addFilter(context->isfiltered, context->filters); fs.addFilter(context->ishomefiltered, context->homeFilters); fs.addFilter(ltmTool->isFiltered(), ltmTool->filters()); + fs.addFilter(myPerspective->isFiltered(), myPerspective->filterlist(myDateRange)); settings.specification.setFilterSet(fs); settings.specification.setDateRange(DateRange(settings.start.date(), settings.end.date())); @@ -1072,6 +1075,7 @@ LTMWindow::filterChanged() fs.addFilter(context->isfiltered, context->filters); fs.addFilter(context->ishomefiltered, context->homeFilters); fs.addFilter(ltmTool->isFiltered(), ltmTool->filters()); + fs.addFilter(myPerspective->isFiltered(), myPerspective->filterlist(myDateRange)); settings.specification.setFilterSet(fs); settings.specification.setDateRange(DateRange(settings.start.date(), settings.end.date())); @@ -1198,6 +1202,7 @@ LTMWindow::applyClicked() fs.addFilter(context->isfiltered, context->filters); fs.addFilter(context->ishomefiltered, context->homeFilters); fs.addFilter(ltmTool->isFiltered(), ltmTool->filters()); + fs.addFilter(myPerspective->isFiltered(), myPerspective->filterlist(myDateRange)); settings.specification.setFilterSet(fs); settings.specification.setDateRange(DateRange(settings.start.date(), settings.end.date())); diff --git a/src/Charts/Overview.cpp b/src/Charts/Overview.cpp index 3ae7232de..a924dd764 100644 --- a/src/Charts/Overview.cpp +++ b/src/Charts/Overview.cpp @@ -40,7 +40,7 @@ OverviewWindow::OverviewWindow(Context *context, int scope) : GcChartWindow(cont main->setSpacing(0); main->setContentsMargins(0,0,0,0); - space = new ChartSpace(context, scope); + space = new ChartSpace(context, scope, this); main->addWidget(space); HelpWhatsThis *help = new HelpWhatsThis(space); @@ -59,7 +59,9 @@ OverviewWindow::OverviewWindow(Context *context, int scope) : GcChartWindow(cont connect(this, SIGNAL(dateRangeChanged(DateRange)), space, SLOT(dateRangeChanged(DateRange))); connect(context, SIGNAL(filterChanged()), space, SLOT(filterChanged())); connect(context, SIGNAL(homeFilterChanged()), space, SLOT(filterChanged())); + connect(this, SIGNAL(perspectiveFilterChanged(QString)), space, SLOT(filterChanged())); } + connect(this, SIGNAL(perspectiveChanged(Perspective*)), space, SLOT(refresh())); connect(context, SIGNAL(refreshEnd()), space, SLOT(refresh())); connect(context, SIGNAL(estimatesRefreshed()), space, SLOT(refresh())); diff --git a/src/Charts/OverviewItems.cpp b/src/Charts/OverviewItems.cpp index cacbe7f30..6351aabda 100644 --- a/src/Charts/OverviewItems.cpp +++ b/src/Charts/OverviewItems.cpp @@ -78,6 +78,11 @@ static void setFilter(ChartSpaceItem *item, Specification &spec) fs.addFilter(item->parent->context->isfiltered, item->parent->context->filters); fs.addFilter(item->parent->context->ishomefiltered, item->parent->context->homeFilters); + // property gets set after chartspace is initialised, so when we start up its not + // available, but comes later... + if (item->parent->window->property("perspective").isValid()) + fs.addFilter(item->parent->window->myPerspective->isFiltered(), item->parent->window->myPerspective->filterlist(item->parent->myDateRange)); + // local filter fs.addFilter(item->datafilter != "", SearchFilterBox::matches(item->parent->context, item->datafilter)); spec.setFilterSet(fs); @@ -466,9 +471,13 @@ KPIOverviewItem::setData(RideItem *item) void KPIOverviewItem::setDateRange(DateRange dr) { + Specification spec; + spec.setDateRange(dr); + setFilter(this, spec); + // calculate the value... DataFilter parser(this, parent->context, program); - Result res = parser.evaluate(dr, datafilter); + Result res = parser.evaluate(spec, dr); // set to zero for daft values value = res.string(); diff --git a/src/Charts/PythonChart.cpp b/src/Charts/PythonChart.cpp index 661d8db1b..aecae09e2 100644 --- a/src/Charts/PythonChart.cpp +++ b/src/Charts/PythonChart.cpp @@ -191,6 +191,7 @@ void PythonConsole::keyPressEvent(QKeyEvent *e) if (pythonHost->chart()) { python->canvas = pythonHost->chart()->canvas; python->chart = pythonHost->chart(); + python->perspective = myPerspective; } try { @@ -228,6 +229,7 @@ void PythonConsole::keyPressEvent(QKeyEvent *e) // clear context python->canvas = NULL; + python->perspective = NULL; python->chart = NULL; } diff --git a/src/Charts/RChart.cpp b/src/Charts/RChart.cpp index 6b40e7f96..fe99e5d2d 100644 --- a/src/Charts/RChart.cpp +++ b/src/Charts/RChart.cpp @@ -384,6 +384,10 @@ RChart::RChart(Context *context, bool ridesummary) : GcChartWindow(context), con // refresh when comparing connect(context, SIGNAL(compareDateRangesStateChanged(bool)), this, SLOT(runScript())); connect(context, SIGNAL(compareDateRangesChanged()), this, SLOT(runScript())); + + // perspective or perspective filter changed + connect(this, SIGNAL(perspectiveFilterChanged(QString)), this, SLOT(runScript())); + connect(this, SIGNAL(perspectiveChanged(Perspective *)), this, SLOT(runScript())); } // we apply BOTH filters, so update when either change @@ -537,6 +541,7 @@ RChart::runScript() // run it !! rtool->context = context; rtool->canvas = canvas; + rtool->perspective = myPerspective; rtool->chart = this; // set default page size @@ -601,6 +606,7 @@ RChart::runScript() // clear context rtool->context = NULL; rtool->canvas = NULL; + rtool->perspective = NULL; rtool->chart = NULL; } } diff --git a/src/Charts/TreeMapWindow.cpp b/src/Charts/TreeMapWindow.cpp index 495866c5a..8a8b6324b 100644 --- a/src/Charts/TreeMapWindow.cpp +++ b/src/Charts/TreeMapWindow.cpp @@ -28,6 +28,7 @@ #include "Units.h" // for MILES_PER_KM #include "HelpWhatsThis.h" #include "GoldenCheetah.h" +#include "Perspective.h" #include #include @@ -50,17 +51,17 @@ TreeMapWindow::TreeMapWindow(Context *context) : // the plot mainLayout = new QVBoxLayout; - ltmPlot = new TreeMapPlot(this, context); - ltmPlot->setVisible(true); - mainLayout->addWidget(ltmPlot); + treemapPlot = new TreeMapPlot(this, context); + treemapPlot->setVisible(true); + mainLayout->addWidget(treemapPlot); mainLayout->setSpacing(0); mainLayout->setContentsMargins(0,0,0,0); setChartLayout(mainLayout); setIsBlank(false); - HelpWhatsThis *helpLTMPlot = new HelpWhatsThis(ltmPlot); - ltmPlot->setWhatsThis(helpLTMPlot->getWhatsThisText(HelpWhatsThis::ChartTrends_CollectionTreeMap)); + HelpWhatsThis *helpLTMPlot = new HelpWhatsThis(treemapPlot); + treemapPlot->setWhatsThis(helpLTMPlot->getWhatsThisText(HelpWhatsThis::ChartTrends_CollectionTreeMap)); // read metadata.xml QString filename = QDir(gcroot).canonicalPath()+"/metadata.xml"; @@ -152,11 +153,13 @@ TreeMapWindow::TreeMapWindow(Context *context) : connect(context, SIGNAL(rideDeleted(RideItem*)), this, SLOT(refresh(void))); connect(context, SIGNAL(filterChanged()), this, SLOT(refresh(void))); connect(context, SIGNAL(homeFilterChanged()), this, SLOT(refresh(void))); + connect(this, SIGNAL(perspectiveFilterChanged(QString)), this, SLOT(refresh())); + connect(this, SIGNAL(perspectiveChanged(Perspective*)), this, SLOT(refresh())); connect(context, SIGNAL(configChanged(qint32)), this, SLOT(refresh())); // user clicked on a cell in the plot - connect(ltmPlot, SIGNAL(clicked(QString,QString)), this, SLOT(cellClicked(QString,QString))); + connect(treemapPlot, SIGNAL(clicked(QString,QString)), this, SLOT(cellClicked(QString,QString))); // date settings connect(dateSetting, SIGNAL(useCustomRange(DateRange)), this, SLOT(useCustomRange(DateRange))); @@ -180,7 +183,7 @@ TreeMapWindow::rideSelected() void TreeMapWindow::refreshPlot() { - ltmPlot->setData(&settings); + treemapPlot->setData(&settings); } void @@ -247,6 +250,7 @@ TreeMapWindow::refresh() FilterSet fs; fs.addFilter(context->isfiltered, context->filters); fs.addFilter(context->ishomefiltered, context->homeFilters); + fs.addFilter(myPerspective->isFiltered(), myPerspective->filterlist(dr)); settings.specification.setFilterSet(fs); settings.specification.setDateRange(dr); diff --git a/src/Charts/TreeMapWindow.h b/src/Charts/TreeMapWindow.h index b1bbd9e5d..6477f7c2c 100644 --- a/src/Charts/TreeMapWindow.h +++ b/src/Charts/TreeMapWindow.h @@ -178,7 +178,7 @@ class TreeMapWindow : public GcChartWindow // Widgets QVBoxLayout *mainLayout; - TreeMapPlot *ltmPlot; + TreeMapPlot *treemapPlot; QLabel *title; QComboBox *field1, diff --git a/src/Charts/UserChart.cpp b/src/Charts/UserChart.cpp index 71bf1eba5..2d406c7c3 100644 --- a/src/Charts/UserChart.cpp +++ b/src/Charts/UserChart.cpp @@ -61,11 +61,13 @@ UserChart::UserChart(Context *context, bool rangemode) : GcChartWindow(context), connect(this, SIGNAL(dateRangeChanged(DateRange)), SLOT(setDateRange(DateRange))); connect(context, SIGNAL(homeFilterChanged()), this, SLOT(refresh())); connect(context, SIGNAL(filterChanged()), this, SLOT(refresh())); + connect(this, SIGNAL(perspectiveFilterChanged(QString)), this, SLOT(refresh())); } // need to refresh when chart settings change connect(settingsTool, SIGNAL(chartConfigChanged()), this, SLOT(chartConfigChanged())); connect(context, SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32))); + connect(this, SIGNAL(perspectiveChanged(Perspective*)), this, SLOT(refresh())); configChanged(0); } diff --git a/src/Charts/UserChartData.cpp b/src/Charts/UserChartData.cpp index 23e4ca1cf..7e042a7e2 100644 --- a/src/Charts/UserChartData.cpp +++ b/src/Charts/UserChartData.cpp @@ -17,6 +17,8 @@ */ #include "RideMetric.h" +#include "Perspective.h" +#include "UserChart.h" #include "UserChartData.h" #include "DataFilter.h" #include "Athlete.h" @@ -78,7 +80,7 @@ UserChartData::compute(RideItem *item, Specification spec, DateRange dr) FilterSet fs; fs.addFilter(context->isfiltered, context->filters); fs.addFilter(context->ishomefiltered, context->homeFilters); - Specification spec; + fs.addFilter(rt->chart->myPerspective->isFiltered(), rt->chart->myPerspective->filterlist(dr)); spec.setFilterSet(fs); // loop through rides for daterange diff --git a/src/Cloud/CloudDBChart.cpp b/src/Cloud/CloudDBChart.cpp index 4ca9f273d..0515defbd 100644 --- a/src/Cloud/CloudDBChart.cpp +++ b/src/Cloud/CloudDBChart.cpp @@ -1066,12 +1066,11 @@ CloudDBChartListDialog::applyAllFilters() { // setup to only show charts that are relevant to the current view unsigned int mask=0; switch(g_chartView) { - case 0 : mask = VIEW_HOME; break; + case 0 : mask = VIEW_TRENDS; break; default: case 1 : mask = VIEW_ANALYSIS; break; case 2 : mask = VIEW_DIARY; break; case 3 : mask = VIEW_TRAIN; break; - case 4 : mask = VIEW_INTERVAL; break; } QStringList searchList; diff --git a/src/Core/DataFilter.cpp b/src/Core/DataFilter.cpp index 7301ed899..eb4b4e024 100644 --- a/src/Core/DataFilter.cpp +++ b/src/Core/DataFilter.cpp @@ -2788,22 +2788,10 @@ Result DataFilter::evaluate(RideItem *item, RideFilePoint *p) return res; } -Result DataFilter::evaluate(DateRange dr, QString filter) +Result DataFilter::evaluate(Specification spec, DateRange dr) { - // if there is no current ride item then there is no data - // so it really is ok to baulk at no current ride item here - // we must always have a ride since context is used - if (context->currentRideItem() == NULL || !treeRoot || DataFiltererrors.count()) return Result(0); - - // reset stack - rt.stack = 0; - Result res(0); - Specification spec; - spec.setDateRange(dr); - if (filter != "") spec.addMatches(SearchFilterBox::matches(context, filter)); - // if we are a set of functions.. if (rt.functions.count()) { @@ -2820,6 +2808,23 @@ Result DataFilter::evaluate(DateRange dr, QString filter) return res; } +Result DataFilter::evaluate(DateRange dr, QString filter) +{ + // if there is no current ride item then there is no data + // so it really is ok to baulk at no current ride item here + // we must always have a ride since context is used + if (context->currentRideItem() == NULL || !treeRoot || DataFiltererrors.count()) return Result(0); + + // reset stack + rt.stack = 0; + + Specification spec; + spec.setDateRange(dr); + if (filter != "") spec.addMatches(SearchFilterBox::matches(context, filter)); + + return evaluate(spec, dr); +} + QStringList DataFilter::check(QString query) { // since we may use it afterwards diff --git a/src/Core/DataFilter.h b/src/Core/DataFilter.h index 6e6fe7678..5fcc6f453 100644 --- a/src/Core/DataFilter.h +++ b/src/Core/DataFilter.h @@ -237,6 +237,7 @@ class DataFilter : public QObject // RideItem always available and supplies th context Result evaluate(RideItem *rideItem, RideFilePoint *p); Result evaluate(DateRange dr, QString filter=""); + Result evaluate(Specification spec, DateRange dr); QStringList getErrors() { return errors; }; void colorSyntax(QTextDocument *content, int pos); diff --git a/src/Gui/AthleteView.cpp b/src/Gui/AthleteView.cpp index f2170f936..8503e9593 100644 --- a/src/Gui/AthleteView.cpp +++ b/src/Gui/AthleteView.cpp @@ -22,7 +22,7 @@ static const int gl_progress_width = ROWHEIGHT/2; static const int gl_button_height = ROWHEIGHT*1.5; static const int gl_button_width = ROWHEIGHT*5; -AthleteView::AthleteView(Context *context) : ChartSpace(context, OverviewScope::ATHLETES) +AthleteView::AthleteView(Context *context) : ChartSpace(context, OverviewScope::ATHLETES, NULL) { HelpWhatsThis *help = new HelpWhatsThis(this); this->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::ScopeBar_Athletes)); diff --git a/src/Gui/ChartSpace.cpp b/src/Gui/ChartSpace.cpp index e7b964770..558861870 100644 --- a/src/Gui/ChartSpace.cpp +++ b/src/Gui/ChartSpace.cpp @@ -35,8 +35,8 @@ double gl_major; static QIcon grayConfig, whiteConfig, accentConfig; ChartSpaceItemRegistry *ChartSpaceItemRegistry::_instance; -ChartSpace::ChartSpace(Context *context, int scope) : - state(NONE), context(context), scope(scope), group(NULL), fixedZoom(0), _viewY(0), +ChartSpace::ChartSpace(Context *context, int scope, GcWindow *window) : + state(NONE), context(context), scope(scope), window(window), group(NULL), fixedZoom(0), _viewY(0), yresizecursor(false), xresizecursor(false), block(false), scrolling(false), setscrollbar(false), lasty(-1) { diff --git a/src/Gui/ChartSpace.h b/src/Gui/ChartSpace.h index bdc603b32..241b7da05 100644 --- a/src/Gui/ChartSpace.h +++ b/src/Gui/ChartSpace.h @@ -155,7 +155,7 @@ class ChartSpace : public QWidget public: - ChartSpace(Context *context, int scope); + ChartSpace(Context *context, int scope, GcWindow *window); // current state for event processing enum { NONE, DRAG, XRESIZE, YRESIZE } state; @@ -174,6 +174,9 @@ class ChartSpace : public QWidget QGraphicsView *device() { return view; } const QList allItems() { return items; } + // window we are rendered in + GcWindow *window; + signals: void itemConfigRequested(ChartSpaceItem*); diff --git a/src/Gui/GcWindowRegistry.cpp b/src/Gui/GcWindowRegistry.cpp index 71f6c78b4..1a6f3007f 100644 --- a/src/Gui/GcWindowRegistry.cpp +++ b/src/Gui/GcWindowRegistry.cpp @@ -65,12 +65,6 @@ // Not until v4.0 //#include "RouteWindow.h" -#define VIEW_TRAIN 0x01 -#define VIEW_ANALYSIS 0x02 -#define VIEW_DIARY 0x04 -#define VIEW_HOME 0x08 -#define VIEW_INTERVAL 0x16 - // GcWindows initialization is done in initialize method to enable translations GcWindowRegistry* GcWindows; @@ -79,37 +73,37 @@ GcWindowRegistry::initialize() { static GcWindowRegistry GcWindowsInit[34] = { // name GcWinID - { VIEW_HOME|VIEW_DIARY, tr("Overview "),GcWindowTypes::OverviewTrends }, - { VIEW_HOME|VIEW_DIARY, tr("User Chart"),GcWindowTypes::UserTrends }, - { VIEW_HOME|VIEW_DIARY, tr("Trends"),GcWindowTypes::LTM }, - { VIEW_HOME|VIEW_DIARY, tr("TreeMap"),GcWindowTypes::TreeMap }, - //{ VIEW_HOME, tr("Weekly Summary"),GcWindowTypes::WeeklySummary },// DEPRECATED - { VIEW_HOME|VIEW_DIARY, tr("Power Duration "),GcWindowTypes::CriticalPowerSummary }, - //{ VIEW_HOME, tr("Training Plan"),GcWindowTypes::SeasonPlan }, - //{ VIEW_HOME|VIEW_DIARY, tr("Performance Manager"),GcWindowTypes::PerformanceManager }, + { VIEW_TRENDS|VIEW_DIARY, tr("Overview "),GcWindowTypes::OverviewTrends }, + { VIEW_TRENDS|VIEW_DIARY, tr("User Chart"),GcWindowTypes::UserTrends }, + { VIEW_TRENDS|VIEW_DIARY, tr("Trends"),GcWindowTypes::LTM }, + { VIEW_TRENDS|VIEW_DIARY, tr("TreeMap"),GcWindowTypes::TreeMap }, + //{ VIEW_TRENDS, tr("Weekly Summary"),GcWindowTypes::WeeklySummary },// DEPRECATED + { VIEW_TRENDS|VIEW_DIARY, tr("Power Duration "),GcWindowTypes::CriticalPowerSummary }, + //{ VIEW_TRENDS, tr("Training Plan"),GcWindowTypes::SeasonPlan }, + //{ VIEW_TRENDS|VIEW_DIARY, tr("Performance Manager"),GcWindowTypes::PerformanceManager }, { VIEW_ANALYSIS, tr("User Chart "),GcWindowTypes::UserAnalysis }, { VIEW_ANALYSIS, tr("Overview"),GcWindowTypes::Overview }, - { VIEW_ANALYSIS|VIEW_INTERVAL, tr("Summary"),GcWindowTypes::RideSummary }, + { VIEW_ANALYSIS, tr("Summary"),GcWindowTypes::RideSummary }, { VIEW_ANALYSIS, tr("Data"),GcWindowTypes::MetadataWindow }, //{ VIEW_ANALYSIS, tr("Summary and Details"),GcWindowTypes::Summary }, //{ VIEW_ANALYSIS, tr("Editor"),GcWindowTypes::RideEditor }, - { VIEW_ANALYSIS|VIEW_INTERVAL, tr("Performance"),GcWindowTypes::AllPlot }, + { VIEW_ANALYSIS, tr("Performance"),GcWindowTypes::AllPlot }, { VIEW_ANALYSIS, tr("Power Duration"),GcWindowTypes::CriticalPower }, { VIEW_ANALYSIS, tr("Histogram"),GcWindowTypes::Histogram }, - { VIEW_HOME|VIEW_DIARY, tr("Distribution"),GcWindowTypes::Distribution }, + { VIEW_TRENDS|VIEW_DIARY, tr("Distribution"),GcWindowTypes::Distribution }, { VIEW_ANALYSIS, tr("Pedal Force vs Velocity"),GcWindowTypes::PfPv }, { VIEW_ANALYSIS, tr("Heartrate vs Power"),GcWindowTypes::HrPw }, - { VIEW_ANALYSIS|VIEW_INTERVAL, tr("Map"),GcWindowTypes::RideMapWindow }, + { VIEW_ANALYSIS, tr("Map"),GcWindowTypes::RideMapWindow }, { VIEW_ANALYSIS, tr("R Chart"),GcWindowTypes::RConsole }, - { VIEW_HOME, tr("R Chart "),GcWindowTypes::RConsoleSeason }, + { VIEW_TRENDS, tr("R Chart "),GcWindowTypes::RConsoleSeason }, { VIEW_ANALYSIS, tr("Python Chart"),GcWindowTypes::Python }, - { VIEW_HOME, tr("Python Chart "),GcWindowTypes::PythonSeason }, + { VIEW_TRENDS, tr("Python Chart "),GcWindowTypes::PythonSeason }, //{ VIEW_ANALYSIS, tr("Bing Map"),GcWindowTypes::BingMap }, { VIEW_ANALYSIS, tr("Scatter"),GcWindowTypes::Scatter }, { VIEW_ANALYSIS, tr("Aerolab"),GcWindowTypes::Aerolab }, { VIEW_DIARY, tr("Calendar"),GcWindowTypes::Diary }, { VIEW_DIARY, tr("Navigator"), GcWindowTypes::ActivityNavigator }, - { VIEW_DIARY|VIEW_HOME, tr("Summary "), GcWindowTypes::DateRangeSummary }, + { VIEW_DIARY|VIEW_TRENDS, tr("Summary "), GcWindowTypes::DateRangeSummary }, { VIEW_TRAIN, tr("Telemetry"),GcWindowTypes::DialWindow }, { VIEW_TRAIN, tr("Workout"),GcWindowTypes::WorkoutPlot }, { VIEW_TRAIN, tr("Realtime"),GcWindowTypes::RealtimePlot }, @@ -117,7 +111,7 @@ GcWindowRegistry::initialize() { VIEW_TRAIN, tr("Video Player"),GcWindowTypes::VideoPlayer }, { VIEW_TRAIN, tr("Workout Editor"),GcWindowTypes::WorkoutWindow }, { VIEW_TRAIN, tr("Live Map"),GcWindowTypes::LiveMapWebPageWindow }, - { VIEW_ANALYSIS|VIEW_HOME|VIEW_TRAIN, tr("Web page"),GcWindowTypes::WebPageWindow }, + { VIEW_ANALYSIS|VIEW_TRENDS|VIEW_TRAIN, tr("Web page"),GcWindowTypes::WebPageWindow }, { 0, "", GcWindowTypes::None }}; // initialize the global registry GcWindows = GcWindowsInit; diff --git a/src/Gui/GcWindowRegistry.h b/src/Gui/GcWindowRegistry.h index dc3c63854..cd9f8130c 100644 --- a/src/Gui/GcWindowRegistry.h +++ b/src/Gui/GcWindowRegistry.h @@ -85,8 +85,7 @@ Q_DECLARE_METATYPE(GcWinID) #define VIEW_TRAIN 0x01 #define VIEW_ANALYSIS 0x02 #define VIEW_DIARY 0x04 -#define VIEW_HOME 0x08 -#define VIEW_INTERVAL 0x16 +#define VIEW_TRENDS 0x08 class GcChartWindow; class GcWindowRegistry { diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp index 5548c51c6..5a2d9ebad 100644 --- a/src/Gui/MainWindow.cpp +++ b/src/Gui/MainWindow.cpp @@ -655,7 +655,7 @@ MainWindow::MainWindow(const QDir &home) viewMenu->addSeparator(); viewMenu->addAction(tr("Activities"), this, SLOT(selectAnalysis())); - viewMenu->addAction(tr("Trends"), this, SLOT(selectHome())); + viewMenu->addAction(tr("Trends"), this, SLOT(selectTrends())); viewMenu->addAction(tr("Train"), this, SLOT(selectTrain())); viewMenu->addSeparator(); viewMenu->addAction(tr("Import Perspective..."), this, SLOT(importPerspective())); @@ -874,12 +874,11 @@ MainWindow::setChartMenu() // setup to only show charts that are relevant // to this view switch(currentTab->currentView()) { - case 0 : mask = VIEW_HOME; break; + case 0 : mask = VIEW_TRENDS; break; default: case 1 : mask = VIEW_ANALYSIS; break; case 2 : mask = VIEW_DIARY; break; case 3 : mask = VIEW_TRAIN; break; - case 4 : mask = VIEW_INTERVAL; break; } chartMenu->clear(); @@ -905,12 +904,11 @@ MainWindow::setChartMenu(QMenu *menu) // setup to only show charts that are relevant // to this view switch(currentTab->currentView()) { - case 0 : mask = VIEW_HOME; break; + case 0 : mask = VIEW_TRENDS; break; default: case 1 : mask = VIEW_ANALYSIS; break; case 2 : mask = VIEW_DIARY; break; case 3 : mask = VIEW_TRAIN; break; - case 4 : mask = VIEW_INTERVAL; break; } menu->clear(); @@ -1319,7 +1317,7 @@ MainWindow::sidebarSelected(int id) case 0: selectAthlete(); break; case 1: // plan not written yet break; - case 2: selectHome(); break; + case 2: selectTrends(); break; case 3: selectAnalysis(); break; case 4: // reflect not written yet break; @@ -1369,7 +1367,7 @@ MainWindow::selectDiary() } void -MainWindow::selectHome() +MainWindow::selectTrends() { currentTab->homeView->setPerspectives(perspectiveSelector); viewStack->setCurrentIndex(1); @@ -1626,7 +1624,7 @@ MainWindow::dropEvent(QDropEvent *event) QMessageBox::information(this, tr("Chart Import"), QString(tr("Imported %1 metric charts")).arg(imported.count())); // switch to trend view if we aren't on it - selectHome(); + selectTrends(); // now select what was added currentTab->context->notifyPresetSelected(currentTab->context->athlete->presets.count()-1); diff --git a/src/Gui/MainWindow.h b/src/Gui/MainWindow.h index 135cc5e21..b708dca20 100644 --- a/src/Gui/MainWindow.h +++ b/src/Gui/MainWindow.h @@ -186,7 +186,7 @@ class MainWindow : public QMainWindow void clearFilter(); void selectAthlete(); - void selectHome(); + void selectTrends(); void selectDiary(); void selectAnalysis(); void selectTrain(); diff --git a/src/Gui/NavigationModel.cpp b/src/Gui/NavigationModel.cpp index ddf1bb9b6..28832d202 100644 --- a/src/Gui/NavigationModel.cpp +++ b/src/Gui/NavigationModel.cpp @@ -30,7 +30,7 @@ NavigationModel::NavigationModel(Tab *tab) : tab(tab), block(false), viewinit(fa { connect(tab, SIGNAL(viewChanged(int)), this, SLOT(viewChanged(int))); connect(tab, SIGNAL(rideItemSelected(RideItem*)), this, SLOT(rideChanged(RideItem*))); - connect(static_cast(tab->view(0))->sidebar, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateChanged(DateRange))); + connect(static_cast(tab->view(0))->sidebar, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateChanged(DateRange))); connect(tab->context->mainWindow, SIGNAL(backClicked()), this, SLOT(back())); connect(tab->context->mainWindow, SIGNAL(forwardClicked()), this, SLOT(forward())); @@ -185,7 +185,7 @@ NavigationModel::action(bool redo, NavigationEvent event) case NavigationEvent::DATERANGE: { dr = redo ? event.after.value() : event.before.value(); - static_cast(tab->view(0))->sidebar->selectDateRange(dr); + static_cast(tab->view(0))->sidebar->selectDateRange(dr); } break; } diff --git a/src/Gui/Perspective.cpp b/src/Gui/Perspective.cpp index 5355aaea6..5e14e5b4c 100644 --- a/src/Gui/Perspective.cpp +++ b/src/Gui/Perspective.cpp @@ -51,7 +51,7 @@ static const int tileSpacing = 10; Perspective::Perspective(Context *context, QString title, int type) : GcWindow(context), context(context), active(false), clicked(NULL), dropPending(false), - type(type), title_(title), chartCursor(-2), df(NULL), expression_("") + type_(type), title_(title), chartCursor(-2), df(NULL), expression_("") { // setup control area QWidget *cw = new QWidget(this); @@ -77,10 +77,10 @@ Perspective::Perspective(Context *context, QString title, int type) : cl->addWidget(controlStack); setControls(cw); - switch(this->type) { + switch(this->type_) { case VIEW_ANALYSIS: view="analysis"; break; case VIEW_DIARY: view="diary"; break; - case VIEW_HOME: view="home"; break; + case VIEW_TRENDS: view="home"; break; case VIEW_TRAIN: view="train"; break; } setProperty("isManager", true); @@ -175,7 +175,7 @@ Perspective::Perspective(Context *context, QString title, int type) : connect(titleEdit, SIGNAL(textChanged(const QString&)), SLOT(titleChanged())); // trends view we should select a library chart when a chart is selected. - if (type == VIEW_HOME) connect(context, SIGNAL(presetSelected(int)), this, SLOT(presetSelected(int))); + if (type == VIEW_TRENDS) connect(context, SIGNAL(presetSelected(int)), this, SLOT(presetSelected(int))); // Allow realtime controllers to scroll train view with steering movements if (type == VIEW_TRAIN) connect(context, SIGNAL(steerScroll(int)), this, SLOT(steerScroll(int))); @@ -662,6 +662,7 @@ Perspective::addChart(GcChartWindow* newone) newone->setProperty("ride", QVariant::fromValue(notconst)); newone->setProperty("dateRange", property("dateRange")); newone->setProperty("style", currentStyle); + newone->setProperty("perspective", QVariant::fromValue(this)); // add to tabs switch (currentStyle) { @@ -1212,6 +1213,12 @@ GcWindowDialog::GcWindowDialog(GcWinID type, Context *context, GcChartWindow **h title->setText(GcWindowRegistry::title(type)); win = GcWindowRegistry::newGcWindow(type, context); + + // before we do anything, we need to set the perspective, in case + // the chart uses it to decide something - apologies for the convoluted + // method to determine the perspective, but its rare to use this outside + // the context of a chart or a view + win->setProperty("perspective", QVariant::fromValue(context->mainWindow->athleteTab()->view(context->mainWindow->athleteTab()->currentView())->page())); chartLayout->addWidget(win); //win->setFrameStyle(QFrame::Box); @@ -1447,7 +1454,7 @@ Perspective *Perspective::fromFile(Context *context, QString filename, int type) // return the first one with the right type (if there are multiple) for(int i=0; itype == type) + if (returning == NULL && handler.perspectives[i]->type_ == type) returning = handler.perspectives[i]; // delete any further perspectives @@ -1483,7 +1490,7 @@ void Perspective::toXml(QTextStream &out) { out<<"\n"; + <<"\" type=\"" << type_<<"\" expression=\"" << Utils::xmlprotect(expression_) << "\">\n"; // iterate over charts foreach (GcChartWindow *chart, charts) { @@ -1548,12 +1555,17 @@ Perspective::setExpression(QString expr) if (expression_ != "") df = new DataFilter(this, context, expression_); + // notify charts that the filter changed + // but only for trends views where it matters + if (type_ == VIEW_TRENDS) + foreach(GcWindow *chart, charts) + chart->notifyPerspectiveFilterChanged(expression_); } bool Perspective::relevant(RideItem *item) { - if (type != VIEW_ANALYSIS) return true; + if (type_ != VIEW_ANALYSIS) return true; else if (df == NULL) return false; else if (df == NULL || item == NULL) return false; @@ -1563,6 +1575,25 @@ Perspective::relevant(RideItem *item) } +QStringList +Perspective::filterlist(DateRange dr) +{ + QStringList returning; + + Specification spec; + spec.setDateRange(dr); + + foreach(RideItem *item, context->athlete->rideCache->rides()) { + if (!spec.pass(item)) continue; + + // if no filter, or the filter passes add to count + if (!isFiltered() || df->evaluate(item, NULL).number() != 0) + returning << item->fileName; + } + + return returning; +} + /*-------------------------------------------------------------------------------- * Import Chart Dialog - select/deselect charts before importing them * -----------------------------------------------------------------------------*/ @@ -1694,7 +1725,7 @@ ImportChartDialog::importClicked() } int x=0; - if (view == tr("Trends")) { x=0; context->mainWindow->selectHome(); } + if (view == tr("Trends")) { x=0; context->mainWindow->selectTrends(); } if (view == tr("Activities")) { x=1; context->mainWindow->selectAnalysis(); } if (view == tr("Diary")) { x=2; context->mainWindow->selectDiary(); } if (view == tr("Train")) { x=3; context->mainWindow->selectTrain(); } @@ -1729,12 +1760,13 @@ AddPerspectiveDialog::AddPerspectiveDialog(Context *context, QString &name, QStr form->addRow(new QLabel(tr("Perspective Name")), nameEdit); layout->addLayout(form); - if (type == VIEW_ANALYSIS) { + if (type == VIEW_ANALYSIS || type == VIEW_TRENDS) { filterEdit = new SearchBox(context, this); filterEdit->setFixedMode(true); filterEdit->setMode(SearchBox::Filter); filterEdit->setText(expression); - form->addRow(new QLabel(tr("Switch expression")), filterEdit); + if (type == VIEW_ANALYSIS) form->addRow(new QLabel(tr("Switch expression")), filterEdit); + if (type == VIEW_TRENDS) form->addRow(new QLabel(tr("Activities filter")), filterEdit); } QHBoxLayout *buttons = new QHBoxLayout(); @@ -1754,7 +1786,7 @@ void AddPerspectiveDialog::addClicked() { name = nameEdit->text(); - if (type == VIEW_ANALYSIS) expression = filterEdit->text(); + if (type == VIEW_ANALYSIS || type == VIEW_TRENDS) expression = filterEdit->text(); accept(); } diff --git a/src/Gui/Perspective.h b/src/Gui/Perspective.h index f87212e40..f64520fa3 100644 --- a/src/Gui/Perspective.h +++ b/src/Gui/Perspective.h @@ -46,6 +46,7 @@ class PerspectiveDialog; class QTextStream; class DataFilter; class SearchBox; +class TrendsView; class Perspective : public GcWindow { @@ -53,6 +54,7 @@ class Perspective : public GcWindow G_OBJECT friend ::TabView; + friend ::TrendsView; friend ::ViewParser; friend ::PerspectiveDialog; @@ -64,6 +66,10 @@ class Perspective : public GcWindow // am I relevant? (for switching when ride selected) bool relevant(RideItem*); + // the items I'd choose (for filtering on trends view) + bool isFiltered() const override { return (type_ == VIEW_TRENDS && df != NULL); } + QStringList filterlist(DateRange dr); + // get/set the expression (will compile df) QString expression() const; void setExpression(QString); @@ -73,6 +79,7 @@ class Perspective : public GcWindow bool toFile(QString filename); void toXml(QTextStream &out); + int type() const { return type_; } QString title() const { return title_; } void resetLayout(); @@ -142,7 +149,7 @@ class Perspective : public GcWindow bool dropPending; // what are we? - int type; + int type_; QString view; // top bar @@ -176,6 +183,8 @@ class Perspective : public GcWindow static void translateChartTitles(QList charts); }; +Q_DECLARE_METATYPE(Perspective*); + // setup the chart class GcWindowDialog : public QDialog { diff --git a/src/Gui/PerspectiveDialog.cpp b/src/Gui/PerspectiveDialog.cpp index 88d341c9a..2b0c3ae04 100644 --- a/src/Gui/PerspectiveDialog.cpp +++ b/src/Gui/PerspectiveDialog.cpp @@ -273,7 +273,7 @@ PerspectiveDialog::exportPerspectiveClicked() QString typedesc; switch (tabView->type) { - case VIEW_HOME: typedesc="Trends"; break; + case VIEW_TRENDS: typedesc="Trends"; break; case VIEW_ANALYSIS: typedesc="Analysis"; break; case VIEW_DIARY: typedesc="Diary"; break; case VIEW_TRAIN: typedesc="Train"; break; @@ -429,8 +429,13 @@ PerspectiveTableWidget::dropEvent(QDropEvent *event) // move, but only if source and dest are not the same if (perspective != hoverp) { chart = perspective->takeChart(chart); - if (chart) hoverp->addChart(chart); - emit chartMoved(chart); + if (chart) { + hoverp->addChart(chart); + emit chartMoved(chart); + + // let the chart know it has moved perspectives. + chart->notifyPerspectiveChanged(hoverp); + } } } } diff --git a/src/Gui/Tab.cpp b/src/Gui/Tab.cpp index 87504b0c0..64c18df3f 100644 --- a/src/Gui/Tab.cpp +++ b/src/Gui/Tab.cpp @@ -61,7 +61,7 @@ Tab::Tab(Context *context) : QWidget(context->mainWindow), context(context), nos homeControls = new QStackedWidget(this); homeControls->setFrameStyle(QFrame::Plain | QFrame::NoFrame); homeControls->setContentsMargins(0,0,0,0); - homeView = new HomeView(context, homeControls); + homeView = new TrendsView(context, homeControls); // Diary diaryControls = new QStackedWidget(this); diff --git a/src/Gui/Tab.h b/src/Gui/Tab.h index 9e66492ca..ed1d0cb31 100644 --- a/src/Gui/Tab.h +++ b/src/Gui/Tab.h @@ -102,7 +102,7 @@ class Tab: public QWidget // Each of the views QStackedWidget *views; AnalysisView *analysisView; - HomeView *homeView; + TrendsView *homeView; TrainView *trainView; DiaryView *diaryView; diff --git a/src/Gui/TabView.cpp b/src/Gui/TabView.cpp index fd60bfb91..a6ced667d 100644 --- a/src/Gui/TabView.cpp +++ b/src/Gui/TabView.cpp @@ -65,7 +65,7 @@ TabView::TabView(Context *context, int type) : stack->insertWidget(0, splitter); // splitter always at index 0 QString heading = tr("Compare Activities and Intervals"); - if (type == VIEW_HOME) heading = tr("Compare Date Ranges"); + if (type == VIEW_TRENDS) heading = tr("Compare Date Ranges"); else if (type == VIEW_TRAIN) heading = tr("Intensity Adjustments and Workout Control"); mainSplitter = new ViewSplitter(Qt::Vertical, heading, this); @@ -276,7 +276,7 @@ TabView::saveState() case VIEW_ANALYSIS: view = "analysis"; break; case VIEW_TRAIN: view = "train"; break; case VIEW_DIARY: view = "diary"; break; - case VIEW_HOME: view = "home"; break; + case VIEW_TRENDS: view = "home"; break; } QString filename = context->athlete->home->config().canonicalPath() + "/" + view + "-perspectives.xml"; @@ -314,7 +314,7 @@ TabView::restoreState(bool useDefault) case VIEW_ANALYSIS: view = "analysis"; break; case VIEW_TRAIN: view = "train"; break; case VIEW_DIARY: view = "diary"; break; - case VIEW_HOME: view = "home"; break; + case VIEW_TRENDS: view = "home"; break; } // restore window state diff --git a/src/Gui/TabView.h b/src/Gui/TabView.h index 3815b4809..2337c6fea 100644 --- a/src/Gui/TabView.h +++ b/src/Gui/TabView.h @@ -142,7 +142,7 @@ class TabView : public QWidget protected: Context *context; - int type; // used by windowregistry; e.g VIEW_TRAIN VIEW_ANALYSIS VIEW_DIARY VIEW_HOME + int type; // used by windowregistry; e.g VIEW_TRAIN VIEW_ANALYSIS VIEW_DIARY VIEW_TRENDS // we don't care what values are pass through to the GcWindowRegistry to decide // what charts are relevant for this view. diff --git a/src/Gui/Views.cpp b/src/Gui/Views.cpp index 4045945e0..a1579222d 100644 --- a/src/Gui/Views.cpp +++ b/src/Gui/Views.cpp @@ -26,6 +26,7 @@ #include "TrainDB.h" #include "ComparePane.h" #include "TrainBottom.h" +#include "Specification.h" #include extern QDesktopWidget *desktop; @@ -176,7 +177,7 @@ DiaryView::isBlank() else return true; } -HomeView::HomeView(Context *context, QStackedWidget *controls) : TabView(context, VIEW_HOME) +TrendsView::TrendsView(Context *context, QStackedWidget *controls) : TabView(context, VIEW_TRENDS) { sidebar = new LTMSidebar(context); BlankStateHomePage *b = new BlankStateHomePage(context); @@ -200,7 +201,7 @@ HomeView::HomeView(Context *context, QStackedWidget *controls) : TabView(context connect(bottomSplitter(), SIGNAL(compareClear()), bottom(), SLOT(clear())); } -HomeView::~HomeView() +TrendsView::~TrendsView() { appsettings->setValue(GC_SETTINGS_MAIN_SIDEBAR "trend", _sidebar); delete sidebar; @@ -208,28 +209,73 @@ HomeView::~HomeView() } void -HomeView::compareChanged(bool state) +TrendsView::compareChanged(bool state) { // we turned compare on / off context->notifyCompareDateRanges(state); } -void -HomeView::dateRangeChanged(DateRange dr) +int +TrendsView::countActivities(Perspective *perspective, DateRange dr) { + // get the filterset for the current daterange + // using the data filter expression + int returning=0; + bool filtered= perspective->df != NULL; + + Specification spec; + FilterSet fs; + fs.addFilter(context->isfiltered, context->filters); + fs.addFilter(context->ishomefiltered, context->homeFilters); + spec.setDateRange(dr); + spec.setFilterSet(fs); + + foreach(RideItem *item, context->athlete->rideCache->rides()) { + if (!spec.pass(item)) continue; + + // if no filter, or the filter passes add to count + if (!filtered || perspective->df->evaluate(item, NULL).number() != 0) + returning++; + } + return returning; + +} + +void +TrendsView::dateRangeChanged(DateRange dr) +{ +#if 0 // commented out auto switching perspectives on trends because it was annoying... + // if there are no activities for the current perspective + // lets switch to one that has the most + if (countActivities(page(), dr) == 0) { + + int max=0; + int index=perspectives_.indexOf(page()); + int switchto=index; + for (int i=0; i max) { max=count; switchto=i; } + } + + // if we found a better one + if (index != switchto) context->mainWindow->switchPerspective(switchto); + } +#endif + + // Once the right perspective is set, we can go ahead and update everyone emit dateChanged(dr); context->notifyDateRangeChanged(dr); if (loaded) page()->setProperty("dateRange", QVariant::fromValue(dr)); } bool -HomeView::isBlank() +TrendsView::isBlank() { if (context->athlete->rideCache->rides().count() > 0) return false; else return true; } void -HomeView::justSelected() +TrendsView::justSelected() { if (isSelected()) { // force date range refresh diff --git a/src/Gui/Views.h b/src/Gui/Views.h index 1b28ec5f2..530c40205 100644 --- a/src/Gui/Views.h +++ b/src/Gui/Views.h @@ -100,18 +100,20 @@ private slots: }; class LTMSidebar; -class HomeView : public TabView +class TrendsView : public TabView { Q_OBJECT public: - HomeView(Context *context, QStackedWidget *controls); - ~HomeView(); + TrendsView(Context *context, QStackedWidget *controls); + ~TrendsView(); LTMSidebar *sidebar; Perspective *hw; + int countActivities(Perspective *, DateRange dr); + signals: void dateChanged(DateRange); diff --git a/src/Python/PythonEmbed.cpp b/src/Python/PythonEmbed.cpp index 1c465e564..bb8fc5bb1 100644 --- a/src/Python/PythonEmbed.cpp +++ b/src/Python/PythonEmbed.cpp @@ -186,6 +186,8 @@ bool PythonEmbed::pythonInstalled(QString &pybin, QString &pypath, QString PYTHO PythonEmbed::PythonEmbed(const bool verbose, const bool interactive) : verbose(verbose), interactive(interactive) { loaded = false; + chart = NULL; + perspective = NULL; threadid=-1; name = QString("GoldenCheetah"); diff --git a/src/Python/PythonEmbed.h b/src/Python/PythonEmbed.h index 54fba33ea..bfb9c8ec9 100644 --- a/src/Python/PythonEmbed.h +++ b/src/Python/PythonEmbed.h @@ -95,6 +95,7 @@ class PythonEmbed { // context for caller - can be called in a thread QMap contexts; + Perspective *perspective; PythonChart *chart; QWidget *canvas; diff --git a/src/Python/SIP/Bindings.cpp b/src/Python/SIP/Bindings.cpp index 41134f7df..b1c045a82 100644 --- a/src/Python/SIP/Bindings.cpp +++ b/src/Python/SIP/Bindings.cpp @@ -15,6 +15,7 @@ #include "HrZones.h" #include "PaceZones.h" #include "DataProcessor.h" +#include "Perspective.h" #include "Bindings.h" @@ -583,6 +584,7 @@ Bindings::activities(QString filter) const FilterSet fs; fs.addFilter(context->isfiltered, context->filters); fs.addFilter(context->ishomefiltered, context->homeFilters); + fs.addFilter(python->perspective->isFiltered(), python->perspective->filterlist(DateRange(QDate(1,1,1970),QDate(31,12,3000)))); // did call contain any filters? if (filter != "") { @@ -1187,6 +1189,7 @@ Bindings::seasonMetrics(bool all, DateRange range, QString filter) const FilterSet fs; fs.addFilter(context->isfiltered, context->filters); fs.addFilter(context->ishomefiltered, context->homeFilters); + fs.addFilter(python->perspective->isFiltered(), python->perspective->filterlist(DateRange(QDate(1,1,1970),QDate(31,12,3000)))); // did call contain a filter? if (filter != "") { @@ -1380,6 +1383,7 @@ Bindings::seasonIntervals(DateRange range, QString type) const FilterSet fs; fs.addFilter(context->isfiltered, context->filters); fs.addFilter(context->ishomefiltered, context->homeFilters); + fs.addFilter(python->perspective->isFiltered(), python->perspective->filterlist(DateRange(QDate(1,1,1970),QDate(31,12,3000)))); specification.setFilterSet(fs); // we need to count intervals that are in range... @@ -1817,6 +1821,7 @@ Bindings::metrics(QString metric, bool all, QString filter) const FilterSet fs; fs.addFilter(context->isfiltered, context->filters); fs.addFilter(context->ishomefiltered, context->homeFilters); + fs.addFilter(python->perspective->isFiltered(), python->perspective->filterlist(DateRange(QDate(1,1,1970),QDate(31,12,3000)))); // did call contain a filter? if (filter != "") { @@ -1983,8 +1988,8 @@ Bindings::seasonMeanmax(bool all, DateRange range, QString filter) const if (all) range = DateRange(QDate(1900,01,01), QDate(2100,01,01)); // did call contain any filters? - QStringList filelist; - bool filt=false; + QStringList filelist=python->perspective->filterlist(DateRange(QDate(1,1,1970),QDate(31,12,3000))); + bool filt=python->perspective->isFiltered(); // if not empty write a filter if (filter != "") { @@ -2402,6 +2407,7 @@ Bindings::seasonPeaks(bool all, DateRange range, QString filter, QListisfiltered, context->filters); fs.addFilter(context->ishomefiltered, context->homeFilters); + fs.addFilter(python->perspective->isFiltered(), python->perspective->filterlist(DateRange(QDate(1,1,1970),QDate(31,12,3000)))); specification.setFilterSet(fs); // did call contain any filters? diff --git a/src/R/RTool.cpp b/src/R/RTool.cpp index a4bee1dcd..e109a3f54 100644 --- a/src/R/RTool.cpp +++ b/src/R/RTool.cpp @@ -37,6 +37,7 @@ #include "HrZones.h" #include "PaceZones.h" #include "GenericChart.h" +#include "Perspective.h" // Structure used to register routines has changed in v3.4 of R // @@ -68,6 +69,7 @@ RTool::RTool() failed = false; starting = true; canvas = NULL; + perspective = NULL; chart = NULL; context = NULL; @@ -1227,6 +1229,7 @@ RTool::dfForDateRange(bool all, DateRange range, SEXP filter) FilterSet fs; fs.addFilter(rtool->context->isfiltered, rtool->context->filters); fs.addFilter(rtool->context->ishomefiltered, rtool->context->homeFilters); + fs.addFilter(rtool->perspective->isFiltered(), rtool->perspective->filterlist(range)); specification.setFilterSet(fs); // did call contain any filters? @@ -1451,6 +1454,7 @@ RTool::dfForDateRangeIntervals(DateRange range, QStringList types) FilterSet fs; fs.addFilter(rtool->context->isfiltered, rtool->context->filters); fs.addFilter(rtool->context->ishomefiltered, rtool->context->homeFilters); + fs.addFilter(rtool->perspective->isFiltered(), rtool->perspective->filterlist(range)); specification.setFilterSet(fs); // we need to count intervals that are in range... @@ -2629,6 +2633,12 @@ RTool::dfForDateRangeMeanmax(bool all, DateRange range, SEXP filter) } UNPROTECT(1); + // apply perspective filter if trends view and filtered + if (rtool->perspective && rtool->perspective->type() == VIEW_TRENDS && rtool->perspective->isFiltered()) { + filt = true; + filelist << rtool->perspective->filterlist(DateRange(range)); + } + // RideFileCache for a date range with our filters (if any) RideFileCache cache(rtool->context, range.from, range.to, filt, filelist, true, NULL); @@ -2921,6 +2931,7 @@ RTool::dfForDateRangePeaks(bool all, DateRange range, SEXP filter, QListcontext->isfiltered, rtool->context->filters); fs.addFilter(rtool->context->ishomefiltered, rtool->context->homeFilters); + fs.addFilter(rtool->perspective->isFiltered(), rtool->perspective->filterlist(range)); specification.setFilterSet(fs); // did call contain any filters? diff --git a/src/R/RTool.h b/src/R/RTool.h index 66a936f57..33f6b31f1 100644 --- a/src/R/RTool.h +++ b/src/R/RTool.h @@ -38,6 +38,7 @@ class RTool { // the canvas to plot on, it may be null // if no canvas is active RCanvas *canvas; + Perspective *perspective; RChart *chart; Context *context;