From 64da909243b9e2b7c9d25ab57ceede298bbbcb5c Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Sun, 5 Jul 2020 16:38:01 +0100 Subject: [PATCH] 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 --- src/Charts/ChartBar.cpp | 9 +- src/Core/Context.cpp | 1 + src/Core/Context.h | 5 + src/Core/RideCache.h | 7 +- src/Core/RideItem.cpp | 4 + src/Core/RideItem.h | 5 +- src/Core/TimeUtils.cpp | 1 + src/Core/TimeUtils.h | 11 ++ src/Gui/LTMSidebar.cpp | 27 ++++ src/Gui/LTMSidebar.h | 1 + src/Gui/MainWindow.cpp | 31 +++- src/Gui/MainWindow.h | 8 +- src/Gui/NavigationModel.cpp | 209 +++++++++++++++++++++++++++ src/Gui/NavigationModel.h | 94 ++++++++++++ src/Gui/Tab.cpp | 16 +- src/Gui/Tab.h | 7 + src/Gui/Views.cpp | 13 +- src/Gui/Views.h | 5 + src/Resources/application.qrc | 2 + src/Resources/images/mac/back.png | Bin 0 -> 4661 bytes src/Resources/images/mac/forward.png | Bin 0 -> 4542 bytes src/src.pro | 4 +- 22 files changed, 441 insertions(+), 19 deletions(-) create mode 100644 src/Gui/NavigationModel.cpp create mode 100644 src/Gui/NavigationModel.h create mode 100644 src/Resources/images/mac/back.png create mode 100644 src/Resources/images/mac/forward.png diff --git a/src/Charts/ChartBar.cpp b/src/Charts/ChartBar.cpp index bf256fa71..5dab0211a 100644 --- a/src/Charts/ChartBar.cpp +++ b/src/Charts/ChartBar.cpp @@ -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())); diff --git a/src/Core/Context.cpp b/src/Core/Context.cpp index 2a8da5ab2..c1e5c4a90 100644 --- a/src/Core/Context.cpp +++ b/src/Core/Context.cpp @@ -20,6 +20,7 @@ #include "Athlete.h" #include "RideMetric.h" +#include "NavigationModel.h" #include "UserMetricSettings.h" #include "UserMetricParser.h" #include "DataFilter.h" diff --git a/src/Core/Context.h b/src/Core/Context.h index 39506a106..ab6205993 100644 --- a/src/Core/Context.h +++ b/src/Core/Context.h @@ -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 diff --git a/src/Core/RideCache.h b/src/Core/RideCache.h index 24d67d8a6..305490cd1 100644 --- a/src/Core/RideCache.h +++ b/src/Core/RideCache.h @@ -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 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 rides_, reverse_, delete_, deletelist; RideCacheModel *model_; bool exiting; double progress_; // percent diff --git a/src/Core/RideItem.cpp b/src/Core/RideItem.cpp index 642b07f08..32eb66dc0 100644 --- a/src/Core/RideItem.cpp +++ b/src/Core/RideItem.cpp @@ -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:"< +#include #include #include #include @@ -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) { diff --git a/src/Gui/LTMSidebar.cpp b/src/Gui/LTMSidebar.cpp index 8325e802f..e2b891f27 100644 --- a/src/Gui/LTMSidebar.cpp +++ b/src/Gui/LTMSidebar.cpp @@ -323,6 +323,33 @@ LTMSidebar::configChanged(qint32) * Selections Made *----------------------------------------------------------------------*/ +void +LTMSidebar::selectDateRange(DateRange dr) +{ + for(int i=0; iseasons.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; jselectionModel()->clearSelection(); // clear selection + allDateRanges->child(i)->child(j)->setSelected(true); // select ours + return; + } + } + } + } + } +} + void LTMSidebar::dateRangeTreeWidgetSelectionChanged() { diff --git a/src/Gui/LTMSidebar.h b/src/Gui/LTMSidebar.h index b931bd9ee..689cc6295 100644 --- a/src/Gui/LTMSidebar.h +++ b/src/Gui/LTMSidebar.h @@ -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(); diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp index 0610daa0e..3e7fd3af6 100644 --- a/src/Gui/MainWindow.cpp +++ b/src/Gui/MainWindow.cpp @@ -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; diff --git a/src/Gui/MainWindow.h b/src/Gui/MainWindow.h index 4948c692a..83d79dd98 100644 --- a/src/Gui/MainWindow.h +++ b/src/Gui/MainWindow.h @@ -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; diff --git a/src/Gui/NavigationModel.cpp b/src/Gui/NavigationModel.cpp new file mode 100644 index 000000000..da4aec0fc --- /dev/null +++ b/src/Gui/NavigationModel.cpp @@ -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(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() : event.before.value(); + + // 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() : event.before.value(); + static_cast(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]); + } + } +} diff --git a/src/Gui/NavigationModel.h b/src/Gui/NavigationModel.h new file mode 100644 index 000000000..6d37d5f12 --- /dev/null +++ b/src/Gui/NavigationModel.h @@ -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 +#include +#include +#include + +#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 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 stack; +}; diff --git a/src/Gui/Tab.cpp b/src/Gui/Tab.cpp index 7eb79b557..1d66473e8 100644 --- a/src/Gui/Tab.cpp +++ b/src/Gui/Tab.cpp @@ -25,6 +25,7 @@ #include "IntervalTreeView.h" #include "MainWindow.h" #include "Colors.h" +#include "NavigationModel.h" #include @@ -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) diff --git a/src/Gui/Tab.h b/src/Gui/Tab.h index 36b0c0612..7638aae54 100644 --- a/src/Gui/Tab.h +++ b/src/Gui/Tab.h @@ -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*); diff --git a/src/Gui/Views.cpp b/src/Gui/Views.cpp index 5745f4d3a..2714508aa 100644 --- a/src/Gui/Views.cpp +++ b/src/Gui/Views.cpp @@ -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(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(dr)); } bool @@ -176,7 +177,7 @@ HomeView::justSelected() { if (isSelected()) { // force date range refresh - static_cast(sidebar())->dateRangeTreeWidgetSelectionChanged(); + static_cast(sidebar)->dateRangeTreeWidgetSelectionChanged(); } } diff --git a/src/Gui/Views.h b/src/Gui/Views.h index 66f5cabf7..048a07e19 100644 --- a/src/Gui/Views.h +++ b/src/Gui/Views.h @@ -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: diff --git a/src/Resources/application.qrc b/src/Resources/application.qrc index 468f6a68d..9761c95d4 100644 --- a/src/Resources/application.qrc +++ b/src/Resources/application.qrc @@ -139,6 +139,8 @@ images/toolbar/popbutton.png images/toolbar/flipbutton.png images/splashscreen.png + images/mac/back.png + images/mac/forward.png images/mac/compose.png images/mac/download.png images/mac/share.png diff --git a/src/Resources/images/mac/back.png b/src/Resources/images/mac/back.png new file mode 100644 index 0000000000000000000000000000000000000000..9b043b8671a184238509d83491d64232dc2d29f8 GIT binary patch literal 4661 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 literal 0 HcmV?d00001 diff --git a/src/Resources/images/mac/forward.png b/src/Resources/images/mac/forward.png new file mode 100644 index 0000000000000000000000000000000000000000..1fba38c099df0e089470cdf45fe5a1c15800c277 GIT binary patch literal 4542 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 literal 0 HcmV?d00001 diff --git a/src/src.pro b/src/src.pro index 1f8a71271..0e92db2a8 100644 --- a/src/src.pro +++ b/src/src.pro @@ -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 \