Basic Navigation Model

.. back and forward buttons to navigate between views and selections.

.. currently limited to just rides, date ranges and views.

.. next step is to enable click to select from trends overviews to allow
   users to drill down from the season overview into activities and
   back again.

.. part of the shift from searching through lists to analyse data to
   exploring data visually with drill down and click through.

.. the buttons are very basic and there is no way to explore the
   history / recently viewed items etc. these will come later.

Fixes #3529
This commit is contained in:
Mark Liversedge
2020-07-05 16:38:01 +01:00
parent 94c723f4a0
commit 64da909243
22 changed files with 441 additions and 19 deletions

View File

@@ -106,7 +106,7 @@ ChartBar::ChartBar(Context *context) : QWidget(context->mainWindow), context(con
menuButton->setStyleSheet("QToolButton { border: none; padding: 0px; }");
menuButton->setAutoFillBackground(false);
menuButton->setFixedSize(20*dpiXFactor,20*dpiYFactor);
menuButton->setIcon(iconFromPNG(":images/sidebar/extra.png"));
menuButton->setIcon(iconFromPNG(":images/sidebar/plus.png"));
menuButton->setIconSize(QSize(10*dpiXFactor,10*dpiYFactor));
menuButton->setFocusPolicy(Qt::NoFocus);
mlayout->addWidget(menuButton);
@@ -119,13 +119,12 @@ ChartBar::ChartBar(Context *context) : QWidget(context->mainWindow), context(con
connect(menuMapper, SIGNAL(mapped(int)), this, SLOT(triggerContextMenu(int)));
barMenu = new QMenu("Add");
chartMenu = barMenu->addMenu(tr("Add Chart"));
chartMenu = barMenu->addMenu(tr("New "));
barMenu->addAction(tr("Import Chart..."), context->mainWindow, SLOT(importChart()));
barMenu->addAction(tr("Import ..."), context->mainWindow, SLOT(importChart()));
#ifdef GC_HAS_CLOUD_DB
barMenu->addAction(tr("Upload Chart..."), context->mainWindow, SLOT(exportChartToCloudDB()));
barMenu->addAction(tr("Download Chart..."), context->mainWindow, SLOT(addChartFromCloudDB()));
barMenu->addAction(tr("Download ..."), context->mainWindow, SLOT(addChartFromCloudDB()));
#endif
// menu
connect(menuButton, SIGNAL(clicked()), this, SLOT(menuPopup()));

View File

@@ -20,6 +20,7 @@
#include "Athlete.h"
#include "RideMetric.h"
#include "NavigationModel.h"
#include "UserMetricSettings.h"
#include "UserMetricParser.h"
#include "DataFilter.h"

View File

@@ -68,6 +68,7 @@ class Context;
class Athlete;
class MainWindow;
class Tab;
class NavigationModel;
class Context : public QObject
{
@@ -78,6 +79,7 @@ class Context : public QObject
~Context();
// mainwindow state
NavigationModel *nav;
int viewIndex;
bool showSidebar, showLowbar, showToolbar, showTabbar;
int style;
@@ -169,6 +171,8 @@ class Context : public QObject
void notifyStop() { emit stop(); }
void notifySeek(long x) { emit seek(x); }
// date range selection
void notifyDateRangeChanged(DateRange x) { dr_=x; emit dateRangeSelected(x); }
void notifyWorkoutsChanged() { emit workoutsChanged(); }
void notifyVideoSyncChanged() { emit VideoSyncChanged(); }
@@ -243,6 +247,7 @@ class Context : public QObject
void autoDownloadEnd();
void autoDownloadProgress(QString, double, int, int);
void dateRangeSelected(DateRange);
void rideSelected(RideItem*);
// we added/deleted/changed an item

View File

@@ -135,11 +135,16 @@ class RideCache : public QObject
friend class ::LTMPlot; // get weekly performances
friend class ::Banister; // get weekly performances
friend class ::Leaf; // get weekly performances
friend class ::RideItem; // adds to deletelist in destructor
friend class ::NavigationModel; // checks deletelist during redo/undo
Context *context;
QDir directory, plannedDirectory;
QVector<RideItem*> rides_, reverse_, delete_;
// rides and reverse are the main lists
// delete_ is a list of items to garbage collect (delete later)
// deletelist is a list of items that no longer exist (deleted)
QVector<RideItem*> rides_, reverse_, delete_, deletelist;
RideCacheModel *model_;
bool exiting;
double progress_; // percent

View File

@@ -17,6 +17,7 @@
*/
#include "RideItem.h"
#include "RideCache.h"
#include "RideMetric.h"
#include "RideFile.h"
#include "RideFileCache.h"
@@ -238,6 +239,9 @@ RideFile *RideItem::ride(bool open)
RideItem::~RideItem()
{
// add to the deleted list
if (context->athlete->rideCache) context->athlete->rideCache->deletelist << this;
//qDebug()<<"deleting:"<<fileName;
if (isOpen()) close();
if (fileCache_) delete fileCache_;

View File

@@ -39,8 +39,6 @@ class Context;
class UserData;
class ComparePane;
Q_DECLARE_METATYPE(RideItem*)
class RideItem : public QObject
{
@@ -222,4 +220,7 @@ class RideItem : public QObject
void updateIntervals();
};
Q_DECLARE_OPAQUE_POINTER(RideItem*);
Q_DECLARE_METATYPE(RideItem*)
#endif // _GC_RideItem_h

View File

@@ -183,6 +183,7 @@ DateRange::DateRange(const DateRange &other) : QObject()
to=other.to;
name=other.name;
color=other.color;
id = other.id;
valid = from.isValid() && to.isValid();
}

View File

@@ -20,6 +20,7 @@
#define _TimeUtils_h
#include <QDate>
#include <QUuid>
#include <QDateEdit>
#include <QColor>
#include <QDateTime>
@@ -46,9 +47,19 @@ class DateRange : QObject
DateRange(const DateRange& other);
DateRange(QDate from = QDate(), QDate to = QDate(), QString name ="", QColor=QColor(127,127,127));
DateRange& operator=(const DateRange &);
bool operator!=(const DateRange&other) {
if (other.from != from || other.to != to || other.name != name) return true;
return false;
}
bool operator==(const DateRange&other) {
if (other.from == from && other.to == to && other.name == name) return true;
return false;
}
QDate from, to;
QString name;
QColor color; // used by R code only
QUuid id;
// does this date fall in the range selection ?
bool pass(QDate date) {

View File

@@ -323,6 +323,33 @@ LTMSidebar::configChanged(qint32)
* Selections Made
*----------------------------------------------------------------------*/
void
LTMSidebar::selectDateRange(DateRange dr)
{
for(int i=0; i<seasons->seasons.count(); i++) {
Season s = seasons->seasons.at(i);
if (s.start == dr.from && s.end == dr.to && s.name == dr.name) {
// bingo
dateRangeTree->selectionModel()->clearSelection(); // clear selection
allDateRanges->child(i)->setSelected(true); // select ours
return;
} else {
QStringList names=dr.name.split("/");
if (names.count() == 2 && s.name == names.at(0)) {
for (int j=0; j<s.phases.count(); j++) {
Phase p = s.phases.at(j);
if (p.start == dr.from && p.end == dr.to && p.name == names.at(1)) {
// bingo
dateRangeTree->selectionModel()->clearSelection(); // clear selection
allDateRanges->child(i)->child(j)->setSelected(true); // select ours
return;
}
}
}
}
}
}
void
LTMSidebar::dateRangeTreeWidgetSelectionChanged()
{

View File

@@ -55,6 +55,7 @@ class LTMSidebar : public QWidget
public slots:
// date range selection and editing
void selectDateRange(DateRange);
void dateRangeTreeWidgetSelectionChanged();
void dateRangePopup(QPoint);
void dateRangePopup();

View File

@@ -271,8 +271,26 @@ MainWindow::MainWindow(const QDir &home)
lowbarIcon = iconFromPNG(":images/mac/lowbar.png");
tabbedIcon = iconFromPNG(":images/mac/tabbed.png");
tiledIcon = iconFromPNG(":images/mac/tiled.png");
backIcon = iconFromPNG(":images/mac/back.png");
forwardIcon = iconFromPNG(":images/mac/forward.png");
QSize isize(19 *dpiXFactor,19 *dpiYFactor);
back = new QPushButton(this);
back->setIcon(backIcon);
back->setFixedHeight(24 *dpiYFactor);
back->setFixedWidth(32 *dpiYFactor);
back->setIconSize(isize);
back->setStyle(toolStyle);
connect(back, SIGNAL(clicked(bool)), this, SIGNAL(backClicked()));
forward = new QPushButton(this);
forward->setIcon(forwardIcon);
forward->setFixedHeight(24 *dpiYFactor);
forward->setFixedWidth(32 *dpiYFactor);
forward->setIconSize(isize);
forward->setStyle(toolStyle);
connect(forward, SIGNAL(clicked(bool)), this, SIGNAL(forwardClicked()));
lowbar = new QPushButton(this);
lowbar->setIcon(lowbarIcon);
lowbar->setFixedHeight(24 *dpiYFactor);
@@ -324,11 +342,19 @@ MainWindow::MainWindow(const QDir &home)
searchBox->setStyle(toolStyle);
searchBox->setFixedWidth(400 * dpiXFactor);
searchBox->setFixedHeight(28 * dpiYFactor);
QWidget *space = new QWidget(this);
space->setAutoFillBackground(false);
space->setFixedWidth(5 * dpiYFactor);
head->addWidget(space);
head->addWidget(back);
head->addWidget(forward);
head->addStretch();
head->addWidget(sidelist);
head->addWidget(lowbar);
head->addWidget(styleSelector);
head->setFixedHeight(searchBox->height() + (10 *dpiXFactor));
head->setFixedHeight(searchBox->height() + (16 *dpiXFactor));
connect(searchBox, SIGNAL(searchResults(QStringList)), this, SLOT(setFilter(QStringList)));
connect(searchBox, SIGNAL(searchClear()), this, SLOT(clearFilter()));
@@ -2199,6 +2225,9 @@ MainWindow::configChanged(qint32)
searchBox->setStyleSheet(QString("QLineEdit { background: %1; color: %2; }").arg(GColor(CCHROME).name()).arg(GCColor::invertColor(GColor(CCHROME)).name()));
#endif
QString buttonstyle = QString("QPushButton { border: none; background-color: %1; }").arg(CCHROME);
back->setStyleSheet(buttonstyle);
forward->setStyleSheet(buttonstyle);
// All platforms
QPalette tabbarPalette;

View File

@@ -93,6 +93,7 @@ class MainWindow : public QMainWindow
// currently selected tab
Tab *athleteTab() { return currentTab; }
NewSideBar *newSidebar() { return sidebar; }
protected:
@@ -112,6 +113,10 @@ class MainWindow : public QMainWindow
void setSplash(bool first=false);
void clearSplash();
signals:
void backClicked();
void forwardClicked();
public slots:
bool eventFilter(QObject*,QEvent*);
@@ -273,11 +278,12 @@ class MainWindow : public QMainWindow
// Not on Mac so use other types
QPushButton *sidelist, *lowbar;
QPushButton *back, *forward;
QtSegmentControl *styleSelector;
GcToolBar *head;
// the icons
QIcon sidebarIcon, lowbarIcon, tabbedIcon, tiledIcon;
QIcon backIcon, forwardIcon, sidebarIcon, lowbarIcon, tabbedIcon, tiledIcon;
// tab bar (that supports swtitching on drag and drop)
DragBar *tabbar;

209
src/Gui/NavigationModel.cpp Normal file
View File

@@ -0,0 +1,209 @@
/*
* Copyright (c) 2020 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 "NavigationModel.h"
#include "RideCache.h"
// a little too intertwined with these two,
// probably needs refactoring out at some point.
#include "LTMSidebar.h"
#include "NewSideBar.h"
#include "MainWindow.h"
NavigationModel::NavigationModel(Tab *tab) : tab(tab), block(false), viewinit(false), iteminit(false), drinit(false)
{
connect(tab, SIGNAL(viewChanged(int)), this, SLOT(viewChanged(int)));
connect(tab, SIGNAL(rideItemSelected(RideItem*)), this, SLOT(rideChanged(RideItem*)));
connect(static_cast<HomeView*>(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()));
stackpointer=-1;
}
NavigationModel::~NavigationModel()
{
}
void
NavigationModel::addToStack(NavigationEvent &event)
{
if (stackpointer == -1) {
// empty
stack.clear();
stack << event;
stackpointer++;
} else if (stackpointer == (stack.count()-1)) {
// end, just append
stack << event;
stackpointer++;
} else if (stackpointer < (stack.count()-1)) {
// middle, truncate
stack.remove(stackpointer+1, stack.count() - (stackpointer+1));
stack << event;
stackpointer++;
}
}
void
NavigationModel::rideChanged(RideItem *x)
{
if (block) return;
// initial values
if (iteminit == false) {
iteminit = true;
item = x;
return;
}
if (item != x) {
// add to recent
if (!recent.contains(x)) recent << x;
// add to the stack -- truncate if needed
NavigationEvent add(NavigationEvent::RIDE, x);
add.before.setValue(item);
addToStack(add);
item = x;
}
}
void
NavigationModel::dateChanged(DateRange x)
{
if (block) return;
// initial values
if (drinit == false) {
drinit = true;
dr = x;
return;
}
if (dr != x) {
// add to the stack
NavigationEvent add(NavigationEvent::DATERANGE, x);
add.before.setValue(dr);
addToStack(add);
dr = x;
}
}
void
NavigationModel::viewChanged(int x)
{
if (block) return;
// initial values
if (viewinit == false) {
viewinit = true;
view = x;
return;
}
if (view != x) {
// add to the stack
NavigationEvent add(NavigationEvent::VIEW, x);
add.before.setValue(view);
addToStack(add);
view = x;
}
}
void
NavigationModel::action(bool redo, NavigationEvent event)
{
block = true; // don't observe events during redo/undo
switch(event.type) {
case NavigationEvent::VIEW:
{
int view = redo ? event.after.toInt() : event.before.toInt();
tab->selectView(view);
// new side bar uses a different id, which will
// eventually be refactored to be the only id
// but for now we need to map this
int id=0;
switch(view) {
case 0: id=2; break; // trends
case 1: id=3; break; // analysis
case 2: id=0; break; // diary
case 3: id=5; break; // train
}
tab->context->mainWindow->newSidebar()->setItemSelected(id, true);
}
break;
case NavigationEvent::RIDE:
{
RideItem *item = redo ? event.after.value<RideItem*>() : event.before.value<RideItem*>();
// don't select deleted rides (!!)
if (!tab->context->athlete->rideCache->deletelist.contains(item))
tab->context->athlete->selectRideFile(item->fileName);
}
break;
case NavigationEvent::DATERANGE:
{
DateRange dr = redo ? event.after.value<DateRange>() : event.before.value<DateRange>();
static_cast<HomeView*>(tab->view(0))->sidebar->selectDateRange(dr);
}
break;
}
block = false;
}
void
NavigationModel::back()
{
// are we the current tab?
if (tab->context->mainWindow->athleteTab() == tab) {
if (stackpointer >= 0) {
action(false, stack[stackpointer]);
stackpointer--;
}
}
}
void
NavigationModel::forward()
{
// are we the current tab?
if (tab->context->mainWindow->athleteTab() == tab) {
if ((stackpointer+1) < stack.count() && stack.count()) {
stackpointer++;
action(true, stack[stackpointer]);
}
}
}

94
src/Gui/NavigationModel.h Normal file
View File

@@ -0,0 +1,94 @@
/*
* Copyright (c) 2020 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 <QObject>
#include <QUuid>
#include <QString>
#include <QVariant>
#include "Tab.h"
#include "TimeUtils.h"
#include "Context.h"
#include "Athlete.h"
#include "RideItem.h"
class NavigationEvent {
public:
// events we observe and replay is a limited subset mostly
// about changing views, dates and ride selections
enum NavigationEventType { VIEW=0, TAB, RIDE, DATERANGE, FILTER } type;
NavigationEvent(NavigationEventType t, int v) : type(t) { after.setValue(v); }
NavigationEvent(NavigationEventType t, DateRange v) : type(t) { after.setValue(v); }
NavigationEvent(NavigationEventType t, RideItem* v) : type(t) { after.setValue(v); }
NavigationEvent(NavigationEventType t, QString v) : type(t) { after.setValue(v); }
// before is updated by the navigation model
QVariant before, after;
};
//
// Navigation is for the athlete tab -- not mainwindow.
// Remember: multiple athletes can be opened at once.
//
class NavigationModel : public QObject
{
Q_OBJECT
public:
NavigationModel(Tab *tab);
~NavigationModel();
// keep a track of the rides we've looked
// at recently-- might want to quick link
// to these in the future ...
QVector<RideItem*> recent;
public slots:
void viewChanged(int);
void rideChanged(RideItem*);
void dateChanged(DateRange dr);
void back();
void forward();
void action(bool redo, NavigationEvent); // redo/undo the event
private:
Tab *tab;
// block observer when undo/redo is active
bool block;
// current state, before an event arrives
int view; // which view is selected
DateRange dr;
RideItem *item;
// have we seen first values for:
bool viewinit, drinit, iteminit;
// the stack
void addToStack(NavigationEvent&); // truncates stack if needed
int stackpointer;
QVector<NavigationEvent> stack;
};

View File

@@ -25,6 +25,7 @@
#include "IntervalTreeView.h"
#include "MainWindow.h"
#include "Colors.h"
#include "NavigationModel.h"
#include <QPaintEvent>
@@ -88,6 +89,12 @@ Tab::Tab(Context *context) : QWidget(context->mainWindow), context(context)
chartSettings->setMaximumHeight(600);
chartSettings->hide();
// navigation model after main items as it uses the observer
// pattern on views etc, so they need to be created first
// but we need to get setup before ride selection happens
// below, so we can observe the iniital ride select
nav = new NavigationModel(this);
// cpx aggregate cache check
connect(context,SIGNAL(rideSelected(RideItem*)), this, SLOT(rideSelected(RideItem*)));
@@ -100,9 +107,11 @@ Tab::Tab(Context *context) : QWidget(context->mainWindow), context(context)
break;
}
}
// otherwise just the latest
if (context->currentRideItem() == NULL && context->athlete->rideCache->rides().count() != 0)
if (context->currentRideItem() == NULL && context->athlete->rideCache->rides().count() != 0) {
context->athlete->selectRideFile(context->athlete->rideCache->rides().last()->fileName);
}
}
Tab::~Tab()
@@ -112,6 +121,7 @@ Tab::~Tab()
delete trainView;
delete diaryView;
delete views;
delete nav;
}
RideNavigator *
@@ -174,6 +184,8 @@ Tab::view(int index)
void
Tab::selectView(int index)
{
emit viewChanged(index);
// first we deselect the current
view(views->currentIndex())->setSelected(false);
@@ -187,6 +199,8 @@ Tab::selectView(int index)
void
Tab::rideSelected(RideItem*)
{
emit rideItemSelected(context->ride);
// update the ride property on all widgets
// to let them know they need to replot new
// selected ride (now the tree is up to date)

View File

@@ -26,6 +26,7 @@ class RideNavigator;
class MainWindow;
class ProgressLine;
class QPaintEvent;
class NavigationModel;
class Tab: public QWidget
{
@@ -41,15 +42,21 @@ class Tab: public QWidget
int currentView() { return views->currentIndex(); }
TabView *view(int index);
NavigationModel *nav; // back/forward for this tab
RideNavigator *rideNavigator(); // to get logical headings
protected:
friend class ::MainWindow;
friend class ::NavigationModel;
Context *context;
signals:
void viewChanged(int);
void rideItemSelected(RideItem*);
void dateRangeSelected(DateRange);
public slots:
void rideSelected(RideItem*);

View File

@@ -117,7 +117,7 @@ DiaryView::setRide(RideItem*ride)
void
DiaryView::dateRangeChanged(DateRange dr)
{
context->dr_ = dr;
//context->notifyDateRangeChanged(dr); // diary view deprecated and not part of navigation model
page()->setProperty("dateRange", QVariant::fromValue<DateRange>(dr));
}
@@ -130,18 +130,18 @@ DiaryView::isBlank()
HomeView::HomeView(Context *context, QStackedWidget *controls) : TabView(context, VIEW_HOME)
{
LTMSidebar *s = new LTMSidebar(context);
sidebar = new LTMSidebar(context);
HomeWindow *h = new HomeWindow(context, "home", "Trends");
controls->addWidget(h->controls());
controls->setCurrentIndex(0);
BlankStateHomePage *b = new BlankStateHomePage(context);
setSidebar(s);
setSidebar(sidebar);
setPage(h);
setBlank(b);
setBottom(new ComparePane(context, this, ComparePane::season));
connect(s, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateRangeChanged(DateRange)));
connect(sidebar, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateRangeChanged(DateRange)));
connect(this, SIGNAL(onSelectionChanged()), this, SLOT(justSelected()));
connect(bottomSplitter(), SIGNAL(compareChanged(bool)), this, SLOT(compareChanged(bool)));
connect(bottomSplitter(), SIGNAL(compareClear()), bottom(), SLOT(clear()));
@@ -161,7 +161,8 @@ HomeView::compareChanged(bool state)
void
HomeView::dateRangeChanged(DateRange dr)
{
context->dr_ = dr;
emit dateChanged(dr);
context->notifyDateRangeChanged(dr);
page()->setProperty("dateRange", QVariant::fromValue<DateRange>(dr));
}
bool
@@ -176,7 +177,7 @@ HomeView::justSelected()
{
if (isSelected()) {
// force date range refresh
static_cast<LTMSidebar*>(sidebar())->dateRangeTreeWidgetSelectionChanged();
static_cast<LTMSidebar*>(sidebar)->dateRangeTreeWidgetSelectionChanged();
}
}

View File

@@ -92,6 +92,7 @@ private slots:
void onAutoHideChanged(bool enabled);
};
class LTMSidebar;
class HomeView : public TabView
{
Q_OBJECT
@@ -101,6 +102,10 @@ class HomeView : public TabView
HomeView(Context *context, QStackedWidget *controls);
~HomeView();
LTMSidebar *sidebar;
signals:
void dateChanged(DateRange);
public slots:

View File

@@ -139,6 +139,8 @@
<file>images/toolbar/popbutton.png</file>
<file>images/toolbar/flipbutton.png</file>
<file>images/splashscreen.png</file>
<file>images/mac/back.png</file>
<file>images/mac/forward.png</file>
<file>images/mac/compose.png</file>
<file>images/mac/download.png</file>
<file>images/mac/share.png</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -723,7 +723,7 @@ HEADERS += Gui/AboutDialog.h Gui/AddIntervalDialog.h Gui/AnalysisSidebar.h Gui/C
Gui/SaveDialogs.h Gui/SearchBox.h Gui/SearchFilterBox.h Gui/SolveCPDialog.h Gui/Tab.h Gui/TabView.h Gui/ToolsRhoEstimator.h \
Gui/Views.h Gui/BatchExportDialog.h Gui/DownloadRideDialog.h Gui/ManualRideDialog.h Gui/NewMainWindow.h Gui/NewSideBar.h \
Gui/MergeActivityWizard.h Gui/RideImportWizard.h Gui/SplitActivityWizard.h Gui/SolverDisplay.h Gui/MetricSelect.h \
Gui/AddChartWizard.h
Gui/AddChartWizard.h Gui/NavigationModel.h
# metrics and models
HEADERS += Metrics/Banister.h Metrics/CPSolver.h Metrics/Estimator.h Metrics/ExtendedCriticalPower.h Metrics/HrZones.h Metrics/PaceZones.h \
@@ -818,7 +818,7 @@ SOURCES += Gui/AboutDialog.cpp Gui/AddIntervalDialog.cpp Gui/AnalysisSidebar.cpp
Gui/SearchBox.cpp Gui/SearchFilterBox.cpp Gui/SolveCPDialog.cpp Gui/Tab.cpp Gui/TabView.cpp Gui/ToolsRhoEstimator.cpp Gui/Views.cpp \
Gui/BatchExportDialog.cpp Gui/DownloadRideDialog.cpp Gui/ManualRideDialog.cpp Gui/EditUserMetricDialog.cpp Gui/NewMainWindow.cpp Gui/NewSideBar.cpp \
Gui/MergeActivityWizard.cpp Gui/RideImportWizard.cpp Gui/SplitActivityWizard.cpp Gui/SolverDisplay.cpp Gui/MetricSelect.cpp \
Gui/AddChartWizard.cpp
Gui/AddChartWizard.cpp Gui/NavigationModel.cpp
## Models and Metrics
SOURCES += Metrics/aBikeScore.cpp Metrics/aCoggan.cpp Metrics/AerobicDecoupling.cpp Metrics/Banister.cpp Metrics/BasicRideMetrics.cpp \