From b2f81db4a9f0b08d8033f0b882fbcc7d911a8ed5 Mon Sep 17 00:00:00 2001 From: Gabriel Ozouf Date: Wed, 22 Jul 2020 16:43:25 +0200 Subject: [PATCH] [apps/home] Apps placement defined by country Instead of relying on the order in which the apps are passed at compile time, the look of the Home app is defined in a config file, and depends on the country chosen. Change-Id: If7f56a284e0001d6d75ece1e7efdf5fd1d0a9978 --- apps/country_preferences.csv | 22 ++++----- apps/country_preferences.h | 12 ++++- apps/global_preferences.h | 1 + apps/home/Makefile | 19 ++++++++ apps/home/apps_layout.csv | 2 + apps/home/apps_layout.py | 90 ++++++++++++++++++++++++++++++++++++ apps/home/controller.cpp | 5 +- 7 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 apps/home/apps_layout.csv create mode 100644 apps/home/apps_layout.py diff --git a/apps/country_preferences.csv b/apps/country_preferences.csv index 0e95a872b..55b196505 100644 --- a/apps/country_preferences.csv +++ b/apps/country_preferences.csv @@ -1,11 +1,11 @@ -CountryCode,CountryPreferences::AvailableExamModes,CountryPreferences::MethodForQuartiles,Poincare::Preferences::UnitFormat -CA,StandardOnly,MedianOfSublist,Metric -DE,StandardOnly,MedianOfSublist,Metric -ES,StandardOnly,MedianOfSublist,Metric -FR,StandardOnly,CumulatedFrequency,Metric -GB,StandardOnly,MedianOfSublist,Metric -IT,StandardOnly,CumulatedFrequency,Metric -NL,All,MedianOfSublist,Metric -PT,StandardOnly,MedianOfSublist,Metric -US,StandardOnly,MedianOfSublist,Imperial -WW,StandardOnly,MedianOfSublist,Metric +CountryCode,CountryPreferences::AvailableExamModes,CountryPreferences::MethodForQuartiles,Poincare::Preferences::UnitFormat,CountryPreferences::HomeAppsLayout +CA,StandardOnly,MedianOfSublist,Metric,Default +DE,StandardOnly,MedianOfSublist,Metric,Default +ES,StandardOnly,MedianOfSublist,Metric,Default +FR,StandardOnly,CumulatedFrequency,Metric,Default +GB,StandardOnly,MedianOfSublist,Metric,Default +IT,StandardOnly,CumulatedFrequency,Metric,Default +NL,All,MedianOfSublist,Metric,Default +PT,StandardOnly,MedianOfSublist,Metric,Default +US,StandardOnly,MedianOfSublist,Imperial,HidePython +WW,StandardOnly,MedianOfSublist,Metric,Default diff --git a/apps/country_preferences.h b/apps/country_preferences.h index 703801c97..2ec6bb7ee 100644 --- a/apps/country_preferences.h +++ b/apps/country_preferences.h @@ -15,20 +15,28 @@ public: CumulatedFrequency }; - constexpr CountryPreferences(AvailableExamModes availableExamModes, MethodForQuartiles methodForQuartiles, Poincare::Preferences::UnitFormat unitFormat) : + enum class HomeAppsLayout : uint8_t { + Default, + HidePython, + }; + + constexpr CountryPreferences(AvailableExamModes availableExamModes, MethodForQuartiles methodForQuartiles, Poincare::Preferences::UnitFormat unitFormat, HomeAppsLayout homeAppsLayout) : m_availableExamModes(availableExamModes), m_methodForQuartiles(methodForQuartiles), - m_unitFormat(unitFormat) + m_unitFormat(unitFormat), + m_homeAppsLayout(homeAppsLayout) {} constexpr AvailableExamModes availableExamModes() const { return m_availableExamModes; } constexpr MethodForQuartiles methodForQuartiles() const { return m_methodForQuartiles; } constexpr Poincare::Preferences::UnitFormat unitFormat() const { return m_unitFormat; } + constexpr HomeAppsLayout homeAppsLayout() const { return m_homeAppsLayout; } private: const AvailableExamModes m_availableExamModes; const MethodForQuartiles m_methodForQuartiles; const Poincare::Preferences::UnitFormat m_unitFormat; + const HomeAppsLayout m_homeAppsLayout; }; #endif diff --git a/apps/global_preferences.h b/apps/global_preferences.h index c6c20ff8b..215824b80 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -19,6 +19,7 @@ public: CountryPreferences::AvailableExamModes availableExamModes() const { return I18n::CountryPreferencesArray[static_cast(m_country)].availableExamModes(); } CountryPreferences::MethodForQuartiles methodForQuartiles() const { return I18n::CountryPreferencesArray[static_cast(m_country)].methodForQuartiles(); } Poincare::Preferences::UnitFormat unitFormat() const { return I18n::CountryPreferencesArray[static_cast(m_country)].unitFormat(); } + CountryPreferences::HomeAppsLayout homeAppsLayout() const { return I18n::CountryPreferencesArray[static_cast(m_country)].homeAppsLayout(); } bool isInExamMode() const { return (int8_t)examMode() > 0; } ExamMode examMode() const; void setExamMode(ExamMode examMode); diff --git a/apps/home/Makefile b/apps/home/Makefile index fc45cb479..c76c45808 100644 --- a/apps/home/Makefile +++ b/apps/home/Makefile @@ -1,9 +1,28 @@ app_home_src = $(addprefix apps/home/,\ app.cpp \ app_cell.cpp \ + apps_layout.py \ controller.cpp \ ) apps_src += $(app_home_src) i18n_files += $(call i18n_without_universal_for,home/base) + +# Apps layout file generation + +# The header is refered to as so make sure it's +# findable this way +SFLAGS += -I$(BUILD_DIR) + +apps_layout = apps/home/apps_layout.csv + +$(eval $(call rule_for, \ + APPS_LAYOUT, \ + apps/home/apps_layout.cpp, \ + , \ + $$(PYTHON) apps/home/apps_layout.py --layouts $(apps_layout) --header $$(subst .cpp,.h,$$@) --implementation $$@ --apps $$(EPSILON_APPS), \ + global \ +)) + +$(BUILD_DIR)/apps/home/apps_layout.h: $(BUILD_DIR)/apps/home/apps_layout.cpp diff --git a/apps/home/apps_layout.csv b/apps/home/apps_layout.csv new file mode 100644 index 000000000..3e2f967d3 --- /dev/null +++ b/apps/home/apps_layout.csv @@ -0,0 +1,2 @@ +Default,calculation,graph,code,statistics,probability,solver,sequence,regression,settings +HidePython,calculation,graph,solver,statistics,probability,regression,sequence,code,settings diff --git a/apps/home/apps_layout.py b/apps/home/apps_layout.py new file mode 100644 index 000000000..cff1f712b --- /dev/null +++ b/apps/home/apps_layout.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +# This script builds the .h/.cpp files for managing the placement of +# applications on the home menu, from the apps_layout.csv file. + +import argparse +import csv +import io + +parser = argparse.ArgumentParser(description="Build tools for the placement of applications on the home menu.") +parser.add_argument('--header', help='the .h file to generate') +parser.add_argument('--implementation', help='the .cpp file to generate') +parser.add_argument('--apps', nargs='+', help='apps that are actually compiled') +parser.add_argument('--layouts', help='the apps_layout.csv file') + +args = parser.parse_args() + +def build_permutation(apps, appsOrdered): + res = [0] * len(apps) + i = 0 + for app in appsOrdered: + if app in apps: + res[i] = apps.index(app) + 1 + i += 1 + return res + +def parse_config_file(layouts, apps): + res = {'styles':[], 'permutations':[]} + with io.open(layouts, "r", encoding="utf-8") as f: + csvreader = csv.reader(f, delimiter=',') + for row in csvreader: + res['styles'].append(row[0]) + res['permutations'].append(build_permutation(apps, row[1:])) + return res + +data = parse_config_file(args.layouts, args.apps) + +def print_header(header, data): + f = open(header, "w") + f.write("#ifndef HOME_APPS_LAYOUT_H\n") + f.write("#define HOME_APPS_LAYOUT_H\n") + f.write("// This file is auto-generated by apps_layout.py\n\n") + f.write("namespace Home {\n\n") + + f.write("int PermutedAppSnapshotIndex(int index);\n\n") + + f.write("}\n\n") + f.write("#endif") + f.close() + +def print_implementation(implementation, data): + f = open(implementation, "w") + f.write("// This file is auto-generated by apps_layout.py\n\n") + f.write('#include "apps_layout.h"\n') + f.write("#include \n") + f.write("#include \n") + f.write("#include \n\n") + f.write("namespace Home {\n\n") + + styles = data['styles'] + permutations = data['permutations'] + + f.write("/* Permutations are built so that Permutation[n] is the index of the snapshot\n") + f.write(" * for the nth app in the Home menu. */\n\n") + + for i in range(len(styles)): + f.write("static constexpr int " + styles[i] + "AppsPermutation[] = {\n") + f.write(" 0,\n") + for j in permutations[i]: + f.write(" " + str(j) + ",\n") + f.write("};\n") + f.write('static_assert(' + styles[i] + 'AppsPermutation[0] == 0, "The Home apps must always be at index 0");\n\n') + + f.write("int PermutedAppSnapshotIndex(int index) {\n") + f.write(" CountryPreferences::HomeAppsLayout currentLayout = GlobalPreferences::sharedGlobalPreferences()->homeAppsLayout();\n") + for i in range(len(styles)): + f.write(" if (currentLayout == CountryPreferences::HomeAppsLayout::" + styles[i] + ") {\n") + f.write(" return " + styles[i] + "AppsPermutation[index];") + f.write(" }\n") + f.write(" assert(false);\n") + f.write(" return -1;\n") + f.write("}\n\n") + + f.write("}\n\n") + f.close() + +if args.header: + print_header(args.header, data) +if args.implementation: + print_implementation(args.implementation, data) diff --git a/apps/home/controller.cpp b/apps/home/controller.cpp index 754a367dd..387cc5358 100644 --- a/apps/home/controller.cpp +++ b/apps/home/controller.cpp @@ -1,5 +1,6 @@ #include "controller.h" #include "app.h" +#include #include "../apps_container.h" #include "../global_preferences.h" #include "../exam_mode_configuration.h" @@ -60,7 +61,7 @@ Controller::Controller(Responder * parentResponder, SelectableTableViewDataSourc bool Controller::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK || event == Ion::Events::EXE) { AppsContainer * container = AppsContainer::sharedAppsContainer(); - ::App::Snapshot * selectedSnapshot = container->appSnapshotAtIndex(selectionDataSource()->selectedRow() * k_numberOfColumns + selectionDataSource()->selectedColumn() + 1); + ::App::Snapshot * selectedSnapshot = container->appSnapshotAtIndex(PermutedAppSnapshotIndex(selectionDataSource()->selectedRow() * k_numberOfColumns + selectionDataSource()->selectedColumn() + 1)); if (ExamModeConfiguration::appIsForbiddenInExamMode(selectedSnapshot->descriptor()->name(), GlobalPreferences::sharedGlobalPreferences()->examMode())) { App::app()->displayWarning(I18n::Message::ForbidenAppInExamMode1, I18n::Message::ForbidenAppInExamMode2); } else { @@ -128,7 +129,7 @@ void Controller::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { appCell->setVisible(false); } else { appCell->setVisible(true); - ::App::Descriptor * descriptor = container->appSnapshotAtIndex(appIndex)->descriptor(); + ::App::Descriptor * descriptor = container->appSnapshotAtIndex(PermutedAppSnapshotIndex(appIndex))->descriptor(); appCell->setAppDescriptor(descriptor); } }