From f4eb4e3476af4a802f43081612eebc10c8478543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 24 May 2018 17:42:36 +0200 Subject: [PATCH] [apps/stats] Abstract multiple data view and controller --- apps/statistics/Makefile | 2 + apps/statistics/app.h | 1 + apps/statistics/histogram_controller.cpp | 156 +++++------------- apps/statistics/histogram_controller.h | 23 +-- apps/statistics/multiple_data_view.cpp | 102 ++++++++++++ apps/statistics/multiple_data_view.h | 47 ++++++ .../multiple_data_view_controller.cpp | 109 ++++++++++++ .../multiple_data_view_controller.h | 41 +++++ apps/statistics/multiple_histograms_view.cpp | 106 +----------- apps/statistics/multiple_histograms_view.h | 25 +-- 10 files changed, 366 insertions(+), 246 deletions(-) create mode 100644 apps/statistics/multiple_data_view.cpp create mode 100644 apps/statistics/multiple_data_view.h create mode 100644 apps/statistics/multiple_data_view_controller.cpp create mode 100644 apps/statistics/multiple_data_view_controller.h diff --git a/apps/statistics/Makefile b/apps/statistics/Makefile index 5d8a5adf7..99a2f0115 100644 --- a/apps/statistics/Makefile +++ b/apps/statistics/Makefile @@ -12,6 +12,8 @@ app_objs += $(addprefix apps/statistics/,\ histogram_controller.o\ histogram_parameter_controller.o\ histogram_view.o\ + multiple_data_view.o\ + multiple_data_view_controller.o\ multiple_histograms_view.o\ store.o\ store_controller.o\ diff --git a/apps/statistics/app.h b/apps/statistics/app.h index 791f809ed..5790b9bcf 100644 --- a/apps/statistics/app.h +++ b/apps/statistics/app.h @@ -38,6 +38,7 @@ public: uint32_t m_rangeVersion; int m_selectedHistogramBarIndex; BoxView::Quantile m_selectedBoxQuantile; + // TODO add selected Series for both histogram and box }; private: App(Container * container, Snapshot * snapshot); diff --git a/apps/statistics/histogram_controller.cpp b/apps/statistics/histogram_controller.cpp index 95d725885..baacb40d4 100644 --- a/apps/statistics/histogram_controller.cpp +++ b/apps/statistics/histogram_controller.cpp @@ -14,111 +14,41 @@ static inline float min(float x, float y) { return (xy ? x : y); } HistogramController::HistogramController(Responder * parentResponder, ButtonRowController * header, Store * store, uint32_t * storeVersion, uint32_t * barVersion, uint32_t * rangeVersion, int * selectedBarIndex) : - ViewController(parentResponder), + MultipleDataViewController(parentResponder, store, selectedBarIndex), ButtonRowDelegate(header, nullptr), - m_store(store), m_view(this, store), m_storeVersion(storeVersion), m_barVersion(barVersion), m_rangeVersion(rangeVersion), - m_selectedSeries(-1), - m_selectedBarIndex(selectedBarIndex), m_histogramParameterController(nullptr, store) { } +void HistogramController::setCurrentDrawnSeries(int series) { + initYRangeParameters(series); +} + StackViewController * HistogramController::stackController() { StackViewController * stack = (StackViewController *)(parentResponder()->parentResponder()->parentResponder()); return stack; } -void HistogramController::setCurrentDrawnSeries(int series) { - initYRangeParameters(series); -} - -bool HistogramController::isEmpty() const { - return m_store->isEmpty(); -} - -I18n::Message HistogramController::emptyMessage() { - return I18n::Message::NoDataToPlot; -} - -Responder * HistogramController::defaultController() { - return tabController(); -} - const char * HistogramController::title() { return I18n::translate(I18n::Message::HistogramTab); } -void HistogramController::viewWillAppear() { - m_view.setDisplayBanner(true); - if (m_selectedSeries < 0) { - m_selectedSeries = m_view.seriesOfSubviewAtIndex(0); - m_view.selectHistogram(m_selectedSeries); - } - reloadBannerView(); - m_view.reload(); -} - bool HistogramController::handleEvent(Ion::Events::Event event) { - assert(m_selectedSeries >= 0); - if (event == Ion::Events::Down) { - int currentSelectedSubview = m_view.indexOfSubviewAtSeries(m_selectedSeries); - if (currentSelectedSubview < m_view.numberOfSubviews() - 2) { - m_view.deselectHistogram(m_selectedSeries); - m_selectedSeries = m_view.seriesOfSubviewAtIndex(currentSelectedSubview+1); - *m_selectedBarIndex = 0; - m_view.selectHistogram(m_selectedSeries); - reloadBannerView(); - m_view.reload(); - app()->setFirstResponder(this); - return true; - } - return false; - } - if (event == Ion::Events::Up) { - int currentSelectedSubview = m_view.indexOfSubviewAtSeries(m_selectedSeries); - if (currentSelectedSubview > 0) { - m_view.deselectHistogram(m_selectedSeries); - assert(currentSelectedSubview > 0); - m_selectedSeries = m_view.seriesOfSubviewAtIndex(currentSelectedSubview-1); - m_view.selectHistogram(m_selectedSeries); - *m_selectedBarIndex = 0; - app()->setFirstResponder(this); - } else { - app()->setFirstResponder(tabController()); - } - reloadBannerView(); - m_view.reload(); - return true; - } - if (m_selectedSeries >= 0 && (event == Ion::Events::Left || event == Ion::Events::Right)) { - int direction = event == Ion::Events::Left ? -1 : 1; - if (moveSelection(direction)) { - reloadBannerView(); - m_view.reload(); - } - return true; - } + assert(selectedSeries() >= 0); if (event == Ion::Events::OK) { stackController()->push(histogramParameterController()); return true; } - return false; + return MultipleDataViewController::handleEvent(event); } void HistogramController::didBecomeFirstResponder() { - m_view.setDisplayBanner(true); - if (m_selectedSeries < 0 || m_store->sumOfOccurrences(m_selectedSeries) == 0) { - if (m_selectedSeries >= 0) { - m_view.deselectHistogram(m_selectedSeries); - } - m_selectedSeries = m_view.seriesOfSubviewAtIndex(0); - m_view.selectHistogram(m_selectedSeries); - m_view.reload(); - } + MultipleDataViewController::didBecomeFirstResponder(); + uint32_t storeChecksum = m_store->storeChecksum(); if (*m_storeVersion != storeChecksum) { *m_storeVersion = storeChecksum; @@ -136,19 +66,17 @@ void HistogramController::didBecomeFirstResponder() { initBarSelection(); reloadBannerView(); } - m_view.histogramViewAtIndex(m_selectedSeries)->setHighlight(m_store->startOfBarAtIndex(m_selectedSeries, *m_selectedBarIndex), m_store->endOfBarAtIndex(m_selectedSeries, *m_selectedBarIndex)); + HistogramView * selectedHistogramView = static_cast(m_view.dataViewAtIndex(selectedSeries())); + selectedHistogramView->setHighlight(m_store->startOfBarAtIndex(selectedSeries(), *m_selectedBarIndex), m_store->endOfBarAtIndex(selectedSeries(), *m_selectedBarIndex)); } void HistogramController::willExitResponderChain(Responder * nextFirstResponder) { if (nextFirstResponder == nullptr || nextFirstResponder == tabController()) { - if (m_selectedSeries >= 0) { - m_view.histogramViewAtIndex(m_selectedSeries)->selectMainView(false); - m_view.histogramViewAtIndex(m_selectedSeries)->setForceOkDisplay(false); - m_selectedSeries = -1; - m_view.setDisplayBanner(false); + if (selectedSeries() >= 0) { + m_view.dataViewAtIndex(selectedSeries())->setForceOkDisplay(false); } - m_view.reload(); } + MultipleDataViewController::willExitResponderChain(nextFirstResponder); } Responder * HistogramController::tabController() const { @@ -156,7 +84,7 @@ Responder * HistogramController::tabController() const { } void HistogramController::reloadBannerView() { - if (m_selectedSeries < 0) { + if (selectedSeries() < 0) { return; } char buffer[k_maxNumberOfCharacters+ PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits)*2]; @@ -169,16 +97,16 @@ void HistogramController::reloadBannerView() { numberOfChar += legendLength; // Add lower bound - if (m_selectedSeries >= 0) { - double lowerBound = m_store->startOfBarAtIndex(m_selectedSeries, *m_selectedBarIndex); + if (selectedSeries() >= 0) { + double lowerBound = m_store->startOfBarAtIndex(selectedSeries(), *m_selectedBarIndex); numberOfChar += PrintFloat::convertFloatToText(lowerBound, buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); } buffer[numberOfChar++] = ';'; // Add upper bound - if (m_selectedSeries >= 0) { - double upperBound = m_store->endOfBarAtIndex(m_selectedSeries, *m_selectedBarIndex); + if (selectedSeries() >= 0) { + double upperBound = m_store->endOfBarAtIndex(selectedSeries(), *m_selectedBarIndex); numberOfChar += PrintFloat::convertFloatToText(upperBound, buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); } @@ -189,7 +117,7 @@ void HistogramController::reloadBannerView() { buffer[numberOfChar++] = ' '; } buffer[k_maxIntervalLegendLength] = 0; - m_view.bannerView()->setLegendAtIndex(buffer, 1); + m_view.editableBannerView()->setLegendAtIndex(buffer, 1); // Add Size Data numberOfChar = 0; @@ -198,8 +126,8 @@ void HistogramController::reloadBannerView() { strlcpy(buffer, legend, legendLength+1); numberOfChar += legendLength; double size = 0; - if (m_selectedSeries >= 0) { - size = m_store->heightOfBarAtIndex(m_selectedSeries, *m_selectedBarIndex); + if (selectedSeries() >= 0) { + size = m_store->heightOfBarAtIndex(selectedSeries(), *m_selectedBarIndex); numberOfChar += PrintFloat::convertFloatToText(size, buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); } // Padding @@ -207,7 +135,7 @@ void HistogramController::reloadBannerView() { buffer[numberOfChar++] = ' '; } buffer[k_maxLegendLength] = 0; - m_view.bannerView()->setLegendAtIndex(buffer, 3); + m_view.editableBannerView()->setLegendAtIndex(buffer, 3); // Add Frequency Data numberOfChar = 0; @@ -215,8 +143,8 @@ void HistogramController::reloadBannerView() { legendLength = strlen(legend); strlcpy(buffer, legend, legendLength+1); numberOfChar += legendLength; - if (m_selectedSeries >= 0) { - double frequency = size/m_store->sumOfOccurrences(m_selectedSeries); + if (selectedSeries() >= 0) { + double frequency = size/m_store->sumOfOccurrences(selectedSeries()); numberOfChar += PrintFloat::convertFloatToText(frequency, buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); } // Padding @@ -224,31 +152,31 @@ void HistogramController::reloadBannerView() { buffer[numberOfChar++] = ' '; } buffer[k_maxLegendLength] = 0; - m_view.bannerView()->setLegendAtIndex(buffer, 5); + m_view.editableBannerView()->setLegendAtIndex(buffer, 5); } -bool HistogramController::moveSelection(int deltaIndex) { +bool HistogramController::moveSelectionHorizontally(int deltaIndex) { int newSelectedBarIndex = *m_selectedBarIndex; do { newSelectedBarIndex+=deltaIndex; - } while (m_store->heightOfBarAtIndex(m_selectedSeries, newSelectedBarIndex) == 0 + } while (m_store->heightOfBarAtIndex(selectedSeries(), newSelectedBarIndex) == 0 && newSelectedBarIndex >= 0 - && newSelectedBarIndex < m_store->numberOfBars(m_selectedSeries)); + && newSelectedBarIndex < m_store->numberOfBars(selectedSeries())); if (newSelectedBarIndex >= 0 - && newSelectedBarIndex < m_store->numberOfBars(m_selectedSeries) + && newSelectedBarIndex < m_store->numberOfBars(selectedSeries()) && *m_selectedBarIndex != newSelectedBarIndex) { *m_selectedBarIndex = newSelectedBarIndex; - m_view.histogramViewAtIndex(m_selectedSeries)->setHighlight(m_store->startOfBarAtIndex(m_selectedSeries, *m_selectedBarIndex), m_store->endOfBarAtIndex(m_selectedSeries, *m_selectedBarIndex)); - m_store->scrollToSelectedBarIndex(m_selectedSeries, *m_selectedBarIndex); + m_view.dataViewAtIndex(selectedSeries())->setHighlight(m_store->startOfBarAtIndex(selectedSeries(), *m_selectedBarIndex), m_store->endOfBarAtIndex(selectedSeries(), *m_selectedBarIndex)); + m_store->scrollToSelectedBarIndex(selectedSeries(), *m_selectedBarIndex); return true; } return false; } void HistogramController::initRangeParameters() { - assert(m_selectedSeries >= 0 && m_store->sumOfOccurrences(m_selectedSeries) > 0); + assert(selectedSeries() >= 0 && m_store->sumOfOccurrences(selectedSeries()) > 0); float minValue = m_store->firstDrawnBarAbscissa(); float maxValue = -FLT_MAX; for (int i = 0; i < Store::k_numberOfSeries; i ++) { @@ -270,7 +198,7 @@ void HistogramController::initRangeParameters() { m_store->setXMin(xMin - Store::k_displayLeftMarginRatio*(xMax-xMin)); m_store->setXMax(xMax + Store::k_displayRightMarginRatio*(xMax-xMin)); - initYRangeParameters(m_selectedSeries); + initYRangeParameters(selectedSeries()); } void HistogramController::initYRangeParameters(int series) { @@ -297,11 +225,11 @@ void HistogramController::initYRangeParameters(int series) { * yMin = -k_bottomMargin/ratioFloatPixel = yMax*k_bottomMargin/(k_bottomMargin - viewHeight) * */ - m_store->setYMin(m_store->yMax()*(float)Store::k_bottomMargin/((float)Store::k_bottomMargin - m_view.histogramViewAtIndex(series)->bounds().height())); + m_store->setYMin(m_store->yMax()*(float)Store::k_bottomMargin/((float)Store::k_bottomMargin - m_view.dataViewAtIndex(series)->bounds().height())); } void HistogramController::initBarParameters() { - assert(m_selectedSeries >= 0 && m_store->sumOfOccurrences(m_selectedSeries) > 0); + assert(selectedSeries() >= 0 && m_store->sumOfOccurrences(selectedSeries()) > 0); float minValue = FLT_MAX; float maxValue = -FLT_MAX; for (int i = 0; i < Store::k_numberOfSeries; i ++) { @@ -320,20 +248,20 @@ void HistogramController::initBarParameters() { } void HistogramController::initBarSelection() { - assert(m_selectedSeries >= 0 && m_store->sumOfOccurrences(m_selectedSeries) > 0); + assert(selectedSeries() >= 0 && m_store->sumOfOccurrences(selectedSeries()) > 0); *m_selectedBarIndex = 0; - while ((m_store->heightOfBarAtIndex(m_selectedSeries, *m_selectedBarIndex) == 0 || - m_store->startOfBarAtIndex(m_selectedSeries, *m_selectedBarIndex) < m_store->firstDrawnBarAbscissa()) && *m_selectedBarIndex < m_store->numberOfBars(m_selectedSeries)) { + while ((m_store->heightOfBarAtIndex(selectedSeries(), *m_selectedBarIndex) == 0 || + m_store->startOfBarAtIndex(selectedSeries(), *m_selectedBarIndex) < m_store->firstDrawnBarAbscissa()) && *m_selectedBarIndex < m_store->numberOfBars(selectedSeries())) { *m_selectedBarIndex = *m_selectedBarIndex+1; } - if (*m_selectedBarIndex >= m_store->numberOfBars(m_selectedSeries)) { + if (*m_selectedBarIndex >= m_store->numberOfBars(selectedSeries())) { /* No bar is after m_firstDrawnBarAbscissa, so we select the first bar */ *m_selectedBarIndex = 0; - while (m_store->heightOfBarAtIndex(m_selectedSeries, *m_selectedBarIndex) == 0 && *m_selectedBarIndex < m_store->numberOfBars(m_selectedSeries)) { + while (m_store->heightOfBarAtIndex(selectedSeries(), *m_selectedBarIndex) == 0 && *m_selectedBarIndex < m_store->numberOfBars(selectedSeries())) { *m_selectedBarIndex = *m_selectedBarIndex+1; } } - m_store->scrollToSelectedBarIndex(m_selectedSeries, *m_selectedBarIndex); + m_store->scrollToSelectedBarIndex(selectedSeries(), *m_selectedBarIndex); } } diff --git a/apps/statistics/histogram_controller.h b/apps/statistics/histogram_controller.h index db12fa8b1..826075cc9 100644 --- a/apps/statistics/histogram_controller.h +++ b/apps/statistics/histogram_controller.h @@ -3,27 +3,23 @@ #include #include "store.h" +#include "multiple_data_view_controller.h" #include "multiple_histograms_view.h" namespace Statistics { -class HistogramController : public ViewController, public ButtonRowDelegate, public AlternateEmptyViewDelegate { +class HistogramController : public MultipleDataViewController, public ButtonRowDelegate { public: HistogramController(Responder * parentResponder, ButtonRowController * header, Store * store, uint32_t * m_storeVersion, uint32_t * m_barVersion, uint32_t * m_rangeVersion, int * m_selectedBarIndex); - StackViewController * stackController(); + HistogramParameterController * histogramParameterController() { return &m_histogramParameterController; } void setCurrentDrawnSeries(int series); - - // AlternateEmptyViewDelegate - bool isEmpty() const override; - I18n::Message emptyMessage() override; - Responder * defaultController() override; + StackViewController * stackController(); // ViewController const char * title() override; - View * view() override { return &m_view; } - void viewWillAppear() override; + MultipleDataView * multipleDataView() override { return &m_view; } // Responder bool handleEvent(Ion::Events::Event event) override; @@ -34,21 +30,18 @@ private: constexpr static int k_maxIntervalLegendLength = 33; constexpr static int k_maxLegendLength = 13; constexpr static int k_maxNumberOfCharacters = 30; - Responder * tabController() const; - void reloadBannerView(); + Responder * tabController() const override; + void reloadBannerView() override; void initRangeParameters(); void initYRangeParameters(int series); void initBarParameters(); void initBarSelection(); // return true if the window has scrolled - bool moveSelection(int deltaIndex); - Store * m_store; + bool moveSelectionHorizontally(int deltaIndex) override; MultipleHistogramsView m_view; uint32_t * m_storeVersion; uint32_t * m_barVersion; uint32_t * m_rangeVersion; - int m_selectedSeries; - int * m_selectedBarIndex; HistogramParameterController m_histogramParameterController; }; diff --git a/apps/statistics/multiple_data_view.cpp b/apps/statistics/multiple_data_view.cpp new file mode 100644 index 000000000..c07f49685 --- /dev/null +++ b/apps/statistics/multiple_data_view.cpp @@ -0,0 +1,102 @@ +#include "multiple_data_view.h" +#include + +using namespace Shared; + +namespace Statistics { + +void MultipleDataView::reload() { + layoutSubviews(); + for (int i = 0; i < Store::k_numberOfSeries; i++) { + dataViewAtIndex(i)->reload(); + } +} + +int MultipleDataView::indexOfSubviewAtSeries(int series) { + int displayedSubviewIndex = 0; + for (int i = 0; i < Store::k_numberOfSeries; i++) { + if (!m_store->seriesIsEmpty(i)) { + if (i == series) { + return displayedSubviewIndex; + } + displayedSubviewIndex++; + } else if (i == series) { + return -1; + } + } + assert(false); + return -1; +} + +void MultipleDataView::selectDataView(int index) { + changeDataViewSelection(index, true); +} + +void MultipleDataView::deselectDataView(int index) { + changeDataViewSelection(index, false); +} + +void MultipleDataView::drawRect(KDContext * ctx, KDRect rect) const { + if (!m_displayBanner) { + ctx->fillRect(bannerFrame(), KDColorWhite); + } +} + +int MultipleDataView::numberOfSubviews() const { + int result = m_store->numberOfNonEmptySeries(); + assert(result <= Store::k_numberOfSeries); + return result + 1; // +1 for the banner view +} + +KDRect MultipleDataView::bannerFrame() const { + KDCoordinate bannerHeight = bannerView()->minimalSizeForOptimalDisplay().height(); + KDRect frame = KDRect(0, bounds().height() - bannerHeight, bounds().width(), bannerHeight); + return frame; +} + +View * MultipleDataView::subviewAtIndex(int index) { + if (index == numberOfSubviews() -1) { + return editableBannerView(); + } + int seriesIndex = 0; + int nonEmptySeriesIndex = -1; + while (seriesIndex < Store::k_numberOfSeries) { + if (!m_store->seriesIsEmpty(seriesIndex)) { + nonEmptySeriesIndex++; + if (nonEmptySeriesIndex == index) { + return dataViewAtIndex(seriesIndex); + } + } + seriesIndex++; + } + assert(false); + return nullptr; +} + +void MultipleDataView::layoutSubviews() { + int numberDataSubviews = m_store->numberOfNonEmptySeries(); + assert(numberDataSubviews > 0); + KDCoordinate bannerHeight = bannerFrame().height(); + KDCoordinate subviewHeight = (bounds().height() - bannerHeight)/numberDataSubviews; + int displayedSubviewIndex = 0; + for (int i = 0; i < Store::k_numberOfSeries; i++) { + if (!m_store->seriesIsEmpty(i)) { + CurveView * dataView = dataViewAtIndex(i); + KDRect frame = KDRect(0, displayedSubviewIndex*subviewHeight, bounds().width(), subviewHeight); + dataView->setFrame(frame); + displayedSubviewIndex++; + } + } + if (m_displayBanner) { + editableBannerView()->setFrame(bannerFrame()); + } else { + KDRect frame = KDRect(0, bounds().height() - bannerHeight, bounds().width(), 0); + editableBannerView()->setFrame(frame); + } +} + +void MultipleDataView::changeDataViewSelection(int index, bool select) { + dataViewAtIndex(index)->selectMainView(select); +} + +} diff --git a/apps/statistics/multiple_data_view.h b/apps/statistics/multiple_data_view.h new file mode 100644 index 000000000..c53b52cde --- /dev/null +++ b/apps/statistics/multiple_data_view.h @@ -0,0 +1,47 @@ +#ifndef STATISTICS_MULTIPLE_DATA_VIEW_H +#define STATISTICS_MULTIPLE_DATA_VIEW_H + +#include +#include "store.h" +#include "../shared/banner_view.h" +#include "../shared/curve_view.h" + +namespace Statistics { + +class MultipleDataView : public View { +public: + MultipleDataView(Store * store) : + m_displayBanner(false), + m_store(store) + {} + // Data views + void selectDataView(int index); + void deselectDataView(int index); + virtual Shared::CurveView * dataViewAtIndex(int index) = 0; + Shared::BannerView * editableBannerView() { return const_cast(bannerView()); } + + // Index/series + int indexOfSubviewAtSeries(int series); + virtual int seriesOfSubviewAtIndex(int index) = 0; + + // Display + void setDisplayBanner(bool display) { m_displayBanner = display; } + void reload(); + + // View + int numberOfSubviews() const override; +protected: + virtual const Shared::BannerView * bannerView() const = 0; + void layoutSubviews() override; + View * subviewAtIndex(int index) override; + virtual void changeDataViewSelection(int index, bool select); +private: + void drawRect(KDContext * ctx, KDRect rect) const override; + KDRect bannerFrame() const; + bool m_displayBanner; + Store * m_store; +}; + +} + +#endif diff --git a/apps/statistics/multiple_data_view_controller.cpp b/apps/statistics/multiple_data_view_controller.cpp new file mode 100644 index 000000000..02c2e9269 --- /dev/null +++ b/apps/statistics/multiple_data_view_controller.cpp @@ -0,0 +1,109 @@ +#include "multiple_data_view_controller.h" +#include "../i18n.h" +#include + +using namespace Shared; + +namespace Statistics { + +MultipleDataViewController::MultipleDataViewController(Responder * parentResponder, Store * store, int * selectedBarIndex) : + ViewController(parentResponder), + m_store(store), + m_selectedSeries(-1), + m_selectedBarIndex(selectedBarIndex) +{ +} + +MultipleDataView * MultipleDataViewController::multipleDataView() { + return static_cast(view()); +} + +bool MultipleDataViewController::isEmpty() const { + return m_store->isEmpty(); +} + +I18n::Message MultipleDataViewController::emptyMessage() { + return I18n::Message::NoDataToPlot; +} + +Responder * MultipleDataViewController::defaultController() { + return tabController(); +} + +void MultipleDataViewController::viewWillAppear() { + multipleDataView()->setDisplayBanner(true); + if (m_selectedSeries < 0) { + m_selectedSeries = multipleDataView()->seriesOfSubviewAtIndex(0); + multipleDataView()->selectDataView(m_selectedSeries); + } + reloadBannerView(); + multipleDataView()->reload(); +} + +bool MultipleDataViewController::handleEvent(Ion::Events::Event event) { + assert(m_selectedSeries >= 0); + if (event == Ion::Events::Down) { + int currentSelectedSubview = multipleDataView()->indexOfSubviewAtSeries(m_selectedSeries); + if (currentSelectedSubview < multipleDataView()->numberOfSubviews() - 2) { + multipleDataView()->deselectDataView(m_selectedSeries); + m_selectedSeries = multipleDataView()->seriesOfSubviewAtIndex(currentSelectedSubview+1); + *m_selectedBarIndex = 0; + multipleDataView()->selectDataView(m_selectedSeries); + reloadBannerView(); + multipleDataView()->reload(); + app()->setFirstResponder(this); + return true; + } + return false; + } + if (event == Ion::Events::Up) { + int currentSelectedSubview = multipleDataView()->indexOfSubviewAtSeries(m_selectedSeries); + if (currentSelectedSubview > 0) { + multipleDataView()->deselectDataView(m_selectedSeries); + assert(currentSelectedSubview > 0); + m_selectedSeries = multipleDataView()->seriesOfSubviewAtIndex(currentSelectedSubview-1); + multipleDataView()->selectDataView(m_selectedSeries); + *m_selectedBarIndex = 0; + app()->setFirstResponder(this); + } else { + app()->setFirstResponder(tabController()); + } + reloadBannerView(); + multipleDataView()->reload(); + return true; + } + if (m_selectedSeries >= 0 && (event == Ion::Events::Left || event == Ion::Events::Right)) { + int direction = event == Ion::Events::Left ? -1 : 1; + if (moveSelectionHorizontally(direction)) { + reloadBannerView(); + multipleDataView()->reload(); + } + return true; + } + return false; +} + +void MultipleDataViewController::didBecomeFirstResponder() { + multipleDataView()->setDisplayBanner(true); + if (m_selectedSeries < 0 || m_store->sumOfOccurrences(m_selectedSeries) == 0) { + if (m_selectedSeries >= 0) { + multipleDataView()->deselectDataView(m_selectedSeries); + } + m_selectedSeries = multipleDataView()->seriesOfSubviewAtIndex(0); + multipleDataView()->selectDataView(m_selectedSeries); + multipleDataView()->reload(); + } +} + +void MultipleDataViewController::willExitResponderChain(Responder * nextFirstResponder) { + if (nextFirstResponder == nullptr || nextFirstResponder == tabController()) { + if (m_selectedSeries >= 0) { + multipleDataView()->dataViewAtIndex(m_selectedSeries)->selectMainView(false); + m_selectedSeries = -1; + multipleDataView()->setDisplayBanner(false); + } + multipleDataView()->reload(); + } +} + +} diff --git a/apps/statistics/multiple_data_view_controller.h b/apps/statistics/multiple_data_view_controller.h new file mode 100644 index 000000000..b8f5041d0 --- /dev/null +++ b/apps/statistics/multiple_data_view_controller.h @@ -0,0 +1,41 @@ +#ifndef STATISTICS_MULTIPLE_DATA_VIEW_CONTROLLER_H +#define STATISTICS_MULTIPLE_DATA_VIEW_CONTROLLER_H + +#include +#include "store.h" +#include "multiple_data_view.h" + +namespace Statistics { + +class MultipleDataViewController : public ViewController, public AlternateEmptyViewDelegate { + +public: + MultipleDataViewController(Responder * parentResponder, Store * store, int * m_selectedBarIndex); + virtual MultipleDataView * multipleDataView() = 0; + int selectedSeries() const { return m_selectedSeries; } + // AlternateEmptyViewDelegate + bool isEmpty() const override; + I18n::Message emptyMessage() override; + Responder * defaultController() override; + + // ViewController + View * view() override { return multipleDataView(); } + void viewWillAppear() override; + + // Responder + bool handleEvent(Ion::Events::Event event) override; + void didBecomeFirstResponder() override; + void willExitResponderChain(Responder * nextFirstResponder) override; +protected: + virtual Responder * tabController() const = 0; + virtual void reloadBannerView() = 0; + virtual bool moveSelectionHorizontally(int deltaIndex) = 0; + Store * m_store; + int m_selectedSeries; + int * m_selectedBarIndex; +}; + +} + + +#endif diff --git a/apps/statistics/multiple_histograms_view.cpp b/apps/statistics/multiple_histograms_view.cpp index b709ae439..ff304d4d7 100644 --- a/apps/statistics/multiple_histograms_view.cpp +++ b/apps/statistics/multiple_histograms_view.cpp @@ -6,31 +6,23 @@ using namespace Shared; namespace Statistics { MultipleHistogramsView::MultipleHistogramsView(HistogramController * controller, Store * store) : + MultipleDataView(store), m_histogramView1(controller, store, 0, nullptr, Palette::Red), m_histogramView2(controller, store, 1, nullptr, Palette::Blue), m_histogramView3(controller, store, 2, nullptr, Palette::Green), // TODO Share colors with stats/store_controller m_bannerView(), - m_okView(), - m_displayBanner(false), - m_store(store) + m_okView() { for (int i = 0; i < Store::k_numberOfSeries; i++) { - HistogramView * histView = histogramViewAtIndex(i); + HistogramView * histView = dataViewAtIndex(i); histView->setDisplayBannerView(false); histView->setDisplayLabels(false); histView->setForceOkDisplay(true); } } -void MultipleHistogramsView::reload() { - layoutSubviews(); - m_histogramView1.reload(); - m_histogramView2.reload(); - m_histogramView3.reload(); -} - -HistogramView * MultipleHistogramsView::histogramViewAtIndex(int index) { +HistogramView * MultipleHistogramsView::dataViewAtIndex(int index) { assert(index >= 0 && index < 3); HistogramView * views[] = {&m_histogramView1, &m_histogramView2, &m_histogramView3}; return views[index]; @@ -41,93 +33,9 @@ int MultipleHistogramsView::seriesOfSubviewAtIndex(int index) { return static_cast(subviewAtIndex(index))->series(); } -int MultipleHistogramsView::indexOfSubviewAtSeries(int series) { - int displayedSubviewIndex = 0; - for (int i = 0; i < 3; i++) { - if (!m_store->seriesIsEmpty(i)) { - if (i == series) { - return displayedSubviewIndex; - } - displayedSubviewIndex++; - } else if (i == series) { - return -1; - } - } - assert(false); - return -1; -} - -void MultipleHistogramsView::selectHistogram(int index) { - changeHistogramSelection(index, true); -} - -void MultipleHistogramsView::deselectHistogram(int index) { - changeHistogramSelection(index, false); -} - -void MultipleHistogramsView::drawRect(KDContext * ctx, KDRect rect) const { - if (!m_displayBanner) { - ctx->fillRect(bannerFrame(), KDColorWhite); - } -} - -int MultipleHistogramsView::numberOfSubviews() const { - int result = m_store->numberOfNonEmptySeries(); - assert(result <= Store::k_numberOfSeries); - return result + 1; // +1 for the banner view -} - -KDRect MultipleHistogramsView::bannerFrame() const { - KDCoordinate bannerHeight = m_bannerView.minimalSizeForOptimalDisplay().height(); - KDRect frame = KDRect(0, bounds().height() - bannerHeight, bounds().width(), bannerHeight); - return frame; -} - -View * MultipleHistogramsView::subviewAtIndex(int index) { - if (index == numberOfSubviews() -1) { - return &m_bannerView; - } - int seriesIndex = 0; - int nonEmptySeriesIndex = -1; - while (seriesIndex < Store::k_numberOfSeries) { - if (!m_store->seriesIsEmpty(seriesIndex)) { - nonEmptySeriesIndex++; - if (nonEmptySeriesIndex == index) { - return histogramViewAtIndex(seriesIndex); - } - } - seriesIndex++; - } - assert(false); - return nullptr; -} - -void MultipleHistogramsView::layoutSubviews() { - int numberHistogramSubviews = m_store->numberOfNonEmptySeries(); - assert(numberHistogramSubviews > 0); - KDCoordinate bannerHeight = bannerFrame().height(); - KDCoordinate subviewHeight = (bounds().height() - bannerHeight)/numberHistogramSubviews; - int displayedSubviewIndex = 0; - for (int i = 0; i < 3; i++) { - if (!m_store->seriesIsEmpty(i)) { - HistogramView * histView = histogramViewAtIndex(i); - KDRect frame = KDRect(0, displayedSubviewIndex*subviewHeight, bounds().width(), subviewHeight); - histView->setFrame(frame); - displayedSubviewIndex++; - histView->setOkView(displayedSubviewIndex == numberHistogramSubviews ? &m_okView : nullptr); - } - } - if (m_displayBanner) { - m_bannerView.setFrame(bannerFrame()); - } else { - KDRect frame = KDRect(0, bounds().height() - bannerHeight, bounds().width(), 0); - m_bannerView.setFrame(frame); - } -} - -void MultipleHistogramsView::changeHistogramSelection(int index, bool select) { - histogramViewAtIndex(index)->selectMainView(select); - histogramViewAtIndex(index)->setDisplayLabels(select); +void MultipleHistogramsView::changeDataViewSelection(int index, bool select) { + MultipleDataView::changeDataViewSelection(index, select); + dataViewAtIndex(index)->setDisplayLabels(select); } } diff --git a/apps/statistics/multiple_histograms_view.h b/apps/statistics/multiple_histograms_view.h index abbaa7969..1a658e02a 100644 --- a/apps/statistics/multiple_histograms_view.h +++ b/apps/statistics/multiple_histograms_view.h @@ -6,36 +6,25 @@ #include "histogram_view.h" #include "histogram_banner_view.h" #include "histogram_parameter_controller.h" +#include "multiple_data_view.h" #include "../shared/ok_view.h" namespace Statistics { -class MultipleHistogramsView : public View { +class MultipleHistogramsView : public MultipleDataView { public: MultipleHistogramsView(HistogramController * controller, Store * store); - void reload(); - HistogramView * histogramViewAtIndex(int index); - int seriesOfSubviewAtIndex(int index); - int indexOfSubviewAtSeries(int series); - HistogramBannerView * bannerView() { return &m_bannerView; } - void setDisplayBanner(bool display) { m_displayBanner = display; } - void selectHistogram(int index); - void deselectHistogram(int index); - // View - void drawRect(KDContext * ctx, KDRect rect) const override; - int numberOfSubviews() const override; + // MultipleDataView + int seriesOfSubviewAtIndex(int index) override; + const HistogramBannerView * bannerView() const override { return &m_bannerView; } + HistogramView * dataViewAtIndex(int index) override; private: - KDRect bannerFrame() const; - View * subviewAtIndex(int index) override; - void layoutSubviews() override; - void changeHistogramSelection(int index, bool select); + void changeDataViewSelection(int index, bool select) override; HistogramView m_histogramView1; HistogramView m_histogramView2; HistogramView m_histogramView3; HistogramBannerView m_bannerView; Shared::OkView m_okView; - bool m_displayBanner; - Store * m_store; }; }