diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index da0adbbed..000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,12 +0,0 @@ -environment: - matrix: - - MSYS2_ARCH: x86_64 - MSYSTEM: MINGW64 - - -install: - - set PATH=c:\msys64\usr\bin - - pacman -S --noconfirm mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-fltk git make bison mingw-w64-x86_64-python2 - -build_script: - - bash -lc "make -C $APPVEYOR_BUILD_FOLDER PLATFORM=simulator epsilon.exe test.exe" diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml new file mode 100644 index 000000000..35cf2f247 --- /dev/null +++ b/.github/workflows/ci-workflow.yml @@ -0,0 +1,104 @@ +name: Continuous integration +on: [pull_request, push] + +jobs: + build-simulator-android: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - run: make -j2 PLATFORM=simulator TARGET=android + - uses: actions/upload-artifact@master + with: + name: epsilon-simulator-android.apk + path: output/release/simulator/android/app/outputs/apk/release/android-release-unsigned.apk + build-device-n0100: + runs-on: ubuntu-latest + steps: + - run: sudo apt-get install binutils-arm-none-eabi build-essential gcc-arm-none-eabi imagemagick libfreetype6-dev libjpeg-dev libpng-dev pkg-config + - uses: actions/checkout@v1 + - run: make -j2 MODEL=n0100 epsilon.dfu + - run: make -j2 MODEL=n0100 epsilon.onboarding.dfu + - run: make -j2 MODEL=n0100 epsilon.onboarding.update.dfu + - run: make -j2 MODEL=n0100 flasher.light.dfu + - run: make -j2 MODEL=n0100 flasher.verbose.dfu + - uses: actions/upload-artifact@master + with: + name: epsilon-device-n0100.dfu + path: output/release/device/n0100/epsilon.dfu + - run: make -j2 MODEL=n0100 test.elf + build-device-n0110: + runs-on: ubuntu-latest + steps: + - run: sudo apt-get install binutils-arm-none-eabi build-essential gcc-arm-none-eabi imagemagick libfreetype6-dev libjpeg-dev libpng-dev pkg-config + - uses: actions/checkout@v1 + - run: make -j2 epsilon.dfu + - run: make -j2 epsilon.onboarding.dfu + - run: make -j2 epsilon.onboarding.update.dfu + - run: make -j2 flasher.light.dfu + - run: make -j2 flasher.verbose.dfu + - run: make -j2 bench.ram.dfu + - run: make -j2 bench.flash.dfu + - uses: actions/upload-artifact@master + with: + name: epsilon-device-n0110.dfu + path: output/release/device/n0110/epsilon.dfu + - run: make -j2 test.elf + build-simulator-windows: + runs-on: windows-latest + steps: + - uses: numworks/setup-msys2@v1 + - uses: actions/checkout@v1 + - run: msys2do pacman -S --noconfirm mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config make mingw-w64-x86_64-python3 mingw-w64-x86_64-libjpeg-turbo mingw-w64-x86_64-libpng + - run: msys2do make -j2 PLATFORM=simulator + - uses: actions/upload-artifact@master + with: + name: epsilon-simulator-windows.exe + path: output/release/simulator/windows/epsilon.exe + - run: msys2do make -j2 PLATFORM=simulator test.headless.exe + - run: output\release\simulator\windows\test.headless.exe + build-simulator-web: + runs-on: ubuntu-latest + steps: + - uses: numworks/setup-emscripten@v1 + - uses: actions/checkout@v1 + - run: source $EMSDK/emsdk_env.sh && make -j2 PLATFORM=simulator TARGET=web + - uses: actions/upload-artifact@master + with: + name: epsilon-simulator-web.zip + path: output/release/simulator/web/simulator.zip + - run: source $EMSDK/emsdk_env.sh && make -j2 PLATFORM=simulator TARGET=web test.headless.js + - run: node output/release/simulator/web/test.headless.js + build-simulator-linux: + runs-on: ubuntu-latest + steps: + - run: sudo apt-get install build-essential imagemagick libfreetype6-dev libjpeg-dev libpng-dev pkg-config + - uses: actions/checkout@v1 + - run: make -j2 PLATFORM=simulator + - uses: actions/upload-artifact@master + with: + name: epsilon-simulator-linux.bin + path: output/release/simulator/linux/epsilon.bin + - run: make -j2 PLATFORM=simulator test.headless.bin + - run: output/release/simulator/linux/test.headless.bin + build-simulator-macos: + runs-on: macOS-latest + steps: + - run: brew install numworks/tap/epsilon-sdk + - uses: actions/checkout@v1 + - run: make -j2 PLATFORM=simulator + - uses: actions/upload-artifact@master + with: + name: epsilon-simulator-macos.zip + path: output/release/simulator/macos/app/Payload + - run: make -j2 PLATFORM=simulator ARCH=x86_64 test.headless.bin + - run: output/release/simulator/macos/x86_64/test.headless.bin + build-simulator-ios: + runs-on: macOS-latest + steps: + - run: brew install numworks/tap/epsilon-sdk + - uses: actions/checkout@v1 + - run: make -j2 PLATFORM=simulator TARGET=ios GOOGLE_ANALYTICS=0 + - uses: actions/upload-artifact@master + with: + name: epsilon-simulator-ios.ipa + path: output/release/simulator/ios/app/epsilon.ipa diff --git a/.gitignore b/.gitignore index 378eac25d..a43c2d871 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -build +/output/ +build/device/**/*.pyc diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e6765d5e1..000000000 --- a/.travis.yml +++ /dev/null @@ -1,26 +0,0 @@ -language: cpp - -env: - global: - - MAKEFLAGS="-j 2" - -addons: - apt: - sources: - - sourceline: 'ppa:team-gcc-arm-embedded/ppa' - packages: - - gcc-arm-embedded - - libfltk1.3-dev - -matrix: - include: - - env: PLATFORM=device EXT=elf - - env: PLATFORM=simulator EXT=elf - - env: PLATFORM=simulator EXT=elf TOOLCHAIN=host-clang - - env: PLATFORM=blackbox EXT=bin QUIZ_USE_CONSOLE=1 - -os: linux -script: -- set -e -- make clean && make epsilon.$EXT test.$EXT -- if [ "$PLATFORM" = "blackbox" ]; then build/blackbox/test.$EXT; PLATFORM=blackbox make integration_tests; fi diff --git a/Makefile b/Makefile index 16ec20896..8f8af5d51 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,13 @@ -include scripts/config.mak +include build/config.mak # Disable default Make rules .SUFFIXES: object_for = $(addprefix $(BUILD_DIR)/,$(addsuffix .o,$(basename $(1)))) -default: $(BUILD_DIR)/epsilon.$(EXE) +# Define the default recipe + +default: # Define a standard rule helper # If passed a last parameter value of with_local_version, we also define an @@ -18,23 +20,51 @@ define rule_label endef define rule_for -$(addprefix $$(BUILD_DIR)/,$(strip $(2))): $(strip $(3)) | $$$$(@D)/. - @ echo "$(shell printf "%-8s" $(strip $(1)))$$(@:$$(BUILD_DIR)/%=%)" - $(Q) $(4) ifeq ($(strip $(5)),with_local_version) $(addprefix $$(BUILD_DIR)/,$(strip $(2))): $(addprefix $$(BUILD_DIR)/,$(strip $(3))) @ echo "$(shell printf "%-8s" $(strip $(1)))$$(@:$$(BUILD_DIR)/%=%)" $(Q) $(4) endif +$(addprefix $$(BUILD_DIR)/,$(strip $(2))): $(strip $(3)) | $$$$(@D)/. + @ echo "$(shell printf "%-8s" $(strip $(1)))$$(@:$$(BUILD_DIR)/%=%)" + $(Q) $(4) endef .PHONY: info info: @echo "EPSILON_VERSION = $(EPSILON_VERSION)" - @echo "EPSILON_ONBOARDING_APP = $(EPSILON_ONBOARDING_APP)" - @echo "EPSILON_BOOT_PROMPT = $(EPSILON_BOOT_PROMPT)" @echo "EPSILON_APPS = $(EPSILON_APPS)" @echo "EPSILON_I18N = $(EPSILON_I18N)" + @echo "PLATFORM" = $(PLATFORM) + @echo "DEBUG" = $(DEBUG) + @echo "EPSILON_GETOPT" = $(EPSILON_GETOPT) + @echo "ESCHER_LOG_EVENTS_BINARY" = $(ESCHER_LOG_EVENTS_BINARY) + @echo "QUIZ_USE_CONSOLE" = $(QUIZ_USE_CONSOLE) + @echo "ION_STORAGE_LOG" = $(ION_STORAGE_LOG) + @echo "POINCARE_TREE_LOG" = $(POINCARE_TREE_LOG) + @echo "POINCARE_TESTS_PRINT_EXPRESSIONS" = $(POINCARE_TESTS_PRINT_EXPRESSIONS) + +.PHONY: help +help: + @echo "Device targets" + @echo " make epsilon_flash" + @echo " make epsilon.dfu" + @echo " make epsilon.onboarding.dfu" + @echo " make epsilon.onboarding.update.dfu" + @echo " make epsilon.onboarding.beta.dfu" + @echo " make flasher.light.bin" + @echo " make flasher.verbose.dfu" + @echo " make bench.ram.bin" + @echo " make bench.flash.bin" + @echo " make binpack" + @echo "" + @echo "Simulator targets" + @echo " make PLATFORM=simulator" + @echo " make PLATFORM=simulator TARGET=android" + @echo " make PLATFORM=simulator TARGET=ios" + @echo " make PLATFORM=simulator TARGET=macos" + @echo " make PLATFORM=simulator TARGET=web" + @echo " make PLATFORM=simulator TARGET=windows" # Since we're building out-of-tree, we need to make sure the output directories # are created, otherwise the receipes will fail (e.g. gcc will fail to create @@ -50,9 +80,9 @@ $(BUILD_DIR)%/.: # To make objects dependent on their directory, we need a second expansion .SECONDEXPANSION: -# Each sub-Makefile can either add sources to the $(src) variable or define a -# new executable target. The $(src) variable lists the sources that will be -# built and linked to every executable being generated. +# Each sub-Makefile can either add sources to $(%_src) variables or define a +# new executable target. The $(%_src) variables list the sources that can be +# built and linked to executables being generated. ifeq ($(USE_LIBA),0) include liba/Makefile.bridge @@ -68,65 +98,29 @@ include python/Makefile include escher/Makefile # Executable Makefiles include apps/Makefile -include scripts/struct_layout/Makefile -include scripts/scenario/Makefile +include build/struct_layout/Makefile +include build/scenario/Makefile include quiz/Makefile # Quiz needs to be included at the end -objs = $(call object_for,$(src)) -.SECONDARY: $(objs) +all_src = $(apps_all_src) $(escher_src) $(ion_all_src) $(kandinsky_src) $(liba_src) $(libaxx_src) $(poincare_src) $(python_src) $(epsilon_src) $(runner_src) $(ion_target_device_flasher_light_src) $(ion_target_device_flasher_verbose_src) $(ion_target_device_bench_src) $(tests_src) +all_objs = $(call object_for,$(all_src)) +.SECONDARY: $(all_objs) # Load source-based dependencies # Compilers can generate Makefiles that states the dependencies of a given # objet to other source and headers. This serve no purpose for a clean build, # but allows correct yet optimal incremental builds. --include $(objs:.o=.d) +-include $(all_objs:.o=.d) -# Load platform-specific targets -# We include them before the standard ones to give them precedence. --include scripts/targets.$(PLATFORM).mak +# Define main and shortcut targets +include build/targets.mak -# Define rules for executables -# Those can be built directly with make executable.exe as a shortcut. They also -# depends on $(objs) +# Fill in the default recipe +DEFAULT ?= $(BUILD_DIR)/epsilon.$(EXE) +default: $(DEFAULT) -executables = epsilon test flasher - -define rules_for_executable -$$(BUILD_DIR)/$(1).$$(EXE): $$(objs) -.PHONY: $(1).$$(EXE) -$(1).$$(EXE): $$(BUILD_DIR)/$(1).$$(EXE) -endef - -$(foreach executable,$(executables),$(eval $(call rules_for_executable,$(executable)))) - -# Define standard compilation rules - -$(eval $(call rule_for, \ - AS, %.o, %.s, \ - $$(CC) $$(SFLAGS) -c $$< -o $$@ \ -)) - -$(eval $(call rule_for, \ - CC, %.o, %.c, \ - $$(CC) $$(SFLAGS) $$(CFLAGS) -c $$< -o $$@, \ - with_local_version \ -)) - -$(eval $(call rule_for, \ - CXX, %.o, %.cpp, \ - $$(CXX) $$(SFLAGS) $$(CXXFLAGS) -c $$< -o $$@, \ - with_local_version \ -)) - -$(eval $(call rule_for, \ - OCC, %.o, %.m, \ - $$(CC) $$(SFLAGS) $$(CFLAGS) -c $$< -o $$@ \ -)) - -$(eval $(call rule_for, \ - LD, %.$$(EXE), , \ - $$(LD) $$^ $$(LDFLAGS) -o $$@ \ -)) +# Load standard build rules +include build/rules.mk .PHONY: clean clean: diff --git a/README.md b/README.md index fe46a6b46..9d2a90d32 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

Omega x Epsilon 11.2.0

+

Omega x Epsilon 12.0.0

[![Gitlab pipeline status](https://img.shields.io/gitlab/pipeline/joachim2lefournis/Omega/lavaos?logo=gitlab&style=for-the-badge)](https://gitlab.com/joachim2lefournis/Omega/pipelines) [![License: CC BY-NC-SA 4.0](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg?logo=creative%20commons&style=for-the-badge)](https://creativecommons.org/licenses/by-nc-sa/4.0/) diff --git a/apps/Makefile b/apps/Makefile index 34eeb7463..40605127f 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -17,11 +17,12 @@ app_src += $(addprefix apps/,\ backlight_dimming_timer.cpp \ battery_timer.cpp \ battery_view.cpp \ - constant.cpp \ empty_battery_window.cpp \ exam_pop_up_controller.cpp \ global_preferences.cpp \ + i18n.py \ lock_view.cpp \ + main.cpp \ math_toolbox.cpp \ shift_alpha_lock_view.cpp \ suspend_timer.cpp \ @@ -30,6 +31,12 @@ app_src += $(addprefix apps/,\ variable_box_empty_controller.cpp \ ) +apps_launch_on_boarding_src += apps/apps_container_launch_on_boarding.cpp +apps_launch_default_src += apps/apps_container_launch_default.cpp +apps_prompt_none_src += apps/apps_container_prompt_none.cpp +apps_prompt_beta_src += apps/apps_container_prompt_beta.cpp +apps_prompt_update_src += apps/apps_container_prompt_update.cpp + snapshots_declaration = $(foreach i,$(apps),$(i)::Snapshot m_snapshot$(subst :,,$(i))Snapshot;) apps_declaration = $(foreach i,$(apps),$(i) m_$(subst :,,$(i));) snapshots_construction = $(foreach i,$(apps),,m_snapshot$(subst :,,$(i))Snapshot()) @@ -38,7 +45,7 @@ snapshots_count = $(words $(apps)) snapshot_includes = $(foreach i,$(app_headers),-include $(i) ) epsilon_app_names = '$(foreach i,${EPSILON_APPS},"$(i)", )' -$(call object_for,apps/apps_container_storage.cpp apps/main.cpp): CXXFLAGS += $(snapshot_includes) -DAPPS_CONTAINER_APPS_DECLARATION="$(apps_declaration)" -DAPPS_CONTAINER_SNAPSHOT_DECLARATIONS="$(snapshots_declaration)" -DAPPS_CONTAINER_SNAPSHOT_CONSTRUCTORS="$(snapshots_construction)" -DAPPS_CONTAINER_SNAPSHOT_LIST="$(snapshots_list)" -DAPPS_CONTAINER_SNAPSHOT_COUNT=$(snapshots_count) -DEPSILON_APPS_NAMES=$(epsilon_app_names) +$(call object_for,apps/apps_container_storage.cpp apps/apps_container.cpp apps/main.cpp): CXXFLAGS += $(snapshot_includes) -DAPPS_CONTAINER_APPS_DECLARATION="$(apps_declaration)" -DAPPS_CONTAINER_SNAPSHOT_DECLARATIONS="$(snapshots_declaration)" -DAPPS_CONTAINER_SNAPSHOT_CONSTRUCTORS="$(snapshots_construction)" -DAPPS_CONTAINER_SNAPSHOT_LIST="$(snapshots_list)" -DAPPS_CONTAINER_SNAPSHOT_COUNT=$(snapshots_count) -DEPSILON_APPS_NAMES=$(epsilon_app_names) # I18n file generation @@ -78,22 +85,19 @@ $(BUILD_DIR)/apps/i18n.h: $(BUILD_DIR)/apps/i18n.cpp $(eval $(call depends_on_image,apps/title_bar_view.cpp,apps/exam_icon.png)) -# Handle epsilon-only sources -# Other executables may want to do their own i18n and main. That's the case for -# the test runner. Let's add those files to a specific epsilon-only src. When -# building apps/i18n.o, the extension doesn't really matter since it'll be -# processed by the object_for function. - -epsilon_src += $(addprefix apps/, \ - main.cpp \ - i18n.py \ -) - -all_app_src = $(app_src) $(epsilon_src) +all_app_src = $(app_src) $(epsilon_src) $(apps_launch_on_boarding_src) $(apps_launch_default_src) $(apps_prompt_none_src) $(apps_container_prompt_update) $(apps_prompt_beta_src) $(tests_src) $(call object_for,$(all_app_src)): $(BUILD_DIR)/apps/i18n.h $(call object_for,$(all_app_src)): $(BUILD_DIR)/python/port/genhdr/qstrdefs.generated.h -$(BUILD_DIR)/epsilon.$(EXE): $(call object_for,$(epsilon_src)) +apps_tests_src = $(app_calculation_test_src) $(app_probability_test_src) $(app_regression_test_src) $(app_sequence_test_src) $(app_shared_test_src) $(app_statistics_test_src) $(app_solver_test_src) -src += $(app_src) +# Configure variants +apps_all_src = $(app_src) +apps_all_src += $(apps_launch_default_src) $(apps_launch_on_boarding_src +apps_all_src += $(apps_prompt_none_src) $(apps_prompt_update_src) $(apps_prompt_beta_src) + +apps_default_src = $(app_src) $(apps_launch_default_src) $(apps_prompt_none_src) +apps_onboarding_src = $(app_src) $(apps_launch_on_boarding_src) $(apps_prompt_none_src) +apps_onboarding_update_src = $(app_src) $(apps_launch_on_boarding_src) $(apps_prompt_update_src) +apps_onboarding_beta_src = $(app_src) $(apps_launch_on_boarding_src) $(apps_prompt_beta_src) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index 6e7cef01d..adb666104 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -1,4 +1,5 @@ #include "apps_container.h" +#include "apps_container_storage.h" #include "global_preferences.h" #include #include @@ -10,47 +11,10 @@ extern "C" { using namespace Shared; -#if EPSILON_BOOT_PROMPT == EPSILON_BETA_PROMPT - -static I18n::Message sPromptMessages[] = { - I18n::Message::BetaVersion, - I18n::Message::BetaVersionMessage1, - I18n::Message::BetaVersionMessage2, - I18n::Message::BetaVersionMessage3, - I18n::Message::BlankMessage, - I18n::Message::BetaVersionMessage4, - I18n::Message::BetaVersionMessage5, - I18n::Message::BetaVersionMessage6}; - -static KDColor sPromptColors[] = { - KDColorBlack, - KDColorBlack, - KDColorBlack, - KDColorBlack, - KDColorWhite, - KDColorBlack, - KDColorBlack, - Palette::YellowDark}; - -#elif EPSILON_BOOT_PROMPT == EPSILON_UPDATE_PROMPT - -static I18n::Message sPromptMessages[] = { - I18n::Message::UpdateAvailable, - I18n::Message::UpdateMessage1, - I18n::Message::UpdateMessage2, - I18n::Message::BlankMessage, - I18n::Message::UpdateMessage3, - I18n::Message::UpdateMessage4}; - -static KDColor sPromptColors[] = { - KDColorBlack, - KDColorBlack, - KDColorBlack, - KDColorWhite, - KDColorBlack, - Palette::YellowDark}; - -#endif +AppsContainer * AppsContainer::sharedAppsContainer() { + static AppsContainerStorage appsContainerStorage; + return &appsContainerStorage; +} AppsContainer::AppsContainer() : Container(), @@ -59,13 +23,9 @@ AppsContainer::AppsContainer() : m_globalContext(), m_variableBoxController(), m_examPopUpController(this), -#if EPSILON_BOOT_PROMPT == EPSILON_BETA_PROMPT - m_promptController(sPromptMessages, sPromptColors, 8), -#elif EPSILON_BOOT_PROMPT == EPSILON_UPDATE_PROMPT - m_promptController(sPromptMessages, sPromptColors, 6), -#endif - m_batteryTimer(BatteryTimer(this)), - m_suspendTimer(SuspendTimer(this)), + m_promptController(k_promptMessages, k_promptColors, k_promptNumberOfMessages), + m_batteryTimer(), + m_suspendTimer(), m_backlightDimmingTimer(), m_homeSnapshot(), m_onBoardingSnapshot(), @@ -91,7 +51,7 @@ AppsContainer::AppsContainer() : bool AppsContainer::poincareCircuitBreaker() { Ion::Keyboard::State state = Ion::Keyboard::scan(); - return state.keyDown(Ion::Keyboard::Key::A6); + return state.keyDown(Ion::Keyboard::Key::Back); } App::Snapshot * AppsContainer::hardwareTestAppSnapshot() { @@ -128,15 +88,14 @@ VariableBoxController * AppsContainer::variableBoxController() { return &m_variableBoxController; } -void AppsContainer::suspend(bool checkIfPowerKeyReleased) { +void AppsContainer::suspend(bool checkIfOnOffKeyReleased) { resetShiftAlphaStatus(); GlobalPreferences * globalPreferences = GlobalPreferences::sharedGlobalPreferences(); -#ifdef EPSILON_BOOT_PROMPT - if (activeApp()->snapshot()!= onBoardingAppSnapshot() && activeApp()->snapshot() != hardwareTestAppSnapshot() && globalPreferences->showPopUp()) { - activeApp()->displayModalViewController(&m_promptController, 0.f, 0.f); + // Display the prompt if it has a message to display + if (promptController() != nullptr && s_activeApp->snapshot()!= onBoardingAppSnapshot() && s_activeApp->snapshot() != hardwareTestAppSnapshot() && globalPreferences->showPopUp()) { + s_activeApp->displayModalViewController(promptController(), 0.f, 0.f); } -#endif - Ion::Power::suspend(checkIfPowerKeyReleased); + Ion::Power::suspend(checkIfOnOffKeyReleased); /* Ion::Power::suspend() completely shuts down the LCD controller. Therefore * the frame memory is lost. That's why we need to force a window redraw * upon wakeup, otherwise the screen is filled with noise. */ @@ -149,9 +108,12 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { bool alphaLockWantsRedraw = updateAlphaLock(); bool didProcessEvent = false; + if (event == Ion::Events::USBEnumeration || event == Ion::Events::USBPlug || event == Ion::Events::BatteryCharging) { + Ion::LED::updateColorWithPlugAndCharge(); + } if (event == Ion::Events::USBEnumeration) { if (Ion::USB::isPlugged()) { - App::Snapshot * activeSnapshot = (activeApp() == nullptr ? appSnapshotAtIndex(0) : activeApp()->snapshot()); + App::Snapshot * activeSnapshot = (s_activeApp == nullptr ? appSnapshotAtIndex(0) : s_activeApp->snapshot()); /* Just after a software update, the battery timer does not have time to * fire before the calculator enters DFU mode. As the DFU mode blocks the * event loop, we update the battery state "manually" here. @@ -160,6 +122,8 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { updateBatteryState(); if (switchTo(usbConnectedAppSnapshot())) { Ion::USB::DFU(); + // Update LED when exiting DFU mode + Ion::LED::updateColorWithPlugAndCharge(); bool switched = switchTo(activeSnapshot); assert(switched); (void) switched; // Silence compilation warning about unused variable. @@ -201,10 +165,12 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { } bool AppsContainer::processEvent(Ion::Events::Event event) { + // Warning: if the window is dirtied, you need to call window()->redraw() if (event == Ion::Events::USBPlug) { if (Ion::USB::isPlugged()) { if (GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::Activate) { displayExamModePopUp(false); + window()->redraw(); } else { Ion::USB::enable(); } @@ -226,7 +192,7 @@ bool AppsContainer::processEvent(Ion::Events::Event event) { } bool AppsContainer::switchTo(App::Snapshot * snapshot) { - if (activeApp() && snapshot != activeApp()->snapshot()) { + if (s_activeApp && snapshot != s_activeApp->snapshot()) { resetShiftAlphaStatus(); } if (snapshot == hardwareTestAppSnapshot() || snapshot == onBoardingAppSnapshot()) { @@ -254,21 +220,16 @@ void AppsContainer::run() { /* Normal execution. The exception checkpoint must be created before * switching to the first app, because the first app might create nodes on * the pool. */ - bool switched = -#if EPSILON_ONBOARDING_APP - switchTo(onBoardingAppSnapshot()); -#else - switchTo(appSnapshotAtIndex(numberOfApps() == 2 ? 1 : 0)); -#endif + bool switched = switchTo(initialAppSnapshot()); assert(switched); (void) switched; // Silence compilation warning about unused variable. } else { // Exception - if (activeApp() != nullptr) { + if (s_activeApp != nullptr) { /* The app models can reference layouts or expressions that have been * destroyed from the pool. To avoid using them before packing the app * (in App::willBecomeInactive for instance), we tidy them early on. */ - activeApp()->snapshot()->tidy(); + s_activeApp->snapshot()->tidy(); /* When an app encoutered an exception due to a full pool, the next time * the user enters the app, the same exception could happen again which * would prevent from reopening the app. To avoid being stuck outside the @@ -276,13 +237,13 @@ void AppsContainer::run() { * exception. For instance, the calculation app can encounter an * exception when displaying too many huge layouts, if we don't clean the * history here, we will be stuck outside the calculation app. */ - activeApp()->snapshot()->reset(); + s_activeApp->snapshot()->reset(); } bool switched = switchTo(appSnapshotAtIndex(0)); assert(switched); (void) switched; // Silence compilation warning about unused variable. Poincare::Tidy(); - activeApp()->displayWarning(I18n::Message::PoolMemoryFull1, I18n::Message::PoolMemoryFull2, true); + s_activeApp->displayWarning(I18n::Message::PoolMemoryFull1, I18n::Message::PoolMemoryFull2, true); } Container::run(); switchTo(nullptr); @@ -308,7 +269,7 @@ void AppsContainer::reloadTitleBarView() { void AppsContainer::displayExamModePopUp(bool activate) { m_examPopUpController.setActivatingExamMode(activate); - activeApp()->displayModalViewController(&m_examPopUpController, 0.f, 0.f, Metric::ExamPopUpTopMargin, Metric::PopUpRightMargin, Metric::ExamPopUpBottomMargin, Metric::PopUpLeftMargin); + s_activeApp->displayModalViewController(&m_examPopUpController, 0.f, 0.f, Metric::ExamPopUpTopMargin, Metric::PopUpRightMargin, Metric::ExamPopUpBottomMargin, Metric::PopUpLeftMargin); } void AppsContainer::shutdownDueToLowBattery() { @@ -322,6 +283,12 @@ void AppsContainer::shutdownDueToLowBattery() { } while (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) { Ion::Backlight::setBrightness(0); + if (GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::Deactivate) { + /* Unless the LED is lit up for the exam mode, switch off the LED. IF the + * low battery event happened during the Power-On Self-Test, a LED might + * have stayed lit up. */ + Ion::LED::setColor(KDColorBlack); + } m_emptyBatteryWindow.redraw(true); Ion::Timing::msleep(3000); Ion::Power::suspend(); @@ -338,11 +305,12 @@ bool AppsContainer::updateAlphaLock() { return m_window.updateAlphaLock(); } -#ifdef EPSILON_BOOT_PROMPT OnBoarding::PopUpController * AppsContainer::promptController() { + if (k_promptNumberOfMessages == 0) { + return nullptr; + } return &m_promptController; } -#endif void AppsContainer::redrawWindow() { m_window.redraw(); @@ -355,14 +323,14 @@ void AppsContainer::examDeactivatingPopUpIsDismissed() { } void AppsContainer::storageDidChangeForRecord(const Ion::Storage::Record record) { - if (activeApp()) { - activeApp()->snapshot()->storageDidChangeForRecord(record); + if (s_activeApp) { + s_activeApp->snapshot()->storageDidChangeForRecord(record); } } void AppsContainer::storageIsFull() { - if (activeApp()) { - activeApp()->displayWarning(I18n::Message::StorageMemoryFull1, I18n::Message::StorageMemoryFull2, true); + if (s_activeApp) { + s_activeApp->displayWarning(I18n::Message::StorageMemoryFull1, I18n::Message::StorageMemoryFull2, true); } } diff --git a/apps/apps_container.h b/apps/apps_container.h index a46dcebfc..bd0c8fb2c 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -15,19 +15,18 @@ #include "suspend_timer.h" #include "backlight_dimming_timer.h" #include "shared/global_context.h" - -#ifdef EPSILON_BOOT_PROMPT #include "on_boarding/pop_up_controller.h" -#endif #include class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::StorageDelegate { public: + static AppsContainer * sharedAppsContainer(); AppsContainer(); static bool poincareCircuitBreaker(); virtual int numberOfApps() = 0; virtual App::Snapshot * appSnapshotAtIndex(int index) = 0; + App::Snapshot * initialAppSnapshot(); App::Snapshot * hardwareTestAppSnapshot(); App::Snapshot * onBoardingAppSnapshot(); App::Snapshot * usbConnectedAppSnapshot(); @@ -35,7 +34,7 @@ public: Poincare::Context * globalContext(); MathToolbox * mathToolbox(); VariableBoxController * variableBoxController(); - void suspend(bool checkIfPowerKeyReleased = false); + void suspend(bool checkIfOnOffKeyReleased = false); virtual bool dispatchEvent(Ion::Events::Event event) override; bool switchTo(App::Snapshot * snapshot) override; void run() override; @@ -45,9 +44,7 @@ public: void displayExamModePopUp(bool activate); void shutdownDueToLowBattery(); void setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus newStatus); -#ifdef EPSILON_BOOT_PROMPT OnBoarding::PopUpController * promptController(); -#endif void redrawWindow(); // Exam pop-up controller delegate void examDeactivatingPopUpIsDismissed() override; @@ -64,15 +61,16 @@ private: void resetShiftAlphaStatus(); bool updateAlphaLock(); + static I18n::Message k_promptMessages[]; + static KDColor k_promptColors[]; + static int k_promptNumberOfMessages; AppsWindow m_window; EmptyBatteryWindow m_emptyBatteryWindow; Shared::GlobalContext m_globalContext; MathToolbox m_mathToolbox; VariableBoxController m_variableBoxController; ExamPopUpController m_examPopUpController; -#ifdef EPSILON_BOOT_PROMPT OnBoarding::PopUpController m_promptController; -#endif BatteryTimer m_batteryTimer; SuspendTimer m_suspendTimer; BacklightDimmingTimer m_backlightDimmingTimer; diff --git a/apps/apps_container_launch_default.cpp b/apps/apps_container_launch_default.cpp new file mode 100644 index 000000000..ce36dc509 --- /dev/null +++ b/apps/apps_container_launch_default.cpp @@ -0,0 +1,7 @@ +#include "apps_container.h" + +App::Snapshot * AppsContainer::initialAppSnapshot() { + // The backlight has not been initialized + Ion::Backlight::init(); + return appSnapshotAtIndex(numberOfApps() == 2 ? 1 : 0); +} diff --git a/apps/apps_container_launch_on_boarding.cpp b/apps/apps_container_launch_on_boarding.cpp new file mode 100644 index 000000000..18adecc70 --- /dev/null +++ b/apps/apps_container_launch_on_boarding.cpp @@ -0,0 +1,5 @@ +#include "apps_container.h" + +App::Snapshot * AppsContainer::initialAppSnapshot() { + return onBoardingAppSnapshot(); +} diff --git a/apps/apps_container_prompt_beta.cpp b/apps/apps_container_prompt_beta.cpp new file mode 100644 index 000000000..85be074d1 --- /dev/null +++ b/apps/apps_container_prompt_beta.cpp @@ -0,0 +1,23 @@ +#include "apps_container.h" + +I18n::Message AppsContainer::k_promptMessages[] = { + I18n::Message::BetaVersion, + I18n::Message::BetaVersionMessage1, + I18n::Message::BetaVersionMessage2, + I18n::Message::BetaVersionMessage3, + I18n::Message::BlankMessage, + I18n::Message::BetaVersionMessage4, + I18n::Message::BetaVersionMessage5, + I18n::Message::BetaVersionMessage6}; + +KDColor AppsContainer::k_promptColors[] = { + KDColorBlack, + KDColorBlack, + KDColorBlack, + KDColorBlack, + KDColorWhite, + KDColorBlack, + KDColorBlack, + Palette::YellowDark}; + +int AppsContainer::k_promptNumberOfMessages = 8; diff --git a/apps/apps_container_prompt_none.cpp b/apps/apps_container_prompt_none.cpp new file mode 100644 index 000000000..65556d670 --- /dev/null +++ b/apps/apps_container_prompt_none.cpp @@ -0,0 +1,8 @@ +#include "apps_container.h" + +I18n::Message AppsContainer::k_promptMessages[] = {}; + +KDColor AppsContainer::k_promptColors[] = {}; + +int AppsContainer::k_promptNumberOfMessages = 0; + diff --git a/apps/apps_container_prompt_update.cpp b/apps/apps_container_prompt_update.cpp new file mode 100644 index 000000000..25fe11b64 --- /dev/null +++ b/apps/apps_container_prompt_update.cpp @@ -0,0 +1,19 @@ +#include "apps_container.h" + +I18n::Message AppsContainer::k_promptMessages[] = { + I18n::Message::UpdateAvailable, + I18n::Message::UpdateMessage1, + I18n::Message::UpdateMessage2, + I18n::Message::BlankMessage, + I18n::Message::UpdateMessage3, + I18n::Message::UpdateMessage4}; + +KDColor AppsContainer::k_promptColors[] = { + KDColorBlack, + KDColorBlack, + KDColorBlack, + KDColorWhite, + KDColorBlack, + Palette::YellowDark}; + +int AppsContainer::k_promptNumberOfMessages = 6; diff --git a/apps/apps_container_storage.cpp b/apps/apps_container_storage.cpp index 57b1d24d9..2ef60aafa 100644 --- a/apps/apps_container_storage.cpp +++ b/apps/apps_container_storage.cpp @@ -14,11 +14,6 @@ constexpr int k_numberOfCommonApps = 1+APPS_CONTAINER_SNAPSHOT_COUNT; // Take the Home app into account -AppsContainerStorage * AppsContainerStorage::sharedContainer() { - static AppsContainerStorage appsContainerStorage; - return &appsContainerStorage; -} - AppsContainerStorage::AppsContainerStorage() : AppsContainer() APPS_CONTAINER_SNAPSHOT_CONSTRUCTORS diff --git a/apps/apps_container_storage.h b/apps/apps_container_storage.h index e726b1161..9abd3c27a 100644 --- a/apps/apps_container_storage.h +++ b/apps/apps_container_storage.h @@ -9,7 +9,6 @@ class AppsContainerStorage : public AppsContainer { public: - static AppsContainerStorage * sharedContainer(); AppsContainerStorage(); int numberOfApps() override; App::Snapshot * appSnapshotAtIndex(int index) override; diff --git a/apps/battery_timer.cpp b/apps/battery_timer.cpp index ced5c1896..db53d7c90 100644 --- a/apps/battery_timer.cpp +++ b/apps/battery_timer.cpp @@ -1,16 +1,16 @@ #include "battery_timer.h" #include "apps_container.h" -BatteryTimer::BatteryTimer(AppsContainer * container) : - Timer(1), - m_container(container) +BatteryTimer::BatteryTimer() : + Timer(1) { } bool BatteryTimer::fire() { - bool needRedrawing = m_container->updateBatteryState(); + AppsContainer * container = AppsContainer::sharedAppsContainer(); + bool needRedrawing = container->updateBatteryState(); if (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) { - m_container->shutdownDueToLowBattery(); + container->shutdownDueToLowBattery(); } return needRedrawing; } diff --git a/apps/battery_timer.h b/apps/battery_timer.h index ccc7d0c46..90ad16c11 100644 --- a/apps/battery_timer.h +++ b/apps/battery_timer.h @@ -3,14 +3,11 @@ #include -class AppsContainer; - class BatteryTimer : public Timer { public: - BatteryTimer(AppsContainer * container); + BatteryTimer(); private: bool fire() override; - AppsContainer * m_container; }; #endif diff --git a/apps/battery_view.h b/apps/battery_view.h index 2c311bc4a..5c1d373aa 100644 --- a/apps/battery_view.h +++ b/apps/battery_view.h @@ -24,7 +24,7 @@ private: constexpr static KDCoordinate k_batteryWidth = 15; constexpr static KDCoordinate k_elementWidth = 1; constexpr static KDCoordinate k_capHeight = 4; - constexpr static KDCoordinate k_separatorThickness = 1; + constexpr static KDCoordinate k_separatorThickness = Metric::CellSeparatorThickness; Ion::Battery::Charge m_chargeState; bool m_isCharging; bool m_isPlugged; diff --git a/apps/calculation/Makefile b/apps/calculation/Makefile index c8960bb30..6b9f7cf0c 100644 --- a/apps/calculation/Makefile +++ b/apps/calculation/Makefile @@ -1,10 +1,13 @@ apps += Calculation::App app_headers += apps/calculation/app.h -app_src += $(addprefix apps/calculation/,\ - app.cpp \ +app_calculation_test_src += $(addprefix apps/calculation/,\ calculation.cpp \ calculation_store.cpp \ +) + +app_calculation_src = $(addprefix apps/calculation/,\ + app.cpp \ edit_expression_controller.cpp \ expression_field.cpp \ history_view_cell.cpp \ @@ -13,6 +16,9 @@ app_src += $(addprefix apps/calculation/,\ selectable_table_view.cpp \ ) +app_calculation_src += $(app_calculation_test_src) +app_src += $(app_calculation_src) + i18n_files += $(addprefix apps/calculation/,\ base.de.i18n\ base.en.i18n\ @@ -21,7 +27,7 @@ i18n_files += $(addprefix apps/calculation/,\ base.pt.i18n\ ) -tests += $(addprefix apps/calculation/test/,\ +tests_src += $(addprefix apps/calculation/test/,\ calculation_store.cpp\ ) diff --git a/apps/calculation/app.cpp b/apps/calculation/app.cpp index 44c2a938e..26f9ca1c9 100644 --- a/apps/calculation/app.cpp +++ b/apps/calculation/app.cpp @@ -1,5 +1,4 @@ #include "app.h" -#include "../apps_container.h" #include "calculation_icon.h" #include #include @@ -23,7 +22,7 @@ const Image * App::Descriptor::icon() { } App * App::Snapshot::unpack(Container * container) { - return new (container->currentAppBuffer()) App(container, this); + return new (container->currentAppBuffer()) App(this); } void App::Snapshot::reset() { @@ -39,8 +38,8 @@ void App::Snapshot::tidy() { m_calculationStore.tidy(); } -App::App(Container * container, Snapshot * snapshot) : - ExpressionFieldDelegateApp(container, snapshot, &m_editExpressionController), +App::App(Snapshot * snapshot) : + ExpressionFieldDelegateApp(snapshot, &m_editExpressionController), m_historyController(&m_editExpressionController, snapshot->calculationStore()), m_editExpressionController(&m_modalViewController, this, &m_historyController, snapshot->calculationStore()) { @@ -67,11 +66,7 @@ bool App::isAcceptableExpression(const Poincare::Expression expression) { return false; } } - return TextFieldDelegateApp::isAcceptableExpression(expression); -} - -char App::XNT() { - return 'x'; + return !expression.isUninitialized(); } } diff --git a/apps/calculation/app.h b/apps/calculation/app.h index 3379d5c68..8d0f75add 100644 --- a/apps/calculation/app.h +++ b/apps/calculation/app.h @@ -27,14 +27,16 @@ public: void tidy() override; CalculationStore m_calculationStore; }; + static App * app() { + return static_cast(Container::activeApp()); + } bool textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) override; bool layoutFieldDidReceiveEvent(::LayoutField * layoutField, Ion::Events::Event event) override; // TextFieldDelegateApp + bool isAcceptableExpression(const Poincare::Expression expression) override; - bool storeExpressionAllowed() const override { return true; } - char XNT() override; private: - App(Container * container, Snapshot * snapshot); + App(Snapshot * snapshot); HistoryController m_historyController; EditExpressionController m_editExpressionController; }; diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index f9f06e2a9..25b71a71d 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -1,7 +1,6 @@ #include "calculation.h" -#include "calculation_store.h" #include "../shared/poincare_helpers.h" -#include +#include #include #include #include @@ -14,158 +13,110 @@ namespace Calculation { static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { return x > y ? x : y; } -Calculation::Calculation() : - m_inputText(), - m_exactOutputText(), - m_approximateOutputText(), - m_displayOutput(DisplayOutput::Unknown), - m_height(-1), - m_expandedHeight(-1), - m_equalSign(EqualSign::Unknown) -{ -} - bool Calculation::operator==(const Calculation& c) { - return strcmp(m_inputText, c.m_inputText) == 0 - && strcmp(m_approximateOutputText, c.m_approximateOutputText) == 0 + return strcmp(inputText(), c.inputText()) == 0 + && strcmp(approximateOutputText(), c.approximateOutputText()) == 0 /* Some calculations can make appear trigonometric functions in their * exact output. Their argument will be different with the angle unit * preferences but both input and approximate output will be the same. * For example, i^(sqrt(3)) = cos(sqrt(3)*pi/2)+i*sin(sqrt(3)*pi/2) if * angle unit is radian and i^(sqrt(3)) = cos(sqrt(3)*90+i*sin(sqrt(3)*90) * in degree. */ - && strcmp(m_exactOutputText, c.m_exactOutputText) == 0; + && strcmp(exactOutputText(), c.exactOutputText()) == 0; } -void Calculation::reset() { - m_inputText[0] = 0; - m_exactOutputText[0] = 0; - m_approximateOutputText[0] = 0; - tidy(); -} - -void Calculation::setContent(const char * c, Context * context, Expression ansExpression) { - reset(); - { - Symbol ansSymbol = Symbol::Ans(); - Expression input = Expression::Parse(c).replaceSymbolWithExpression(ansSymbol, ansExpression); - /* We do not store directly the text enter by the user because we do not want - * to keep Ans symbol in the calculation store. */ - PoincareHelpers::Serialize(input, m_inputText, sizeof(m_inputText)); +Calculation * Calculation::next() const { + const char * result = reinterpret_cast(this) + sizeof(Calculation); + for (int i = 0; i < 3; i++) { + result = result + strlen(result) + 1; // Pass inputText, exactOutputText, ApproximateOutputText } - Expression exactOutput; - Expression approximateOutput; - PoincareHelpers::ParseAndSimplifyAndApproximate(m_inputText, &exactOutput, &approximateOutput, *context); /// FALSE TO DISABLE LITERAL CALCULATION - PoincareHelpers::Serialize(exactOutput, m_exactOutputText, sizeof(m_exactOutputText)); - PoincareHelpers::Serialize(approximateOutput, m_approximateOutputText, sizeof(m_approximateOutputText)); + return reinterpret_cast(const_cast(result)); } -KDCoordinate Calculation::height(Context * context, bool expanded) { - KDCoordinate * memoizedHeight = expanded ? &m_expandedHeight : &m_height; - if (*memoizedHeight < 0) { - DisplayOutput display = displayOutput(context); - Layout inputLayout = createInputLayout(); - KDCoordinate inputHeight = inputLayout.layoutSize().height(); - if (display == DisplayOutput::ExactOnly) { - KDCoordinate exactOutputHeight = createExactOutputLayout().layoutSize().height(); - *memoizedHeight = inputHeight+exactOutputHeight; - } else if (display == DisplayOutput::ApproximateOnly || (!expanded && display == DisplayOutput::ExactAndApproximateToggle)) { - KDCoordinate approximateOutputHeight = createApproximateOutputLayout(context).layoutSize().height(); - *memoizedHeight = inputHeight+approximateOutputHeight; - } else { - assert(display == DisplayOutput::ExactAndApproximate || (display == DisplayOutput::ExactAndApproximateToggle && expanded)); - Layout approximateLayout = createApproximateOutputLayout(context); - Layout exactLayout = createExactOutputLayout(); - KDCoordinate approximateOutputHeight = approximateLayout.layoutSize().height(); - KDCoordinate exactOutputHeight = exactLayout.layoutSize().height(); - KDCoordinate outputHeight = maxCoordinate(exactLayout.baseline(), approximateLayout.baseline()) + maxCoordinate(exactOutputHeight-exactLayout.baseline(), approximateOutputHeight-approximateLayout.baseline()); - *memoizedHeight = inputHeight + outputHeight; - } - /* For all display output except ExactAndApproximateToggle, the selected - * height and the usual height are identical. We update both heights in - * theses cases. */ - if (display != DisplayOutput::ExactAndApproximateToggle) { - m_height = *memoizedHeight; - m_expandedHeight = *memoizedHeight; - } - } - return *memoizedHeight; +void Calculation::tidy() { + /* Reset height memoization (the complex format could have changed when + * re-entering Calculation app which would impact the heights). */ + m_height = -1; + m_expandedHeight = -1; } -const char * Calculation::inputText() { - return m_inputText; -} - -const char * Calculation::exactOutputText() { - return m_exactOutputText; -} - -const char * Calculation::approximateOutputText() { - return m_approximateOutputText; +const char * Calculation::approximateOutputText() const { + const char * exactOutput = exactOutputText(); + return exactOutput + strlen(exactOutput) + 1; } Expression Calculation::input() { return Expression::Parse(m_inputText); } -Layout Calculation::createInputLayout() { - return input().createLayout(Preferences::PrintFloatMode::Decimal, PrintFloat::k_numberOfStoredSignificantDigits); -} - -bool Calculation::isEmpty() { - /* To test if a calculation is empty, we need to test either m_inputText or - * m_exactOutputText or m_approximateOutputText, the only three fields that - * are not lazy-loaded. We choose m_exactOutputText to consider that a - * calculation being added is still empty until the end of the method - * 'setContent'. Indeed, during 'setContent' method, 'ans' evaluation calls - * the evaluation of the last calculation only if the calculation being - * filled is not taken into account.*/ - if (strlen(m_approximateOutputText) == 0) { - return true; - } - return false; -} - -void Calculation::tidy() { - /* Uninitialized all Expression stored to free the Pool */ - m_displayOutput = DisplayOutput::Unknown; - m_height = -1; - m_expandedHeight = -1; - m_equalSign = EqualSign::Unknown; -} - Expression Calculation::exactOutput() { /* Because the angle unit might have changed, we do not simplify again. We * thereby avoid turning cos(Pi/4) into sqrt(2)/2 and displaying * 'sqrt(2)/2 = 0.999906' (which is totally wrong) instead of * 'cos(pi/4) = 0.999906' (which is true in degree). */ - Expression exactOutput = Expression::Parse(m_exactOutputText); - if (exactOutput.isUninitialized()) { - return Undefined::Builder(); - } + Expression exactOutput = Expression::Parse(exactOutputText()); + assert(!exactOutput.isUninitialized()); return exactOutput; } +Expression Calculation::approximateOutput(Context * context) { + /* To ensure that the expression 'm_output' is a matrix or a complex, we + * call 'evaluate'. */ + Expression exp = Expression::Parse(approximateOutputText()); + assert(!exp.isUninitialized()); + return PoincareHelpers::Approximate(exp, context); +} + +Layout Calculation::createInputLayout() { + return input().createLayout(Preferences::PrintFloatMode::Decimal, PrintFloat::k_numberOfStoredSignificantDigits); +} + Layout Calculation::createExactOutputLayout() { return PoincareHelpers::CreateLayout(exactOutput()); } -Expression Calculation::approximateOutput(Context * context) { - /* To ensure that the expression 'm_output' is a matrix or a complex, we - * call 'evaluate'. */ - Expression exp = Expression::Parse(m_approximateOutputText); - if (exp.isUninitialized()) { - /* TODO: exp might be uninitialized because the serialization did not fit in - * the buffer. Put a special error instead of "undef". */ - return Undefined::Builder(); - } - return PoincareHelpers::Approximate(exp, *context); -} - Layout Calculation::createApproximateOutputLayout(Context * context) { return PoincareHelpers::CreateLayout(approximateOutput(context)); } +KDCoordinate Calculation::height(Context * context, bool expanded) { + KDCoordinate result = expanded ? m_expandedHeight : m_height; + if (result < 0) { + DisplayOutput display = displayOutput(context); + Layout inputLayout = createInputLayout(); + KDCoordinate inputHeight = inputLayout.layoutSize().height(); + if (display == DisplayOutput::ExactOnly) { + KDCoordinate exactOutputHeight = createExactOutputLayout().layoutSize().height(); + result = inputHeight+exactOutputHeight; + } else if (display == DisplayOutput::ApproximateOnly || (!expanded && display == DisplayOutput::ExactAndApproximateToggle)) { + KDCoordinate approximateOutputHeight = createApproximateOutputLayout(context).layoutSize().height(); + result = inputHeight+approximateOutputHeight; + } else { + assert(display == DisplayOutput::ExactAndApproximate || (display == DisplayOutput::ExactAndApproximateToggle && expanded)); + Layout approximateLayout = createApproximateOutputLayout(context); + Layout exactLayout = createExactOutputLayout(); + KDCoordinate approximateOutputHeight = approximateLayout.layoutSize().height(); + KDCoordinate exactOutputHeight = exactLayout.layoutSize().height(); + KDCoordinate outputHeight = maxCoordinate(exactLayout.baseline(), approximateLayout.baseline()) + maxCoordinate(exactOutputHeight-exactLayout.baseline(), approximateOutputHeight-approximateLayout.baseline()); + result = inputHeight + outputHeight; + } + /* For all display output except ExactAndApproximateToggle, the selected + * height and the usual height are identical. We update both heights in + * theses cases. */ + if (display != DisplayOutput::ExactAndApproximateToggle) { + m_height = result; + m_expandedHeight = result; + } else { + if (expanded) { + m_expandedHeight = result; + } else { + m_height = result; + } + } + } + return result; +} + Calculation::DisplayOutput Calculation::displayOutput(Context * context) { if (m_displayOutput != DisplayOutput::Unknown) { return m_displayOutput; @@ -173,26 +124,45 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) { if (shouldOnlyDisplayExactOutput()) { m_displayOutput = DisplayOutput::ExactOnly; } else if (input().recursivelyMatches( - [](const Expression e, Context & c) { - /* If the input contains: - * - Random - * - Round - * or involves a Matrix, we only display the approximate output. */ - ExpressionNode::Type t = e.type(); - return (t == ExpressionNode::Type::Random) || (t == ExpressionNode::Type::Round) || Expression::IsMatrix(e, c); - }, - *context, true)) + [](const Expression e, Context * c) { + constexpr int approximateOnlyTypesCount = 9; + /* If the input contains the following types, we only display the + * approximate output. */ + ExpressionNode::Type approximateOnlyTypes[approximateOnlyTypesCount] = { + ExpressionNode::Type::Random, + ExpressionNode::Type::Round, + ExpressionNode::Type::FracPart, + ExpressionNode::Type::Integral, + ExpressionNode::Type::Product, + ExpressionNode::Type::Sum, + ExpressionNode::Type::Derivative, + ExpressionNode::Type::ConfidenceInterval, + ExpressionNode::Type::PredictionInterval + }; + return e.isOfType(approximateOnlyTypes, approximateOnlyTypesCount); + }, context, true)) { m_displayOutput = DisplayOutput::ApproximateOnly; - } else if (strcmp(m_exactOutputText, m_approximateOutputText) == 0) { + } else if (strcmp(exactOutputText(), approximateOutputText()) == 0) { /* If the exact and approximate results' texts are equal and their layouts * too, do not display the exact result. If the two layouts are not equal * because of the number of significant digits, we display both. */ m_displayOutput = exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal ? DisplayOutput::ApproximateOnly : DisplayOutput::ExactAndApproximate; - } else if (strcmp(m_exactOutputText, Undefined::Name()) == 0 || strcmp(m_approximateOutputText, Unreal::Name()) == 0) { + } else if (strcmp(exactOutputText(), Undefined::Name()) == 0 + || strcmp(approximateOutputText(), Unreal::Name()) == 0 + || exactOutput().type() == ExpressionNode::Type::Undefined) + { // If the approximate result is 'unreal' or the exact result is 'undef' m_displayOutput = DisplayOutput::ApproximateOnly; - } else if (input().recursivelyMatches(Expression::IsApproximate, *context) || exactOutput().recursivelyMatches(Expression::IsApproximate, *context)) { + } else if (strcmp(approximateOutputText(), Undefined::Name()) == 0 + && strcmp(inputText(), exactOutputText()) == 0) + { + /* If the approximate result is 'undef' and the input and exactOutput are + * equal */ + m_displayOutput = DisplayOutput::ApproximateOnly; + } else if (input().recursivelyMatches(Expression::IsApproximate, context) + || exactOutput().recursivelyMatches(Expression::IsApproximate, context)) + { m_displayOutput = DisplayOutput::ExactAndApproximateToggle; } else { m_displayOutput = DisplayOutput::ExactAndApproximate; @@ -203,24 +173,41 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) { bool Calculation::shouldOnlyDisplayExactOutput() { /* If the input is a "store in a function", do not display the approximate * result. This prevents x->f(x) from displaying x = undef. */ - return input().type() == ExpressionNode::Type::Store - && input().childAtIndex(1).type() == ExpressionNode::Type::Function; + Expression i = input(); + return i.type() == ExpressionNode::Type::Store + && i.childAtIndex(1).type() == ExpressionNode::Type::Function; } Calculation::EqualSign Calculation::exactAndApproximateDisplayedOutputsAreEqual(Poincare::Context * context) { if (m_equalSign != EqualSign::Unknown) { return m_equalSign; } - constexpr int bufferSize = Constant::MaxSerializedExpressionSize; - char buffer[bufferSize]; - Preferences * preferences = Preferences::sharedPreferences(); - Expression exactOutputExpression = PoincareHelpers::ParseAndSimplify(m_exactOutputText, *context); /// FALSE TO DISABLE LITERAL CALCULATION - if (exactOutputExpression.isUninitialized()) { - exactOutputExpression = Undefined::Builder(); + /* Displaying the right equal symbol is less important than displaying a + * result, so we do not want exactAndApproximateDisplayedOutputsAreEqual to + * create a pool failure that would prevent from displaying a result that we + * managed to compute. We thus encapsulate the method in an exception + * checkpoint: if there was not enough memory on the pool to compute the equal + * sign, just return EqualSign::Approximation. + * We can safely use an exception checkpoint here because we are sure of not + * modifying any pre-existing node in the pool. We are sure there cannot be a + * Store in the exactOutput. */ + Poincare::ExceptionCheckpoint ecp; + if (ExceptionRun(ecp)) { + constexpr int bufferSize = Constant::MaxSerializedExpressionSize + 30; + char buffer[bufferSize]; + Preferences * preferences = Preferences::sharedPreferences(); + Expression exactOutputExpression = PoincareHelpers::ParseAndSimplify(exactOutputText(), context, false); + if (exactOutputExpression.isUninitialized()) { + exactOutputExpression = Undefined::Builder(); + } + Preferences::ComplexFormat complexFormat = Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), m_inputText); + m_equalSign = exactOutputExpression.isEqualToItsApproximationLayout(approximateOutput(context), buffer, bufferSize, complexFormat, preferences->angleUnit(), preferences->displayMode(), preferences->numberOfSignificantDigits(), context) ? EqualSign::Equal : EqualSign::Approximation; + return m_equalSign; + } else { + /* Do not override m_equalSign in case there is enough room in the pool + * later to compute it. */ + return EqualSign::Approximation; } - Preferences::ComplexFormat complexFormat = Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), m_inputText); - m_equalSign = exactOutputExpression.isEqualToItsApproximationLayout(approximateOutput(context), buffer, bufferSize, complexFormat, preferences->angleUnit(), preferences->displayMode(), preferences->numberOfSignificantDigits(), *context) ? EqualSign::Equal : EqualSign::Approximation; - return m_equalSign; } } diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index ff00be74c..89dbd52a1 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -10,6 +10,13 @@ namespace Calculation { class CalculationStore; + +/* A calculation is: + * | uint8_t |KDCoordinate| KDCoordinate | uint8_t | ... | ... | ... | + * |m_displayOutput| m_height |m_expandedHeight|m_equalSign|m_inputText|m_exactOuputText|m_approximateOuputText| + * + * */ + class Calculation { public: enum class EqualSign : uint8_t { @@ -26,23 +33,41 @@ public: ExactAndApproximateToggle }; - Calculation(); + /* It is not really the minimal size, but it clears enough space for most + * calculations instead of clearing less space, then fail to serialize, clear + * more space, fail to serialize, clear more space, etc., until reaching + * sufficient free space. */ + static int MinimalSize() { return sizeof(uint8_t) + 2*sizeof(KDCoordinate) + sizeof(uint8_t) + 3*Constant::MaxSerializedExpressionSize; } + + Calculation() : + m_displayOutput(DisplayOutput::Unknown), + m_height(-1), + m_expandedHeight(-1), + m_equalSign(EqualSign::Unknown) + { + assert(sizeof(m_inputText) == 0); + } bool operator==(const Calculation& c); - /* c.reset() is the equivalent of c = Calculation() without copy assingment. */ - void reset(); - void setContent(const char * c, Poincare::Context * context, Poincare::Expression ansExpression); - KDCoordinate height(Poincare::Context * context, bool expanded = false); - const char * inputText(); - const char * exactOutputText(); - const char * approximateOutputText(); + Calculation * next() const; + + void tidy(); + + // Texts + const char * inputText() const { return m_inputText; } + const char * exactOutputText() const { return m_inputText + strlen(m_inputText) + 1; } + const char * approximateOutputText() const; + + // Expressions Poincare::Expression input(); - Poincare::Layout createInputLayout(); - Poincare::Expression approximateOutput(Poincare::Context * context); Poincare::Expression exactOutput(); + Poincare::Expression approximateOutput(Poincare::Context * context); + + // Layouts + Poincare::Layout createInputLayout(); Poincare::Layout createExactOutputLayout(); Poincare::Layout createApproximateOutputLayout(Poincare::Context * context); - bool isEmpty(); - void tidy(); + + KDCoordinate height(Poincare::Context * context, bool expanded = false); DisplayOutput displayOutput(Poincare::Context * context); bool shouldOnlyDisplayExactOutput(); EqualSign exactAndApproximateDisplayedOutputsAreEqual(Poincare::Context * context); @@ -51,13 +76,11 @@ private: /* Buffers holding text expressions have to be longer than the text written * by user (of maximum length TextField::maxBufferSize()) because when we * print an expression we add omitted signs (multiplications, parenthesis...) */ - char m_inputText[Constant::MaxSerializedExpressionSize]; - char m_exactOutputText[Constant::MaxSerializedExpressionSize]; - char m_approximateOutputText[Constant::MaxSerializedExpressionSize]; DisplayOutput m_displayOutput; - KDCoordinate m_height; - KDCoordinate m_expandedHeight; + KDCoordinate m_height __attribute__((packed)); + KDCoordinate m_expandedHeight __attribute__((packed)); EqualSign m_equalSign; + char m_inputText[0]; // MUST be the last member variable }; } diff --git a/apps/calculation/calculation_store.cpp b/apps/calculation/calculation_store.cpp index 618439860..e8f224efd 100644 --- a/apps/calculation/calculation_store.cpp +++ b/apps/calculation/calculation_store.cpp @@ -1,88 +1,154 @@ #include "calculation_store.h" -#include +#include "../shared/poincare_helpers.h" #include #include +#include +#include + using namespace Poincare; +using namespace Shared; namespace Calculation { -Calculation * CalculationStore::push(const char * text, Context * context) { - Calculation * result = &m_calculations[m_startIndex]; - result->setContent(text, context, ansExpression(context)); - m_startIndex++; - if (m_startIndex >= k_maxNumberOfCalculations) { - m_startIndex = 0; - } - return result; +CalculationStore::CalculationStore() : + m_bufferEnd(m_buffer), + m_numberOfCalculations(0), + m_slidedBuffer(false), + m_indexOfFirstMemoizedCalculationPointer(0) +{ + resetMemoizedModelsAfterCalculationIndex(-1); } -Calculation * CalculationStore::calculationAtIndex(int i) { - int j = 0; - Calculation * currentCalc = &m_calculations[m_startIndex]; - Calculation * previousCalc = nullptr; - while (j <= i) { - if (!currentCalc++->isEmpty()) { - previousCalc = currentCalc - 1; - j++; - } - if (currentCalc >= m_calculations + k_maxNumberOfCalculations) { - currentCalc = m_calculations; +ExpiringPointer CalculationStore::calculationAtIndex(int i) { + assert(!m_slidedBuffer); + assert(i >= 0 && i < m_numberOfCalculations); + assert(m_indexOfFirstMemoizedCalculationPointer >= 0); + if (i >= m_indexOfFirstMemoizedCalculationPointer && i < m_indexOfFirstMemoizedCalculationPointer + k_numberOfMemoizedCalculationPointers) { + // The calculation is within the range of memoized calculations + Calculation * c = m_memoizedCalculationPointers[i-m_indexOfFirstMemoizedCalculationPointer]; + if (c != nullptr) { + // The pointer was memoized + return ExpiringPointer(c); } + c = bufferCalculationAtIndex(i); + m_memoizedCalculationPointers[i-m_indexOfFirstMemoizedCalculationPointer] = c; + return c; } - return previousCalc; + // Slide the memoization buffer + if (i >= m_indexOfFirstMemoizedCalculationPointer) { + // Slide the memoization buffer to the left + memmove(m_memoizedCalculationPointers, m_memoizedCalculationPointers+1, (k_numberOfMemoizedCalculationPointers - 1) * sizeof(Calculation *)); + m_memoizedCalculationPointers[k_numberOfMemoizedCalculationPointers - 1] = nullptr; + m_indexOfFirstMemoizedCalculationPointer++; + } else { + // Slide the memoization buffer to the right + memmove(m_memoizedCalculationPointers+1, m_memoizedCalculationPointers, (k_numberOfMemoizedCalculationPointers - 1) * sizeof(Calculation *)); + m_memoizedCalculationPointers[0] = nullptr; + m_indexOfFirstMemoizedCalculationPointer--; + } + return calculationAtIndex(i); } -int CalculationStore::numberOfCalculations() { - Calculation * currentCalc= m_calculations; - int numberOfCalculations = 0; - while (currentCalc < m_calculations + k_maxNumberOfCalculations) { - if (!currentCalc++->isEmpty()) { - numberOfCalculations++; +ExpiringPointer CalculationStore::push(const char * text, Context * context) { + /* Compute ans now, before the buffer is slided and before the calculation + * might be deleted */ + Expression ans = ansExpression(context); + + // Prepare the buffer for the new calculation + int minSize = Calculation::MinimalSize(); + assert(k_bufferSize > minSize); + while (remainingBufferSize() < minSize || m_numberOfCalculations > k_maxNumberOfCalculations) { + deleteLastCalculation(); + } + char * newCalculationsLocation = slideCalculationsToEndOfBuffer(); + char * nextSerializationLocation = m_buffer; + + // Add the beginning of the calculation + { + /* Copy the begining of the calculation. The calculation minimal size is + * available, so this memmove will not overide anything. */ + Calculation newCalc = Calculation(); + size_t calcSize = sizeof(newCalc); + memmove(nextSerializationLocation, &newCalc, calcSize); + nextSerializationLocation += calcSize; + } + + /* Add the input expression. + * We do not store directly the text entered by the user because we do not + * want to keep Ans symbol in the calculation store. */ + const char * inputSerialization = nextSerializationLocation; + { + Expression input = Expression::Parse(text).replaceSymbolWithExpression(Symbol::Ans(), ans); + if (!serializeExpression(input, nextSerializationLocation, &newCalculationsLocation)) { + /* If the input does not fit in the store (event if the current + * calculation is the only calculation), just replace the calculation with + * undef. */ + return emptyStoreAndPushUndef(context); + } + nextSerializationLocation += strlen(nextSerializationLocation) + 1; + } + + // Compute and serialize the outputs + { + Expression outputs[] = {Expression(), Expression()}; + PoincareHelpers::ParseAndSimplifyAndApproximate(inputSerialization, &(outputs[0]), &(outputs[1]), context, false); + for (int i = 0; i < 2; i++) { + if (!serializeExpression(outputs[i], nextSerializationLocation, &newCalculationsLocation)) { + /* If the exat/approximate output does not fit in the store (event if the + * current calculation is the only calculation), replace the output with + * undef if it fits, else replace the whole calcualtion with undef. */ + Expression undef = Undefined::Builder(); + if (!serializeExpression(undef, nextSerializationLocation, &newCalculationsLocation)) { + return emptyStoreAndPushUndef(context); + } + } + nextSerializationLocation += strlen(nextSerializationLocation) + 1; } } - return numberOfCalculations; + + // Restore the other calculations + size_t slideSize = m_buffer + k_bufferSize - newCalculationsLocation; + memcpy(nextSerializationLocation, newCalculationsLocation, slideSize); + m_slidedBuffer = false; + m_numberOfCalculations++; + m_bufferEnd+= nextSerializationLocation - m_buffer; + + // Clean the memoization + resetMemoizedModelsAfterCalculationIndex(-1); + + return ExpiringPointer(reinterpret_cast(m_buffer)); } void CalculationStore::deleteCalculationAtIndex(int i) { - int numberOfCalc = numberOfCalculations(); - assert(i >= 0 && i < numberOfCalc); - int indexFirstCalc = m_startIndex; - while (m_calculations[indexFirstCalc].isEmpty()) { - indexFirstCalc++; - if (indexFirstCalc == k_maxNumberOfCalculations) { - indexFirstCalc = 0; - } - assert(indexFirstCalc != m_startIndex); - } - int absoluteIndexCalculationI = indexFirstCalc+i; - absoluteIndexCalculationI = absoluteIndexCalculationI >= k_maxNumberOfCalculations ? absoluteIndexCalculationI - k_maxNumberOfCalculations : absoluteIndexCalculationI; - - int index = absoluteIndexCalculationI; - for (int k = i; k < numberOfCalc-1; k++) { - int nextIndex = index+1 >= k_maxNumberOfCalculations ? 0 : index+1; - m_calculations[index] = m_calculations[nextIndex]; - index++; - if (index == k_maxNumberOfCalculations) { - index = 0; - } - } - m_calculations[index].reset(); - m_startIndex--; - if (m_startIndex == -1) { - m_startIndex = k_maxNumberOfCalculations-1; - } + assert(i >= 0 && i < m_numberOfCalculations); + assert(!m_slidedBuffer); + ExpiringPointer calcI = calculationAtIndex(i); + char * nextCalc = reinterpret_cast(calcI->next()); + assert(m_bufferEnd >= nextCalc); + size_t slidingSize = m_bufferEnd - nextCalc; + memmove((char *)(calcI.pointer()), nextCalc, slidingSize); + m_bufferEnd -= (nextCalc - (char *)(calcI.pointer())); + m_numberOfCalculations--; + resetMemoizedModelsAfterCalculationIndex(i); } void CalculationStore::deleteAll() { - m_startIndex = 0; - for (int i = 0; i < k_maxNumberOfCalculations; i++) { - m_calculations[i].reset(); - } + /* We might call deleteAll because the app closed due to a pool allocation + * failure, so we cannot assert that m_slidedBuffer is false. */ + m_slidedBuffer = false; + m_bufferEnd = m_buffer; + m_numberOfCalculations = 0; + resetMemoizedModelsAfterCalculationIndex(-1); } void CalculationStore::tidy() { - for (int i = 0; i < k_maxNumberOfCalculations; i++) { - m_calculations[i].tidy(); + if (m_slidedBuffer) { + deleteAll(); + return; + } + resetMemoizedModelsAfterCalculationIndex(-1); + for (Calculation * c : *this) { + c->tidy(); } } @@ -90,19 +156,114 @@ Expression CalculationStore::ansExpression(Context * context) { if (numberOfCalculations() == 0) { return Rational::Builder(0); } - Calculation * lastCalculation = calculationAtIndex(numberOfCalculations()-1); + ExpiringPointer mostRecentCalculation = calculationAtIndex(0); /* Special case: the exact output is a Store/Equal expression. * Store/Equal expression can only be at the root of an expression. * To avoid turning 'ans->A' in '2->A->A' or '2=A->A' (which cannot be * parsed), ans is replaced by the approximation output when any Store or * Equal expression appears. */ - bool exactOuptutInvolvesStoreEqual = lastCalculation->exactOutput().recursivelyMatches([](const Expression e, Context & context) { + bool exactOuptutInvolvesStoreEqual = mostRecentCalculation->exactOutput().recursivelyMatches([](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Store || e.type() == ExpressionNode::Type::Equal; - }, *context, false); - if (lastCalculation->input().recursivelyMatches(Expression::IsApproximate, *context) || exactOuptutInvolvesStoreEqual) { - return lastCalculation->approximateOutput(context); + }, context, false); + if (mostRecentCalculation->input().recursivelyMatches(Expression::IsApproximate, context) || exactOuptutInvolvesStoreEqual) { + return mostRecentCalculation->approximateOutput(context); + } + return mostRecentCalculation->exactOutput(); +} + +Calculation * CalculationStore::bufferCalculationAtIndex(int i) { + int currentIndex = 0; + for (Calculation * c : *this) { + if (currentIndex == i) { + return c; + } + currentIndex++; + } + assert(false); + return nullptr; +} + +bool CalculationStore::serializeExpression(Expression e, char * location, char * * newCalculationsLocation) { + assert(m_slidedBuffer); + return pushExpression( + [](char * location, size_t locationSize, void * e) { + return PoincareHelpers::Serialize(*(Expression *)e, location, locationSize) < (int)locationSize-1; + }, &e, location, newCalculationsLocation); +} + +char * CalculationStore::slideCalculationsToEndOfBuffer() { + int calculationsSize = m_bufferEnd - m_buffer; + char * calculationsNewPosition = m_buffer + k_bufferSize - calculationsSize; + memmove(calculationsNewPosition, m_buffer, calculationsSize); + m_slidedBuffer = true; + return calculationsNewPosition; +} + +size_t CalculationStore::deleteLastCalculation(const char * calculationsStart) { + assert(m_numberOfCalculations > 0); + size_t result; + if (!m_slidedBuffer) { + assert(calculationsStart == nullptr); + const char * previousBufferEnd = m_bufferEnd; + m_bufferEnd = lastCalculationPosition(m_buffer); + assert(previousBufferEnd > m_bufferEnd); + result = previousBufferEnd - m_bufferEnd; + } else { + assert(calculationsStart != nullptr); + const char * lastCalc = lastCalculationPosition(calculationsStart); + assert(*lastCalc == 0); + result = m_buffer + k_bufferSize - lastCalc; + memmove(const_cast(calculationsStart + result), calculationsStart, m_buffer + k_bufferSize - calculationsStart - result); + } + m_numberOfCalculations--; + resetMemoizedModelsAfterCalculationIndex(-1); + return result; +} + +const char * CalculationStore::lastCalculationPosition(const char * calculationsStart) const { + assert(calculationsStart >= m_buffer && calculationsStart < m_buffer + k_bufferSize); + Calculation * c = reinterpret_cast(const_cast(calculationsStart)); + int calculationIndex = 0; + while (calculationIndex < m_numberOfCalculations - 1) { + c = c->next(); + calculationIndex++; + } + return reinterpret_cast(c); +} + +bool CalculationStore::pushExpression(ValueCreator valueCreator, Expression * expression, char * location, char * * newCalculationsLocation) { + assert(*newCalculationsLocation <= m_buffer + k_bufferSize); + bool expressionIsPushed = false; + while (true) { + expressionIsPushed = valueCreator(location, *newCalculationsLocation - location, expression); + if (expressionIsPushed || *newCalculationsLocation >= m_buffer + k_bufferSize) { + break; + } + *newCalculationsLocation = *newCalculationsLocation + deleteLastCalculation(); + assert(*newCalculationsLocation <= m_buffer + k_bufferSize); + } + return expressionIsPushed; +} + +Shared::ExpiringPointer CalculationStore::emptyStoreAndPushUndef(Context * context) { + /* We end up here as a result of a failed calculation push. The store + * attributes are not necessarily clean, so we need to reset them. */ + m_slidedBuffer = false; + deleteAll(); + return push(Undefined::Name(), context); +} + +void CalculationStore::resetMemoizedModelsAfterCalculationIndex(int index) { + if (index < m_indexOfFirstMemoizedCalculationPointer) { + memset(&m_memoizedCalculationPointers, 0, k_numberOfMemoizedCalculationPointers * sizeof(Calculation *)); + return; + } + if (index >= m_indexOfFirstMemoizedCalculationPointer + k_numberOfMemoizedCalculationPointers) { + return; + } + for (int i = index - m_indexOfFirstMemoizedCalculationPointer; i < k_numberOfMemoizedCalculationPointers; i++) { + m_memoizedCalculationPointers[i] = nullptr; } - return lastCalculation->exactOutput(); } } diff --git a/apps/calculation/calculation_store.h b/apps/calculation/calculation_store.h index 9eeb3bd86..0103ce08d 100644 --- a/apps/calculation/calculation_store.h +++ b/apps/calculation/calculation_store.h @@ -2,23 +2,77 @@ #define CALCULATION_CALCULATION_STORE_H #include "calculation.h" +#include namespace Calculation { +/* To optimize the storage space, we use one big buffer for all calculations. + * + * The previous solution was to keep 10 calculations, each containing 3 buffers + * (for input and outputs). To optimize the storage, we then wanted to put all + * outputs in a cache where they could be deleted to add a new entry, and + * recomputed on cache miss. However, the computation depends too much on the + * state of the memory for this to be possible. For instance: + * 6->a + * a+1 + * Perform some big computations that remove a+1 from the cache + * Delete a from the variable box. + * Scroll up to display a+1 : a does not exist anymore so the outputs won't be + * recomputed correctly. + * + * Now we do not cap the number of calculations and just delete the oldests to + * create space for a new calculation. */ + class CalculationStore { public: - CalculationStore() : m_startIndex(0) {} - Calculation * calculationAtIndex(int i); - Calculation * push(const char * text, Poincare::Context * context); + CalculationStore(); + Shared::ExpiringPointer calculationAtIndex(int i); + Shared::ExpiringPointer push(const char * text, Poincare::Context * context); void deleteCalculationAtIndex(int i); void deleteAll(); - int numberOfCalculations(); - void tidy(); + int numberOfCalculations() const { return m_numberOfCalculations; } Poincare::Expression ansExpression(Poincare::Context * context); - static constexpr int k_maxNumberOfCalculations = 10; + void tidy(); private: - int m_startIndex; - Calculation m_calculations[k_maxNumberOfCalculations]; + static constexpr int k_maxNumberOfCalculations = 25; + static constexpr int k_bufferSize = 10 * 3 * Constant::MaxSerializedExpressionSize; + + class CalculationIterator { + public: + CalculationIterator(const char * c) : m_calculation(reinterpret_cast(const_cast(c))) {} + Calculation * operator*() { return m_calculation; } + bool operator!=(const CalculationIterator& it) const { return (m_calculation != it.m_calculation); } + CalculationIterator & operator++() { + m_calculation = m_calculation->next(); + return *this; + } + protected: + Calculation * m_calculation; + }; + + CalculationIterator begin() const { return CalculationIterator(m_buffer); } + CalculationIterator end() const { return CalculationIterator(m_bufferEnd); } + + Calculation * bufferCalculationAtIndex(int i); + int remainingBufferSize() const { assert(m_bufferEnd >= m_buffer); return k_bufferSize - (m_bufferEnd - m_buffer); } + bool serializeExpression(Poincare::Expression e, char * location, char * * newCalculationsLocation); + char * slideCalculationsToEndOfBuffer(); // returns the new position of the calculations + size_t deleteLastCalculation(const char * calculationsStart = nullptr); + const char * lastCalculationPosition(const char * calculationsStart) const; + typedef bool (*ValueCreator)(char * location, size_t locationSize, void * e); + bool pushExpression(ValueCreator valueCrator, Poincare::Expression * expression, char * location, char * * newCalculationsLocation); + Shared::ExpiringPointer emptyStoreAndPushUndef(Poincare::Context * context); + + char m_buffer[k_bufferSize]; + const char * m_bufferEnd; + int m_numberOfCalculations; + bool m_slidedBuffer; + + // Memoization + static constexpr int k_numberOfMemoizedCalculationPointers = 10; + void resetMemoizedModelsAfterCalculationIndex(int index); + int m_indexOfFirstMemoizedCalculationPointer; + mutable Calculation * m_memoizedCalculationPointers[k_numberOfMemoizedCalculationPointers]; }; } diff --git a/apps/calculation/edit_expression_controller.cpp b/apps/calculation/edit_expression_controller.cpp index 116cf869f..f631b8f84 100644 --- a/apps/calculation/edit_expression_controller.cpp +++ b/apps/calculation/edit_expression_controller.cpp @@ -1,6 +1,5 @@ #include "edit_expression_controller.h" #include "app.h" -#include "../apps_container.h" #include #include #include @@ -13,9 +12,8 @@ namespace Calculation { EditExpressionController::ContentView::ContentView(Responder * parentResponder, TableView * subview, InputEventHandlerDelegate * inputEventHandlerDelegate, TextFieldDelegate * textFieldDelegate, LayoutFieldDelegate * layoutFieldDelegate) : View(), m_mainView(subview), - m_expressionField(parentResponder, m_textBody, k_bufferLength, inputEventHandlerDelegate, textFieldDelegate, layoutFieldDelegate) + m_expressionField(parentResponder, inputEventHandlerDelegate, textFieldDelegate, layoutFieldDelegate) { - m_textBody[0] = 0; } View * EditExpressionController::ContentView::subviewAtIndex(int index) { @@ -62,7 +60,7 @@ void EditExpressionController::didBecomeFirstResponder() { int lastRow = m_calculationStore->numberOfCalculations() > 0 ? m_calculationStore->numberOfCalculations()-1 : 0; m_historyController->scrollToCell(0, lastRow); ((ContentView *)view())->expressionField()->setEditing(true, false); - app()->setFirstResponder(((ContentView *)view())->expressionField()); + Container::activeApp()->setFirstResponder(((ContentView *)view())->expressionField()); } bool EditExpressionController::textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) { @@ -109,14 +107,6 @@ void EditExpressionController::layoutFieldDidChangeSize(::LayoutField * layoutFi } } -TextFieldDelegateApp * EditExpressionController::textFieldDelegateApp() { - return (App *)app(); -} - -ExpressionFieldDelegateApp * EditExpressionController::expressionFieldDelegateApp() { - return (App *)app(); -} - void EditExpressionController::reloadView() { ((ContentView *)view())->reload(); m_historyController->reload(); @@ -127,14 +117,13 @@ void EditExpressionController::reloadView() { bool EditExpressionController::inputViewDidReceiveEvent(Ion::Events::Event event, bool shouldDuplicateLastCalculation) { if (shouldDuplicateLastCalculation && m_cacheBuffer[0] != 0) { - App * calculationApp = (App *)app(); - /* The input text store in m_cacheBuffer might have beed correct the first + /* The input text store in m_cacheBuffer might have been correct the first * time but then be too long when replacing ans in another context */ - if (!calculationApp->isAcceptableText(m_cacheBuffer)) { - calculationApp->displayWarning(I18n::Message::SyntaxError); + Shared::TextFieldDelegateApp * myApp = textFieldDelegateApp(); + if (!myApp->isAcceptableText(m_cacheBuffer)) { return true; } - m_calculationStore->push(m_cacheBuffer, calculationApp->localContext()); + m_calculationStore->push(m_cacheBuffer, myApp->localContext()); m_historyController->reload(); ((ContentView *)view())->mainView()->scrollToCell(0, m_historyController->numberOfRows()-1); return true; @@ -143,7 +132,7 @@ bool EditExpressionController::inputViewDidReceiveEvent(Ion::Events::Event event if (m_calculationStore->numberOfCalculations() > 0) { m_cacheBuffer[0] = 0; ((ContentView *)view())->expressionField()->setEditing(false, false); - app()->setFirstResponder(m_historyController); + Container::activeApp()->setFirstResponder(m_historyController); } return true; } @@ -152,14 +141,13 @@ bool EditExpressionController::inputViewDidReceiveEvent(Ion::Events::Event event bool EditExpressionController::inputViewDidFinishEditing(const char * text, Layout layoutR) { - App * calculationApp = (App *)app(); if (layoutR.isUninitialized()) { assert(text); strlcpy(m_cacheBuffer, text, k_cacheBufferSize); } else { layoutR.serializeParsedExpression(m_cacheBuffer, k_cacheBufferSize); } - m_calculationStore->push(m_cacheBuffer, calculationApp->localContext()); + m_calculationStore->push(m_cacheBuffer, textFieldDelegateApp()->localContext()); m_historyController->reload(); ((ContentView *)view())->mainView()->scrollToCell(0, m_historyController->numberOfRows()-1); ((ContentView *)view())->expressionField()->setEditing(true, true); diff --git a/apps/calculation/edit_expression_controller.h b/apps/calculation/edit_expression_controller.h index 1b80d8d48..159679468 100644 --- a/apps/calculation/edit_expression_controller.h +++ b/apps/calculation/edit_expression_controller.h @@ -44,17 +44,13 @@ private: View * subviewAtIndex(int index) override; void layoutSubviews() override; private: - static constexpr int k_bufferLength = TextField::maxBufferSize(); TableView * m_mainView; - char m_textBody[k_bufferLength]; ExpressionField m_expressionField; }; void reloadView(); bool inputViewDidReceiveEvent(Ion::Events::Event event, bool shouldDuplicateLastCalculation); bool inputViewDidFinishEditing(const char * text, Poincare::Layout layoutR); bool inputViewDidAbortEditing(const char * text); - Shared::TextFieldDelegateApp * textFieldDelegateApp() override; - Shared::ExpressionFieldDelegateApp * expressionFieldDelegateApp() override; static constexpr int k_cacheBufferSize = Constant::MaxSerializedExpressionSize; char m_cacheBuffer[k_cacheBufferSize]; HistoryController * m_historyController; diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index 3b75f6ee6..ff8025660 100644 --- a/apps/calculation/history_controller.cpp +++ b/apps/calculation/history_controller.cpp @@ -1,6 +1,5 @@ #include "history_controller.h" #include "app.h" -#include "../apps_container.h" #include using namespace Shared; @@ -25,7 +24,7 @@ void HistoryController::reload() { void HistoryController::didBecomeFirstResponder() { selectCellAtLocation(0, numberOfRows()-1); - app()->setFirstResponder(&m_selectableTableView); + Container::activeApp()->setFirstResponder(&m_selectableTableView); } void HistoryController::willExitResponderChain(Responder * nextFirstResponder) { @@ -37,7 +36,7 @@ void HistoryController::willExitResponderChain(Responder * nextFirstResponder) { bool HistoryController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::Down) { m_selectableTableView.deselectTable(); - app()->setFirstResponder(parentResponder()); + Container::activeApp()->setFirstResponder(parentResponder()); return true; } if (event == Ion::Events::Up) { @@ -49,8 +48,8 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { SubviewType subviewType = selectedSubviewType(); EditExpressionController * editController = (EditExpressionController *)parentResponder(); m_selectableTableView.deselectTable(); - app()->setFirstResponder(editController); - Calculation * calculation = m_calculationStore->calculationAtIndex(focusRow); + Container::activeApp()->setFirstResponder(editController); + Shared::ExpiringPointer calculation = calculationAtIndex(focusRow); if (subviewType == SubviewType::Input) { editController->insertTextBody(calculation->inputText()); } else { @@ -70,10 +69,10 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { SubviewType subviewType = selectedSubviewType(); m_selectableTableView.deselectTable(); EditExpressionController * editController = (EditExpressionController *)parentResponder(); - m_calculationStore->deleteCalculationAtIndex(focusRow); + m_calculationStore->deleteCalculationAtIndex(storeIndex(focusRow)); reload(); if (numberOfRows()== 0) { - app()->setFirstResponder(editController); + Container::activeApp()->setFirstResponder(editController); return true; } if (focusRow > 0) { @@ -93,18 +92,22 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { m_selectableTableView.deselectTable(); m_calculationStore->deleteAll(); reload(); - app()->setFirstResponder(parentResponder()); + Container::activeApp()->setFirstResponder(parentResponder()); return true; } if (event == Ion::Events::Back) { EditExpressionController * editController = (EditExpressionController *)parentResponder(); m_selectableTableView.deselectTable(); - app()->setFirstResponder(editController); + Container::activeApp()->setFirstResponder(editController); return true; } return false; } +Shared::ExpiringPointer HistoryController::calculationAtIndex(int i) { + return m_calculationStore->calculationAtIndex(storeIndex(i)); +} + void HistoryController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { if (withinTemporarySelection || previousSelectedCellY == selectedRow()) { return; @@ -121,10 +124,10 @@ void HistoryController::tableViewDidChangeSelection(SelectableTableView * t, int if (selectedCell == nullptr) { return; } - app()->setFirstResponder(selectedCell); + Container::activeApp()->setFirstResponder(selectedCell); } -int HistoryController::numberOfRows() { +int HistoryController::numberOfRows() const { return m_calculationStore->numberOfCalculations(); }; @@ -142,7 +145,7 @@ int HistoryController::reusableCellCount(int type) { void HistoryController::willDisplayCellForIndex(HighlightCell * cell, int index) { HistoryViewCell * myCell = (HistoryViewCell *)cell; - myCell->setCalculation(m_calculationStore->calculationAtIndex(index), index == selectedRow() && selectedSubviewType() == SubviewType::Output); + myCell->setCalculation(calculationAtIndex(index).pointer(), index == selectedRow() && selectedSubviewType() == SubviewType::Output); myCell->setEven(index%2 == 0); myCell->setHighlighted(myCell->isHighlighted()); } @@ -151,9 +154,8 @@ KDCoordinate HistoryController::rowHeight(int j) { if (j >= m_calculationStore->numberOfCalculations()) { return 0; } - Calculation * calculation = m_calculationStore->calculationAtIndex(j); - App * calculationApp = (App *)app(); - return calculation->height(calculationApp->localContext(), j == selectedRow() && selectedSubviewType() == SubviewType::Output) + 4 * Metric::CommonSmallMargin; + Shared::ExpiringPointer calculation = calculationAtIndex(j); + return calculation->height(App::app()->localContext(), j == selectedRow() && selectedSubviewType() == SubviewType::Output) + 4 * Metric::CommonSmallMargin; } int HistoryController::typeAtLocation(int i, int j) { diff --git a/apps/calculation/history_controller.h b/apps/calculation/history_controller.h index 67caf5ec9..f4d1d61fb 100644 --- a/apps/calculation/history_controller.h +++ b/apps/calculation/history_controller.h @@ -18,7 +18,7 @@ public: void didBecomeFirstResponder() override; void willExitResponderChain(Responder * nextFirstResponder) override; void reload(); - int numberOfRows() override; + int numberOfRows() const override; HighlightCell * reusableCell(int index, int type) override; int reusableCellCount(int type) override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; @@ -27,6 +27,8 @@ public: void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false) override; void scrollToCell(int i, int j); private: + int storeIndex(int i) { return numberOfRows() - i - 1; } + Shared::ExpiringPointer calculationAtIndex(int i); CalculationSelectableTableView * selectableTableView(); void historyViewCellDidChangeSelection() override; constexpr static int k_maxNumberOfDisplayedRows = 5; diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index f886a2674..a28d2c477 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -28,11 +28,12 @@ void HistoryViewCellDataSource::setSelectedSubviewType(SubviewType subviewType, HistoryViewCell::HistoryViewCell(Responder * parentResponder) : Responder(parentResponder), - m_calculation(), + m_calculationDisplayOutput(Calculation::DisplayOutput::Unknown), m_calculationExpanded(false), m_inputView(this), m_scrollableOutputView(this) { + m_calculationCRC32 = 0; } Shared::ScrollableExactApproximateExpressionsView * HistoryViewCell::outputView() { @@ -75,14 +76,14 @@ void HistoryViewCell::reloadScroll() { } void HistoryViewCell::reloadOutputSelection() { - App * calculationApp = (App *)app(); - Calculation::DisplayOutput display = m_calculation.displayOutput(calculationApp->localContext()); /* Select the right output according to the calculation display output. This * will reload the scroll to display the selected output. */ - if (display == Calculation::DisplayOutput::ExactAndApproximate) { + if (m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximate) { m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Left); } else { - assert(display == Calculation::DisplayOutput::ApproximateOnly || (display == Calculation::DisplayOutput::ExactAndApproximateToggle) || display == Calculation::DisplayOutput::ExactOnly); + assert((m_calculationDisplayOutput == Calculation::DisplayOutput::ApproximateOnly) + || (m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximateToggle) + || (m_calculationDisplayOutput == Calculation::DisplayOutput::ExactOnly)); m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Right); } } @@ -92,11 +93,16 @@ void HistoryViewCell::cellDidSelectSubview(HistoryViewCellDataSource::SubviewTyp if (type == HistoryViewCellDataSource::SubviewType::Output) { reloadOutputSelection(); } + /* The selected subview has changed. The displayed outputs might have changed. * For example, for the calculation 1.2+2 --> 3.2, selecting the output would * display 1.2+2 --> 16/5 = 3.2. */ - setCalculation(&m_calculation, type == HistoryViewCellDataSource::SubviewType::Output); - // Reload scroll when switching from one subview to another + m_calculationExpanded = (type == HistoryViewCellDataSource::SubviewType::Output); + m_scrollableOutputView.setDisplayLeftLayout(displayLeftLayout()); + + /* The displayed outputs have changed. We need to re-layout the cell + * and re-initialize the scroll. */ + layoutSubviews(); reloadScroll(); } @@ -105,7 +111,6 @@ KDColor HistoryViewCell::backgroundColor() const { return background; } - int HistoryViewCell::numberOfSubviews() const { return 2; } @@ -134,30 +139,30 @@ void HistoryViewCell::layoutSubviews() { } void HistoryViewCell::setCalculation(Calculation * calculation, bool expanded) { - if (m_calculationExpanded == expanded && *calculation == m_calculation) { + uint32_t newCalculationCRC = Ion::crc32Byte((const uint8_t *)calculation, ((char *)calculation->next()) - ((char *) calculation)); + if (m_calculationExpanded == expanded && newCalculationCRC == m_calculationCRC32) { return; } + Poincare::Context * context = App::app()->localContext(); + + // Clean the layouts to make room in the pool + m_inputView.setLayout(Poincare::Layout()); + m_scrollableOutputView.setLayouts(Poincare::Layout(), Poincare::Layout()); + // Memoization - m_calculation = *calculation; + m_calculationCRC32 = newCalculationCRC; m_calculationExpanded = expanded; - App * calculationApp = (App *)app(); - Calculation::DisplayOutput display = calculation->displayOutput(calculationApp->localContext()); + m_calculationDisplayOutput = calculation->displayOutput(context); m_inputView.setLayout(calculation->createInputLayout()); /* Both output expressions have to be updated at the same time. Otherwise, * when updating one layout, if the second one still points to a deleted * layout, calling to layoutSubviews() would fail. */ - Poincare::Layout leftOutputLayout = Poincare::Layout(); - Poincare::Layout rightOutputLayout; - if (display == Calculation::DisplayOutput::ExactOnly) { - rightOutputLayout = calculation->createExactOutputLayout(); - } else { - rightOutputLayout = calculation->createApproximateOutputLayout(calculationApp->localContext()); - if (display == Calculation::DisplayOutput::ExactAndApproximate || (display == Calculation::DisplayOutput::ExactAndApproximateToggle && expanded)) { - leftOutputLayout = calculation->createExactOutputLayout(); - } - } + Poincare::Layout leftOutputLayout = calculation->createExactOutputLayout(); + Poincare::Layout rightOutputLayout = (m_calculationDisplayOutput == Calculation::DisplayOutput::ExactOnly) ? leftOutputLayout : + calculation->createApproximateOutputLayout(context); + m_scrollableOutputView.setDisplayLeftLayout(displayLeftLayout()); // Must be before the setLayouts fo the reload m_scrollableOutputView.setLayouts(rightOutputLayout, leftOutputLayout); - I18n::Message equalMessage = calculation->exactAndApproximateDisplayedOutputsAreEqual(calculationApp->localContext()) == Calculation::EqualSign::Equal ? I18n::Message::Equal : I18n::Message::AlmostEqual; + I18n::Message equalMessage = calculation->exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal ? I18n::Message::Equal : I18n::Message::AlmostEqual; m_scrollableOutputView.setEqualMessage(equalMessage); /* The displayed input and outputs have changed. We need to re-layout the cell @@ -169,9 +174,9 @@ void HistoryViewCell::setCalculation(Calculation * calculation, bool expanded) { void HistoryViewCell::didBecomeFirstResponder() { assert(m_dataSource); if (m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Input) { - app()->setFirstResponder(&m_inputView); + Container::activeApp()->setFirstResponder(&m_inputView); } else { - app()->setFirstResponder(&m_scrollableOutputView); + Container::activeApp()->setFirstResponder(&m_scrollableOutputView); } } @@ -183,10 +188,15 @@ bool HistoryViewCell::handleEvent(Ion::Events::Event event) { m_dataSource->setSelectedSubviewType(otherSubviewType, this); CalculationSelectableTableView * tableView = (CalculationSelectableTableView *)parentResponder(); tableView->scrollToSubviewOfTypeOfCellAtLocation(otherSubviewType, tableView->selectedColumn(), tableView->selectedRow()); - app()->setFirstResponder(this); + Container::activeApp()->setFirstResponder(this); return true; } return false; } +bool HistoryViewCell::displayLeftLayout() const { + return (m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximate) + || (m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximateToggle && m_calculationExpanded); +} + } diff --git a/apps/calculation/history_view_cell.h b/apps/calculation/history_view_cell.h index 5a2d00d88..e32572c42 100644 --- a/apps/calculation/history_view_cell.h +++ b/apps/calculation/history_view_cell.h @@ -39,7 +39,6 @@ public: } Poincare::Layout layout() const override; KDColor backgroundColor() const override; - Calculation * calculation() { return &m_calculation; } void setCalculation(Calculation * calculation, bool expanded = false); int numberOfSubviews() const override; View * subviewAtIndex(int index) override; @@ -51,7 +50,9 @@ private: constexpr static KDCoordinate k_resultWidth = 80; void reloadScroll(); void reloadOutputSelection(); - Calculation m_calculation; + bool displayLeftLayout() const; + uint32_t m_calculationCRC32; + Calculation::DisplayOutput m_calculationDisplayOutput; bool m_calculationExpanded; ScrollableExpressionView m_inputView; Shared::ScrollableExactApproximateExpressionsView m_scrollableOutputView; diff --git a/apps/calculation/test/calculation_store.cpp b/apps/calculation/test/calculation_store.cpp index 05633b529..7603d0e6e 100644 --- a/apps/calculation/test/calculation_store.cpp +++ b/apps/calculation/test/calculation_store.cpp @@ -8,46 +8,32 @@ using namespace Poincare; using namespace Calculation; -void assert_store_is(CalculationStore * store, const char * result[10]) { +void assert_store_is(CalculationStore * store, const char * * result) { for (int i = 0; i < store->numberOfCalculations(); i++) { quiz_assert(strcmp(store->calculationAtIndex(i)->inputText(), result[i]) == 0); } } -QUIZ_CASE(calculation_store_ring_buffer) { + +QUIZ_CASE(calculation_store) { Shared::GlobalContext globalContext; CalculationStore store; - quiz_assert(CalculationStore::k_maxNumberOfCalculations == 10); - for (int i = 0; i < CalculationStore::k_maxNumberOfCalculations; i++) { + // Store is now {9, 8, 7, 6, 5, 4, 3, 2, 1, 0} + const char * result[] = {"9", "8", "7", "6", "5", "4", "3", "2", "1", "0"}; + for (int i = 0; i < 10; i++) { char text[2] = {(char)(i+'0'), 0}; store.push(text, &globalContext); quiz_assert(store.numberOfCalculations() == i+1); } - /* Store is now {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} */ - const char * result[10] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; assert_store_is(&store, result); - store.push("10", &globalContext); - /* Store is now {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} */ - const char * result1[10] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}; - assert_store_is(&store, result1); - for (int i = 9; i > 0; i = i-2) { store.deleteCalculationAtIndex(i); } - /* Store is now {1, 3, 5, 7, 9} */ - const char * result2[10] = {"1", "3", "5", "7", "9", "", "", "", "", ""}; + // Store is now {9, 7, 5, 3, 1} + const char * result2[] = {"9", "7", "5", "3", "1"}; assert_store_is(&store, result2); - for (int i = 5; i < CalculationStore::k_maxNumberOfCalculations; i++) { - char text[3] = {(char)(i+'0'), 0}; - store.push(text, &globalContext); - quiz_assert(store.numberOfCalculations() == i+1); - } - /* Store is now {0, 2, 4, 6, 8, 5, 6, 7, 8, 9} */ - const char * result3[10] = {"1", "3", "5", "7", "9", "5", "6", "7", "8", "9"}; - assert_store_is(&store, result3); - store.deleteAll(); } @@ -57,12 +43,12 @@ QUIZ_CASE(calculation_ans) { store.push("1+3/4", &globalContext); store.push("ans+2/3", &globalContext); - ::Calculation::Calculation * lastCalculation = store.calculationAtIndex(1); + Shared::ExpiringPointer<::Calculation::Calculation> lastCalculation = store.calculationAtIndex(0); quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximate); quiz_assert(strcmp(lastCalculation->exactOutputText(),"29/12") == 0); store.push("ans+0.22", &globalContext); - lastCalculation = store.calculationAtIndex(2); + lastCalculation = store.calculationAtIndex(0); quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximateToggle); quiz_assert(strcmp(lastCalculation->approximateOutputText(),"2.6366666666667") == 0); @@ -71,16 +57,16 @@ QUIZ_CASE(calculation_ans) { void assertCalculationDisplay(const char * input, ::Calculation::Calculation::DisplayOutput display, ::Calculation::Calculation::EqualSign sign, const char * exactOutput, const char * approximateOutput, Context * context, CalculationStore * store) { store->push(input, context); - ::Calculation::Calculation * lastCalculation = store->calculationAtIndex(1); + Shared::ExpiringPointer<::Calculation::Calculation> lastCalculation = store->calculationAtIndex(0); quiz_assert(lastCalculation->displayOutput(context) == display); if (sign != ::Calculation::Calculation::EqualSign::Unknown) { quiz_assert(lastCalculation->exactAndApproximateDisplayedOutputsAreEqual(context) == sign); } if (exactOutput) { - quiz_assert(strcmp(lastCalculation->exactOutputText(), exactOutput) == 0); + quiz_assert_print_if_failure(strcmp(lastCalculation->exactOutputText(), exactOutput) == 0, input); } if (approximateOutput) { - quiz_assert(strcmp(lastCalculation->approximateOutputText(), approximateOutput) == 0); + quiz_assert_print_if_failure(strcmp(lastCalculation->approximateOutputText(), approximateOutput) == 0, input); } store->deleteAll(); } @@ -94,7 +80,7 @@ QUIZ_CASE(calculation_display_exact_approximate) { assertCalculationDisplay("1/0", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); assertCalculationDisplay("2x-x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); assertCalculationDisplay("[[1,2,3]]", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); - assertCalculationDisplay("[[1,x,3]]", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "[[1,undef,3]]", &globalContext, &store); + assertCalculationDisplay("[[1,x,3]]", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "undef", &globalContext, &store); assertCalculationDisplay("28^7", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); assertCalculationDisplay("3+√(2)→a", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(2)+3", nullptr, &globalContext, &store); Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); @@ -107,6 +93,9 @@ QUIZ_CASE(calculation_display_exact_approximate) { assertCalculationDisplay("1+1+random()", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); assertCalculationDisplay("1+1+round(1.343,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3.34", &globalContext, &store); assertCalculationDisplay("randint(2,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "5", "5", &globalContext, &store); + assertCalculationDisplay("confidence(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); + assertCalculationDisplay("prediction(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); + assertCalculationDisplay("prediction95(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); } @@ -131,15 +120,16 @@ QUIZ_CASE(calculation_symbolic_computation_and_parametered_expressions) { Shared::GlobalContext globalContext; CalculationStore store; - assertCalculationDisplay("int(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", &globalContext, &store); - assertCalculationDisplay("sum(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3", &globalContext, &store); - assertCalculationDisplay("product(x,x,1,2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", &globalContext, &store); - assertCalculationDisplay("diff(x^2,x,3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "6", &globalContext, &store); + assertCalculationDisplay("int((ℯ^(-x))-x^(0.5), x, 0, 3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); // Tests a bug with symbolic computation + assertCalculationDisplay("int(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", &globalContext, &store); + assertCalculationDisplay("sum(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3", &globalContext, &store); + assertCalculationDisplay("product(x,x,1,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", &globalContext, &store); + assertCalculationDisplay("diff(x^2,x,3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "6", &globalContext, &store); assertCalculationDisplay("2→x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); - assertCalculationDisplay("int(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", &globalContext, &store); - assertCalculationDisplay("sum(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3", &globalContext, &store); - assertCalculationDisplay("product(x,x,1,2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", &globalContext, &store); - assertCalculationDisplay("diff(x^2,x,3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "6", &globalContext, &store); + assertCalculationDisplay("int(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", &globalContext, &store); + assertCalculationDisplay("sum(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3", &globalContext, &store); + assertCalculationDisplay("product(x,x,1,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", &globalContext, &store); + assertCalculationDisplay("diff(x^2,x,3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "6", &globalContext, &store); Ion::Storage::sharedStorage()->recordNamed("x.exp").destroy(); } @@ -168,13 +158,13 @@ QUIZ_CASE(calculation_complex_format) { assertCalculationDisplay("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "root(8,4)/2+root(8,4)/2×ð¢", nullptr, &globalContext, &store); Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Polar); - assertCalculationDisplay("1+ð¢", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(2)×ℯ^(Ï€/4×ð¢)", nullptr, &globalContext, &store); - assertCalculationDisplay("√(-1)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ℯ^(Ï€/2×ð¢)", nullptr, &globalContext, &store); + assertCalculationDisplay("1+ð¢", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(2)×ℯ^\u0012Ï€/4×ð¢\u0013", nullptr, &globalContext, &store); + assertCalculationDisplay("√(-1)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ℯ^\u0012Ï€/2×ð¢\u0013", nullptr, &globalContext, &store); assertCalculationDisplay("ln(-2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ln(-2)", nullptr, &globalContext, &store); - assertCalculationDisplay("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "ℯ^(3.1415926535898×ð¢)", &globalContext, &store); - assertCalculationDisplay("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "2×ℯ^(Ï€/3×ð¢)", nullptr, &globalContext, &store); - assertCalculationDisplay("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "4×ℯ^((2×π)/3×ð¢)", nullptr, &globalContext, &store); - assertCalculationDisplay("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "root(2,4)×ℯ^(Ï€/4×ð¢)", nullptr, &globalContext, &store); + assertCalculationDisplay("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "ℯ^\u00123.1415926535898×ð¢\u0013", &globalContext, &store); + assertCalculationDisplay("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "2×ℯ^\u0012Ï€/3×ð¢\u0013", nullptr, &globalContext, &store); + assertCalculationDisplay("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "4×ℯ^\u0012\u00122×π\u0013/3×ð¢\u0013", nullptr, &globalContext, &store); + assertCalculationDisplay("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "root(2,4)×ℯ^\u0012Ï€/4×ð¢\u0013", nullptr, &globalContext, &store); Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Cartesian); } diff --git a/apps/code/Makefile b/apps/code/Makefile index 891aa24d2..f05354dff 100644 --- a/apps/code/Makefile +++ b/apps/code/Makefile @@ -1,7 +1,7 @@ apps += Code::App app_headers += apps/code/app.h -app_src += $(addprefix apps/code/,\ +app_code_src = $(addprefix apps/code/,\ app.cpp \ console_controller.cpp \ console_edit_cell.cpp \ @@ -23,6 +23,8 @@ app_src += $(addprefix apps/code/,\ variable_box_controller.cpp \ ) +app_src += $(app_code_src) + i18n_files += $(addprefix apps/code/,\ base.de.i18n\ base.en.i18n\ diff --git a/apps/code/app.cpp b/apps/code/app.cpp index a640ded5a..b446187e8 100644 --- a/apps/code/app.cpp +++ b/apps/code/app.cpp @@ -1,5 +1,4 @@ #include "app.h" -#include "../apps_container.h" #include "code_icon.h" #include #include "helpers.h" @@ -28,7 +27,7 @@ App::Snapshot::Snapshot() : } App * App::Snapshot::unpack(Container * container) { - return new (container->currentAppBuffer()) App(container, this); + return new (container->currentAppBuffer()) App(this); } App::Descriptor * App::Snapshot::descriptor() { @@ -45,11 +44,11 @@ bool App::Snapshot::lockOnConsole() const { return m_lockOnConsole; } -void App::Snapshot::setOpt(const char * name, char * value) { +void App::Snapshot::setOpt(const char * name, const char * value) { if (strcmp(name, "script") == 0) { m_scriptStore.deleteAllScripts(); char * separator = const_cast(UTF8Helper::CodePointSearch(value, ':')); - if (!separator) { + if (*separator == 0) { return; } *separator = 0; @@ -72,8 +71,8 @@ void App::Snapshot::setOpt(const char * name, char * value) { } #endif -App::App(Container * container, Snapshot * snapshot) : - Shared::InputEventHandlerDelegateApp(container, snapshot, &m_codeStackViewController), +App::App(Snapshot * snapshot) : + Shared::InputEventHandlerDelegateApp(snapshot, &m_codeStackViewController), m_pythonHeap{}, m_pythonUser(nullptr), m_consoleController(nullptr, this, snapshot->scriptStore() diff --git a/apps/code/app.h b/apps/code/app.h index db1c0bfc6..f683b5bbb 100644 --- a/apps/code/app.h +++ b/apps/code/app.h @@ -28,7 +28,7 @@ public: ScriptStore * scriptStore(); #if EPSILON_GETOPT bool lockOnConsole() const; - void setOpt(const char * name, char * value) override; + void setOpt(const char * name, const char * value) override; #endif private: #if EPSILON_GETOPT @@ -36,6 +36,9 @@ public: #endif ScriptStore m_scriptStore; }; + static App * app() { + return static_cast(Container::activeApp()); + } ~App(); bool prepareForExit() override { if (m_consoleController.inputRunLoopActive()) { @@ -75,7 +78,7 @@ private: char m_pythonHeap[k_pythonHeapSize]; const void * m_pythonUser; - App(Container * container, Snapshot * snapshot); + App(Snapshot * snapshot); ConsoleController m_consoleController; ButtonRowController m_listFooter; MenuController m_menuController; diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index 89d307345..847a45b43 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -21,9 +21,9 @@ PythonBin = "Conversion d'un entier en binaire" PythonCeil = "Plafond" PythonChoice = "Nombre aléatoire dans la liste" PythonCmathFunction = "Préfixe fonction du module cmath" -PythonColor = "Définit une couleur rgb" -PythonComplex = "Retourne a+ib" -PythonCopySign = "Retourne x avec le signe de y" +PythonColor = "Définit une couleur rvb" +PythonComplex = "Renvoie a+ib" +PythonCopySign = "Renvoie x avec le signe de y" PythonCos = "Cosinus" PythonCosh = "Cosinus hyperbolique" PythonDegrees = "Conversion de radians en degrés" @@ -64,9 +64,9 @@ PythonKandinskyFunction = "Préfixe fonction module kandinsky" PythonLdexp = "Inverse de frexp : x*(2**i)" PythonLength = "Longueur d'un objet" PythonLgamma = "Logarithme de la fonction gamma" -PythonLog = "Logarithme base a" -PythonLog10 = "Logarithme base 10" -PythonLog2 = "Logarithme base 2" +PythonLog = "Logarithme de base a" +PythonLog10 = "Logarithme décimal" +PythonLog2 = "Logarithme de base 2" PythonMathFunction = "Préfixe fonction du module math" PythonMax = "Maximum" PythonMin = "Minimum" @@ -85,7 +85,7 @@ PythonRandrange = "Nombre dans range(start, stop)" PythonRangeStartStop = "Liste de start à stop-1" PythonRangeStop = "Liste de 0 à stop-1" PythonRect = "Conversion en algébrique" -PythonRound = "Arrondi n chiffres" +PythonRound = "Arrondi à n décimales" PythonSeed = "Initialiser générateur aléatoire" PythonSetPixel = "Colore le pixel (x,y)" PythonSin = "Sinus" @@ -121,7 +121,7 @@ PythonTurtlePurple = "Couleur violette" PythonTurtleRed = "Couleur rouge" PythonTurtleReset = "Réinitialise le dessin" PythonTurtleRight = "Pivote de a degrés vers la droite" -PythonTurtleSetheading = "Met une orientation de a degrés" +PythonTurtleSetheading = "Met un cap de a degrés" PythonTurtleSetposition = "Positionne la tortue" PythonTurtleShowturtle = "Affiche la tortue" PythonTurtleSpeed = "Vitesse du tracé entre 0 et 10" diff --git a/apps/code/console_controller.cpp b/apps/code/console_controller.cpp index bc6fdf4bc..0a1c41586 100644 --- a/apps/code/console_controller.cpp +++ b/apps/code/console_controller.cpp @@ -57,7 +57,7 @@ bool ConsoleController::loadPythonEnvironment() { /* We load functions and variables names in the variable box before running * any other python code to avoid failling to load functions and variables * due to memory exhaustion. */ - static_cast(app())->variableBoxController()->loadFunctionsAndVariables(); + App::app()->variableBoxController()->loadFunctionsAndVariables(); return true; } @@ -90,9 +90,14 @@ void ConsoleController::terminateInputLoop() { } const char * ConsoleController::inputText(const char * prompt) { - AppsContainer * a = (AppsContainer *)(app()->container()); + AppsContainer * appsContainer = AppsContainer::sharedAppsContainer(); m_inputRunLoopActive = true; + // Hide the sandbox if it is displayed + if (sandboxIsDisplayed()) { + hideSandbox(); + } + const char * promptText = prompt; char * s = const_cast(prompt); @@ -119,10 +124,10 @@ const char * ConsoleController::inputText(const char * prompt) { // Reload the history m_selectableTableView.reloadData(); m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines()); - a->redrawWindow(); + appsContainer->redrawWindow(); // Launch a new input loop - a->runWhile([](void * a){ + appsContainer->runWhile([](void * a){ ConsoleController * c = static_cast(a); return c->inputRunLoopActive(); }, this); @@ -153,7 +158,7 @@ void ConsoleController::viewWillAppear() { } void ConsoleController::didBecomeFirstResponder() { - app()->setFirstResponder(&m_editCell); + Container::activeApp()->setFirstResponder(&m_editCell); } bool ConsoleController::handleEvent(Ion::Events::Event event) { @@ -162,7 +167,7 @@ bool ConsoleController::handleEvent(Ion::Events::Event event) { const char * text = m_consoleStore.lineAtIndex(m_selectableTableView.selectedRow()).text(); m_editCell.setEditing(true); m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines()); - app()->setFirstResponder(&m_editCell); + Container::activeApp()->setFirstResponder(&m_editCell); return m_editCell.insertText(text); } } else if (event == Ion::Events::Clear) { @@ -191,7 +196,7 @@ bool ConsoleController::handleEvent(Ion::Events::Event event) { return false; } -int ConsoleController::numberOfRows() { +int ConsoleController::numberOfRows() const { return m_consoleStore.numberOfLines()+1; } @@ -290,7 +295,7 @@ bool ConsoleController::textFieldDidReceiveEvent(TextField * textField, Ion::Eve return true; } } - return static_cast(textField->app())->textInputDidReceiveEvent(textField, event); + return App::app()->textInputDidReceiveEvent(textField, event); } bool ConsoleController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { @@ -395,10 +400,13 @@ void ConsoleController::autoImportScript(Script script, bool force) { /* Copy the script name without the extension ".py". The '.' is overwritten * by the null terminating char. */ int copySizeWithNullTerminatingZero = minInt(k_maxImportCommandSize - currentChar, strlen(scriptName) - strlen(ScriptStore::k_scriptExtension)); + assert(copySizeWithNullTerminatingZero >= 0); + assert(copySizeWithNullTerminatingZero <= k_maxImportCommandSize - currentChar); strlcpy(command+currentChar, scriptName, copySizeWithNullTerminatingZero); currentChar += copySizeWithNullTerminatingZero-1; // Copy " import *" + assert(k_maxImportCommandSize >= currentChar); strlcpy(command+currentChar, k_importCommand2, k_maxImportCommandSize - currentChar); // Step 2 - Run the command diff --git a/apps/code/console_controller.h b/apps/code/console_controller.h index 43779f340..1c32f4f04 100644 --- a/apps/code/console_controller.h +++ b/apps/code/console_controller.h @@ -42,7 +42,7 @@ public: ViewController::DisplayParameter displayParameter() override { return ViewController::DisplayParameter::WantsMaximumSpace; } // ListViewDataSource - int numberOfRows() override; + int numberOfRows() const override; KDCoordinate rowHeight(int j) override; KDCoordinate cumulatedHeightFromIndex(int j) override; int indexFromCumulatedHeight(KDCoordinate offsetY) override; diff --git a/apps/code/console_edit_cell.cpp b/apps/code/console_edit_cell.cpp index 9fc5ff585..e2472d8fe 100644 --- a/apps/code/console_edit_cell.cpp +++ b/apps/code/console_edit_cell.cpp @@ -9,9 +9,8 @@ namespace Code { ConsoleEditCell::ConsoleEditCell(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, TextFieldDelegate * delegate) : HighlightCell(), Responder(parentResponder), - m_textBuffer{0}, m_promptView(ConsoleController::k_font, nullptr, 0, 0.5), - m_textField(this, m_textBuffer, m_textBuffer, TextField::maxBufferSize(), inputEventHandlerDelegate, delegate, false, ConsoleController::k_font) + m_textField(this, nullptr, TextField::maxBufferSize(), TextField::maxBufferSize(), inputEventHandlerDelegate, delegate, ConsoleController::k_font) { } @@ -35,11 +34,11 @@ void ConsoleEditCell::layoutSubviews() { } void ConsoleEditCell::didBecomeFirstResponder() { - app()->setFirstResponder(&m_textField); + Container::activeApp()->setFirstResponder(&m_textField); } -void ConsoleEditCell::setEditing(bool isEditing, bool reinitDraftBuffer) { - m_textField.setEditing(isEditing, reinitDraftBuffer); +void ConsoleEditCell::setEditing(bool isEditing) { + m_textField.setEditing(isEditing); } void ConsoleEditCell::setText(const char * text) { diff --git a/apps/code/console_edit_cell.h b/apps/code/console_edit_cell.h index 8a6b2d9b3..e98cfba46 100644 --- a/apps/code/console_edit_cell.h +++ b/apps/code/console_edit_cell.h @@ -27,13 +27,12 @@ public: } // Edit cell - void setEditing(bool isEditing, bool reinitDraftBuffer = false); + void setEditing(bool isEditing); const char * text() const override { return m_textField.text(); } void setText(const char * text); bool insertText(const char * text); void setPrompt(const char * prompt); private: - char m_textBuffer[TextField::maxBufferSize()]; PointerTextView m_promptView; TextField m_textField; }; diff --git a/apps/code/console_line_cell.cpp b/apps/code/console_line_cell.cpp index 5a696d57a..b0181b6a0 100644 --- a/apps/code/console_line_cell.cpp +++ b/apps/code/console_line_cell.cpp @@ -90,7 +90,7 @@ void ConsoleLineCell::layoutSubviews() { } void ConsoleLineCell::didBecomeFirstResponder() { - app()->setFirstResponder(&m_scrollableView); + Container::activeApp()->setFirstResponder(&m_scrollableView); } } diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index de2dd410e..aba355959 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -39,7 +39,7 @@ bool EditorController::handleEvent(Ion::Events::Event event) { } void EditorController::didBecomeFirstResponder() { - app()->setFirstResponder(&m_editorView); + Container::activeApp()->setFirstResponder(&m_editorView); } void EditorController::viewWillAppear() { @@ -58,7 +58,7 @@ bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events: saveScript(); return false; } - if (static_cast(textArea->app())->textInputDidReceiveEvent(textArea, event)) { + if (App::app()->textInputDidReceiveEvent(textArea, event)) { return true; } if (event == Ion::Events::EXE) { @@ -82,8 +82,8 @@ bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events: cursorIsPrecededOnTheLineBySpacesOnly = true; } numberOfSpaces = numberOfSpaces / UTF8Decoder::CharSizeOfCodePoint(' '); - if (cursorIsPrecededOnTheLineBySpacesOnly && numberOfSpaces >= k_indentationSpacesNumber) { - for (int i = 0; i < k_indentationSpacesNumber; i++) { + if (cursorIsPrecededOnTheLineBySpacesOnly && numberOfSpaces >= TextArea::k_indentationSpaces) { + for (int i = 0; i < TextArea::k_indentationSpaces; i++) { textArea->removePreviousGlyph(); } return true; @@ -96,11 +96,11 @@ bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events: assert(firstNonSpace >= text); if (UTF8Helper::CodePointIs(firstNonSpace, '\n')) { assert(UTF8Decoder::CharSizeOfCodePoint(' ') == 1); - char indentationBuffer[k_indentationSpacesNumber+1]; - for (int i = 0; i < k_indentationSpacesNumber; i++) { + char indentationBuffer[TextArea::k_indentationSpaces+1]; + for (int i = 0; i < TextArea::k_indentationSpaces; i++) { indentationBuffer[i] = ' '; } - indentationBuffer[k_indentationSpacesNumber] = 0; + indentationBuffer[TextArea::k_indentationSpaces] = 0; textArea->handleEventWithText(indentationBuffer); return true; } @@ -109,15 +109,11 @@ bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events: } VariableBoxController * EditorController::variableBoxForInputEventHandler(InputEventHandler * textInput) { - VariableBoxController * varBox = static_cast(app())->variableBoxController(); + VariableBoxController * varBox = App::app()->variableBoxController(); varBox->loadFunctionsAndVariables(); return varBox; } -InputEventHandlerDelegateApp * EditorController::inputEventHandlerDelegateApp() { - return static_cast(app()); -} - StackViewController * EditorController::stackController() { return static_cast(parentResponder()); } diff --git a/apps/code/editor_controller.h b/apps/code/editor_controller.h index 93e3adb60..284bf1af3 100644 --- a/apps/code/editor_controller.h +++ b/apps/code/editor_controller.h @@ -33,8 +33,6 @@ public: VariableBoxController * variableBoxForInputEventHandler(InputEventHandler * textInput) override; private: - Shared::InputEventHandlerDelegateApp * inputEventHandlerDelegateApp() override; - static constexpr int k_indentationSpacesNumber = 2; //TODO LEA merge with text area k_indentationSpaces StackViewController * stackController(); void saveScript(); EditorView m_editorView; diff --git a/apps/code/editor_view.cpp b/apps/code/editor_view.cpp index cf883dc69..4138b5221 100644 --- a/apps/code/editor_view.cpp +++ b/apps/code/editor_view.cpp @@ -31,7 +31,7 @@ View * EditorView::subviewAtIndex(int index) { } void EditorView::didBecomeFirstResponder() { - app()->setFirstResponder(&m_textArea); + Container::activeApp()->setFirstResponder(&m_textArea); } void EditorView::layoutSubviews() { diff --git a/apps/code/menu_controller.cpp b/apps/code/menu_controller.cpp index 80e8fd4af..bed2c31cc 100644 --- a/apps/code/menu_controller.cpp +++ b/apps/code/menu_controller.cpp @@ -36,7 +36,7 @@ MenuController::MenuController(Responder * parentResponder, App * pythonDelegate } ConsoleController * MenuController::consoleController() { - return static_cast(app())->consoleController(); + return App::app()->consoleController(); } StackViewController * MenuController::stackViewController() { @@ -49,7 +49,7 @@ void MenuController::willExitResponderChain(Responder * nextFirstResponder) { if (selectedRow >= 0 && selectedRow < m_scriptStore->numberOfScripts() && selectedColumn == 0) { TextField * tf = static_cast(m_selectableTableView.selectedCell())->textField(); if (tf->isEditing()) { - tf->setEditing(false, false); + tf->setEditing(false); privateTextFieldDidAbortEditing(tf, false); } } @@ -61,14 +61,14 @@ void MenuController::didBecomeFirstResponder() { } if (footer()->selectedButton() == 0) { assert(m_selectableTableView.selectedRow() < 0); - app()->setFirstResponder(&m_consoleButton); + Container::activeApp()->setFirstResponder(&m_consoleButton); return; } if (m_selectableTableView.selectedRow() < 0) { m_selectableTableView.selectCellAtLocation(0,0); } assert(m_selectableTableView.selectedRow() < m_scriptStore->numberOfScripts() + 1); - app()->setFirstResponder(&m_selectableTableView); + Container::activeApp()->setFirstResponder(&m_selectableTableView); #if EPSILON_GETOPT if (consoleController()->locked()) { consoleController()->setAutoImport(true); @@ -92,7 +92,7 @@ bool MenuController::handleEvent(Ion::Events::Event event) { if (footer()->selectedButton() == 0) { footer()->setSelectedButton(-1); m_selectableTableView.selectCellAtLocation(0, numberOfRows()-1); - app()->setFirstResponder(&m_selectableTableView); + Container::activeApp()->setFirstResponder(&m_selectableTableView); return true; } } @@ -121,13 +121,16 @@ bool MenuController::handleEvent(Ion::Events::Event event) { void MenuController::renameSelectedScript() { assert(m_selectableTableView.selectedRow() >= 0); assert(m_selectableTableView.selectedRow() < m_scriptStore->numberOfScripts()); - static_cast(const_cast(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::AlphaLock); + AppsContainer::sharedAppsContainer()->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::AlphaLock); m_selectableTableView.selectCellAtLocation(0, (m_selectableTableView.selectedRow())); ScriptNameCell * myCell = static_cast(m_selectableTableView.selectedCell()); - app()->setFirstResponder(myCell); + Container::activeApp()->setFirstResponder(myCell); myCell->setHighlighted(false); - myCell->textField()->setEditing(true, false); - myCell->textField()->setCursorLocation(myCell->textField()->text() + strlen(myCell->textField()->text())); + TextField * tf = myCell->textField(); + const char * previousText = tf->text(); + tf->setEditing(true); + tf->setText(previousText); + tf->setCursorLocation(tf->text() + strlen(previousText)); } void MenuController::deleteScript(Script script) { @@ -153,7 +156,7 @@ void MenuController::scriptContentEditionDidFinish() { reloadConsole(); } -int MenuController::numberOfRows() { +int MenuController::numberOfRows() const { return m_scriptStore->numberOfScripts() + m_shouldDisplayAddScriptRow; } @@ -312,9 +315,9 @@ bool MenuController::textFieldDidFinishEditing(TextField * textField, const char // The user entered an empty name. Use a numbered default script name. bool foundDefaultName = Script::DefaultName(numberedDefaultName, Script::k_defaultScriptNameMaxSize); int defaultNameLength = strlen(numberedDefaultName); - assert(defaultNameLength < bufferSize); assert(UTF8Decoder::CharSizeOfCodePoint('.') == 1); numberedDefaultName[defaultNameLength++] = '.'; + assert(defaultNameLength < bufferSize); strlcpy(numberedDefaultName + defaultNameLength, ScriptStore::k_scriptExtension, bufferSize - defaultNameLength); /* If there are already scripts named script1.py, script2.py,... until * Script::k_maxNumberOfDefaultScriptNames, we want to write the last tried @@ -337,16 +340,16 @@ bool MenuController::textFieldDidFinishEditing(TextField * textField, const char } m_selectableTableView.selectedCell()->setHighlighted(true); reloadConsole(); - app()->setFirstResponder(&m_selectableTableView); - static_cast(const_cast(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default); + Container::activeApp()->setFirstResponder(&m_selectableTableView); + AppsContainer::sharedAppsContainer()->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default); return true; } else if (error == Script::ErrorStatus::NameTaken) { - app()->displayWarning(I18n::Message::NameTaken); + Container::activeApp()->displayWarning(I18n::Message::NameTaken); } else if (error == Script::ErrorStatus::NonCompliantName) { - app()->displayWarning(I18n::Message::AllowedCharactersaz09, I18n::Message::NameCannotStartWithNumber); + Container::activeApp()->displayWarning(I18n::Message::AllowedCharactersaz09, I18n::Message::NameCannotStartWithNumber); } else { assert(error == Script::ErrorStatus::NotEnoughSpaceAvailable); - app()->displayWarning(I18n::Message::NameTooLong); + Container::activeApp()->displayWarning(I18n::Message::NameTooLong); } return false; } @@ -420,9 +423,9 @@ bool MenuController::privateTextFieldDidAbortEditing(TextField * textField, bool textField->setText(scriptName); if (menuControllerStaysInResponderChain) { m_selectableTableView.selectCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); - app()->setFirstResponder(&m_selectableTableView); + Container::activeApp()->setFirstResponder(&m_selectableTableView); } - static_cast(const_cast(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default); + AppsContainer::sharedAppsContainer()->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default); return true; } diff --git a/apps/code/menu_controller.h b/apps/code/menu_controller.h index 5848a302a..252b2f030 100644 --- a/apps/code/menu_controller.h +++ b/apps/code/menu_controller.h @@ -31,8 +31,8 @@ public: void viewWillAppear() override; /* TableViewDataSource */ - int numberOfRows() override; - int numberOfColumns() override { return 2; } + int numberOfRows() const override; + int numberOfColumns() const override { return 2; } void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; KDCoordinate columnWidth(int i) override; KDCoordinate rowHeight(int j) override { return Metric::StoreRowHeight; } diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index a002b7f8a..55989b01d 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -370,11 +370,11 @@ bool PythonToolbox::selectLeaf(int selectedRow) { editedText = strippedEditedText; } sender()->handleEventWithText(editedText, true); - app()->dismissModalViewController(); + Container::activeApp()->dismissModalViewController(); return true; } -const ToolboxMessageTree * PythonToolbox::rootModel() { +const ToolboxMessageTree * PythonToolbox::rootModel() const { return &toolboxModel; } diff --git a/apps/code/python_toolbox.h b/apps/code/python_toolbox.h index 939b86f78..0889f2ebf 100644 --- a/apps/code/python_toolbox.h +++ b/apps/code/python_toolbox.h @@ -15,7 +15,7 @@ public: protected: KDCoordinate rowHeight(int j) override; bool selectLeaf(int selectedRow) override; - const ToolboxMessageTree * rootModel() override; + const ToolboxMessageTree * rootModel() const override; MessageTableCellWithMessage * leafCellAtIndex(int index) override; MessageTableCellWithChevron* nodeCellAtIndex(int index) override; int maxNumberOfDisplayedRows() override; diff --git a/apps/code/sandbox_controller.cpp b/apps/code/sandbox_controller.cpp index 12b5f0332..5b056c53b 100644 --- a/apps/code/sandbox_controller.cpp +++ b/apps/code/sandbox_controller.cpp @@ -48,7 +48,7 @@ bool SandboxController::handleEvent(Ion::Events::Event event) { } void SandboxController::redrawWindow() { - static_cast(const_cast(app()->container()))->redrawWindow(); + AppsContainer::sharedAppsContainer()->redrawWindow(); } } diff --git a/apps/code/script_name_cell.cpp b/apps/code/script_name_cell.cpp index 8dbc6564b..26792e7f2 100644 --- a/apps/code/script_name_cell.cpp +++ b/apps/code/script_name_cell.cpp @@ -26,7 +26,7 @@ KDSize ScriptNameCell::minimalSizeForOptimalDisplay() const { } void ScriptNameCell::didBecomeFirstResponder() { - app()->setFirstResponder(&m_textField); + Container::activeApp()->setFirstResponder(&m_textField); } void ScriptNameCell::layoutSubviews() { diff --git a/apps/code/script_name_cell.h b/apps/code/script_name_cell.h index 914432114..ec9831bd8 100644 --- a/apps/code/script_name_cell.h +++ b/apps/code/script_name_cell.h @@ -15,8 +15,10 @@ public: ScriptNameCell(Responder * parentResponder = nullptr, TextFieldDelegate * delegate = nullptr) : EvenOddCell(), Responder(parentResponder), - m_textField(k_extensionLength, this, m_textBody, m_textBody, TextField::maxBufferSize(), nullptr, delegate, false) - {} + m_textField(k_extensionLength, this, m_textBody, TextField::maxBufferSize(), TextField::maxBufferSize(), nullptr, delegate) + { + m_textBody[0] = 0; + } Shared::TextFieldWithExtension * textField() { return &m_textField; } diff --git a/apps/code/script_parameter_controller.cpp b/apps/code/script_parameter_controller.cpp index 7dda64195..7a8e67b23 100644 --- a/apps/code/script_parameter_controller.cpp +++ b/apps/code/script_parameter_controller.cpp @@ -45,7 +45,7 @@ bool ScriptParameterController::handleEvent(Ion::Events::Event event) { m_script.toggleImportationStatus(); m_selectableTableView.reloadData(); m_menuController->reloadConsole(); - app()->setFirstResponder(&m_selectableTableView); + Container::activeApp()->setFirstResponder(&m_selectableTableView); return true; case 3: dismissScriptParameterController(); @@ -67,7 +67,7 @@ void ScriptParameterController::viewWillAppear() { void ScriptParameterController::didBecomeFirstResponder() { selectCellAtLocation(0, 0); - app()->setFirstResponder(&m_selectableTableView); + Container::activeApp()->setFirstResponder(&m_selectableTableView); } HighlightCell * ScriptParameterController::reusableCell(int index) { diff --git a/apps/code/script_parameter_controller.h b/apps/code/script_parameter_controller.h index 1fef1d752..c851922d9 100644 --- a/apps/code/script_parameter_controller.h +++ b/apps/code/script_parameter_controller.h @@ -25,8 +25,8 @@ public: /* SimpleListViewDataSource */ KDCoordinate cellHeight() override { return Metric::ParameterCellHeight; } HighlightCell * reusableCell(int index) override; - int reusableCellCount() override { return k_totalNumberOfCell; } - int numberOfRows() override { return k_totalNumberOfCell; } + int reusableCellCount() const override { return k_totalNumberOfCell; } + int numberOfRows() const override { return k_totalNumberOfCell; } void willDisplayCellForIndex(HighlightCell * cell, int index) override; private: diff --git a/apps/code/variable_box_controller.cpp b/apps/code/variable_box_controller.cpp index 13f086a57..c937b4896 100644 --- a/apps/code/variable_box_controller.cpp +++ b/apps/code/variable_box_controller.cpp @@ -33,7 +33,7 @@ void VariableBoxController::didEnterResponderChain(Responder * previousFirstResp * environment where Python has already been inited. This way, we do not * deinit Python when leaving the VariableBoxController, so we do not lose the * environment that was loaded when entering the VariableBoxController. */ - assert(static_cast(app())->pythonIsInited()); + assert(App::app()->pythonIsInited()); } static bool shouldAddObject(const char * name, int maxLength) { @@ -47,7 +47,7 @@ static bool shouldAddObject(const char * name, int maxLength) { return true; } -int VariableBoxController::numberOfRows() { +int VariableBoxController::numberOfRows() const { assert(m_scriptNodesCount <= k_maxScriptNodesCount); return m_scriptNodesCount; } @@ -100,7 +100,7 @@ bool VariableBoxController::selectLeaf(int rowIndex) { if (selectedScriptNode.type() == ScriptNode::Type::Function) { insertTextInCaller(ScriptNodeCell::k_parenthesesWithEmpty); } - app()->dismissModalViewController(); + Container::activeApp()->dismissModalViewController(); return true; } diff --git a/apps/code/variable_box_controller.h b/apps/code/variable_box_controller.h index 6e1047126..79d107f00 100644 --- a/apps/code/variable_box_controller.h +++ b/apps/code/variable_box_controller.h @@ -17,7 +17,7 @@ public: void didEnterResponderChain(Responder * previousFirstResponder) override; /* ListViewDataSource */ - int numberOfRows() override; + int numberOfRows() const override; int reusableCellCount(int type) override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; int typeAtLocation(int i, int j) override; diff --git a/apps/constant.cpp b/apps/constant.cpp deleted file mode 100644 index 6a1259964..000000000 --- a/apps/constant.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "constant.h" - -constexpr int Constant::LargeNumberOfSignificantDigits; -constexpr int Constant::MediumNumberOfSignificantDigits; -constexpr int Constant::ShortNumberOfSignificantDigits; diff --git a/apps/constant.h b/apps/constant.h index 4d4c86520..0190f0eb2 100644 --- a/apps/constant.h +++ b/apps/constant.h @@ -5,9 +5,6 @@ class Constant { public: - constexpr static int LargeNumberOfSignificantDigits = 7; - constexpr static int MediumNumberOfSignificantDigits = 5; - constexpr static int ShortNumberOfSignificantDigits = 4; constexpr static int MaxSerializedExpressionSize = 2*::TextField::maxBufferSize(); }; diff --git a/apps/exam_pop_up_controller.cpp b/apps/exam_pop_up_controller.cpp index dd93a0b4e..8d4f25c65 100644 --- a/apps/exam_pop_up_controller.cpp +++ b/apps/exam_pop_up_controller.cpp @@ -33,16 +33,16 @@ void ExamPopUpController::viewDidDisappear() { } void ExamPopUpController::didBecomeFirstResponder() { - m_contentView.setSelectedButton(0, app()); + m_contentView.setSelectedButton(0); } bool ExamPopUpController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::Left && m_contentView.selectedButton() == 1) { - m_contentView.setSelectedButton(0, app()); + m_contentView.setSelectedButton(0); return true; } if (event == Ion::Events::Right && m_contentView.selectedButton() == 0) { - m_contentView.setSelectedButton(1, app()); + m_contentView.setSelectedButton(1); return true; } return false; @@ -50,9 +50,7 @@ bool ExamPopUpController::handleEvent(Ion::Events::Event event) { ExamPopUpController::ContentView::ContentView(Responder * parentResponder) : m_cancelButton(parentResponder, I18n::Message::Cancel, Invocation([](void * context, void * sender) { - ExamPopUpController * controller = (ExamPopUpController *)context; - Container * container = (Container *)controller->app()->container(); - container->activeApp()->dismissModalViewController(); + Container::activeApp()->dismissModalViewController(); return true; }, parentResponder), KDFont::SmallFont), m_okButton(parentResponder, I18n::Message::Ok, Invocation([](void * context, void * sender) { @@ -60,7 +58,7 @@ ExamPopUpController::ContentView::ContentView(Responder * parentResponder) : GlobalPreferences::ExamMode nextExamMode = controller->isActivatingExamMode() ? GlobalPreferences::ExamMode::Activate : GlobalPreferences::ExamMode::Deactivate; GlobalPreferences::sharedGlobalPreferences()->setExamMode(nextExamMode); Preferences * preferences = Preferences::sharedPreferences(); - AppsContainer * container = (AppsContainer *)controller->app()->container(); + AppsContainer * container = AppsContainer::sharedAppsContainer(); if (controller->isActivatingExamMode()) { container->reset(); switch ((int)preferences->colorOfLED()) { @@ -80,9 +78,10 @@ ExamPopUpController::ContentView::ContentView(Responder * parentResponder) : Ion::LED::setBlinking(1000, 0.1f); } else { Ion::LED::setColor(KDColorBlack); + Ion::LED::updateColorWithPlugAndCharge(); } container->refreshPreferences(); - container->activeApp()->dismissModalViewController(); + Container::activeApp()->dismissModalViewController(); return true; }, parentResponder), KDFont::SmallFont), m_warningTextView(KDFont::SmallFont, I18n::Message::Warning, 0.5, 0.5, KDColorWhite, KDColorBlack), @@ -96,14 +95,10 @@ void ExamPopUpController::ContentView::drawRect(KDContext * ctx, KDRect rect) co ctx->fillRect(bounds(), KDColorBlack); } -void ExamPopUpController::ContentView::setSelectedButton(int selectedButton, App * app) { +void ExamPopUpController::ContentView::setSelectedButton(int selectedButton) { m_cancelButton.setHighlighted(selectedButton == 0); m_okButton.setHighlighted(selectedButton == 1); - if (selectedButton == 0) { - app->setFirstResponder(&m_cancelButton); - } else { - app->setFirstResponder(&m_okButton); - } + Container::activeApp()->setFirstResponder(selectedButton == 0 ? &m_cancelButton : &m_okButton); } int ExamPopUpController::ContentView::selectedButton() { diff --git a/apps/exam_pop_up_controller.h b/apps/exam_pop_up_controller.h index fd04f50dc..ad888cff9 100644 --- a/apps/exam_pop_up_controller.h +++ b/apps/exam_pop_up_controller.h @@ -26,7 +26,7 @@ private: public: ContentView(Responder * parentResponder); void drawRect(KDContext * ctx, KDRect rect) const override; - void setSelectedButton(int selectedButton, App * app); + void setSelectedButton(int selectedButton); int selectedButton(); void setMessages(bool activingExamMode); private: diff --git a/apps/global_preferences.h b/apps/global_preferences.h index d7473d0c6..201957ab2 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -14,10 +14,8 @@ public: void setLanguage(I18n::Language language) { m_language = language; } ExamMode examMode() const { return m_examMode; } void setExamMode(ExamMode examMode) { m_examMode = examMode; } -#ifdef EPSILON_BOOT_PROMPT bool showPopUp() const { return m_showPopUp; } void setShowPopUp(bool showPopUp) { m_showPopUp = showPopUp; } -#endif int brightnessLevel() const { return m_brightnessLevel; } void setBrightnessLevel(int brightnessLevel); constexpr static int NumberOfBrightnessStates = 15; @@ -25,15 +23,11 @@ private: GlobalPreferences() : m_language(I18n::Language::EN), m_examMode(ExamMode::Deactivate), -#ifdef EPSILON_BOOT_PROMPT m_showPopUp(true), -#endif m_brightnessLevel(Ion::Backlight::MaxBrightness) {} I18n::Language m_language; ExamMode m_examMode; -#ifdef EPSILON_BOOT_PROMPT bool m_showPopUp; -#endif int m_brightnessLevel; }; diff --git a/apps/graph/Makefile b/apps/graph/Makefile index b3b7ef5b0..5284a2dac 100644 --- a/apps/graph/Makefile +++ b/apps/graph/Makefile @@ -1,9 +1,9 @@ apps += Graph::App app_headers += apps/graph/app.h -app_src += $(addprefix apps/graph/,\ +app_graph_src = $(addprefix apps/graph/,\ app.cpp \ - cartesian_function_store.cpp \ + continuous_function_store.cpp \ graph/banner_view.cpp \ graph/calculation_graph_controller.cpp \ graph/calculation_parameter_controller.cpp \ @@ -18,14 +18,21 @@ app_src += $(addprefix apps/graph/,\ graph/intersection_graph_controller.cpp \ graph/root_graph_controller.cpp \ graph/tangent_graph_controller.cpp \ + list/domain_parameter_controller.cpp \ list/list_controller.cpp \ list/list_parameter_controller.cpp \ list/text_field_function_title_cell.cpp \ + list/type_helper.cpp \ + list/type_parameter_controller.cpp \ + values/abscissa_title_cell.cpp \ values/derivative_parameter_controller.cpp \ values/function_parameter_controller.cpp \ + values/interval_parameter_selector_controller.cpp \ values/values_controller.cpp \ ) +app_src += $(app_graph_src) + i18n_files += $(addprefix apps/graph/,\ base.de.i18n\ base.en.i18n\ diff --git a/apps/graph/app.cpp b/apps/graph/app.cpp index 01ea5285d..510b133f5 100644 --- a/apps/graph/app.cpp +++ b/apps/graph/app.cpp @@ -28,7 +28,14 @@ App::Snapshot::Snapshot() : } App * App::Snapshot::unpack(Container * container) { - return new (container->currentAppBuffer()) App(container, this); + return new (container->currentAppBuffer()) App(this); +} + +void App::Snapshot::reset() { + Shared::FunctionApp::Snapshot::reset(); + for (int i = 0; i < Shared::ContinuousFunction::k_numberOfPlotTypes; i++) { + m_interval[i].reset(); + } } App::Descriptor * App::Snapshot::descriptor() { @@ -36,30 +43,22 @@ App::Descriptor * App::Snapshot::descriptor() { return &descriptor; } -CartesianFunctionStore * App::Snapshot::functionStore() { - return &m_functionStore; -} - -InteractiveCurveViewRange * App::Snapshot::graphRange() { - return &m_graphRange; -} - void App::Snapshot::tidy() { m_functionStore.tidy(); m_graphRange.setDelegate(nullptr); } -App::App(Container * container, Snapshot * snapshot) : - FunctionApp(container, snapshot, &m_inputViewController), - m_listController(&m_listFooter, &m_listHeader, &m_listFooter), +App::App(Snapshot * snapshot) : + FunctionApp(snapshot, &m_inputViewController), + m_listController(&m_listFooter, &m_listHeader, &m_listFooter, this), m_listFooter(&m_listHeader, &m_listController, &m_listController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGrey), m_listHeader(&m_listStackViewController, &m_listFooter, &m_listController), m_listStackViewController(&m_tabViewController, &m_listHeader), - m_graphController(&m_graphAlternateEmptyViewController, this, snapshot->functionStore(), snapshot->graphRange(), snapshot->cursor(), snapshot->indexFunctionSelectedByCursor(), snapshot->modelVersion(), snapshot->rangeVersion(), snapshot->angleUnitVersion(), &m_graphHeader), + m_graphController(&m_graphAlternateEmptyViewController, this, snapshot->graphRange(), snapshot->cursor(), snapshot->indexFunctionSelectedByCursor(), snapshot->modelVersion(), snapshot->rangeVersion(), snapshot->angleUnitVersion(), &m_graphHeader), m_graphAlternateEmptyViewController(&m_graphHeader, &m_graphController, &m_graphController), m_graphHeader(&m_graphStackViewController, &m_graphAlternateEmptyViewController, &m_graphController), m_graphStackViewController(&m_tabViewController, &m_graphHeader), - m_valuesController(&m_valuesAlternateEmptyViewController, this, snapshot->interval(), &m_valuesHeader), + m_valuesController(&m_valuesAlternateEmptyViewController, this, &m_valuesHeader), m_valuesAlternateEmptyViewController(&m_valuesHeader, &m_valuesController, &m_valuesController), m_valuesHeader(&m_valuesStackViewController, &m_valuesAlternateEmptyViewController, &m_valuesController), m_valuesStackViewController(&m_tabViewController, &m_valuesHeader), @@ -68,16 +67,17 @@ App::App(Container * container, Snapshot * snapshot) : { } -InputViewController * App::inputViewController() { - return &m_inputViewController; -} - -char App::XNT() { +CodePoint App::XNT() { + if (m_inputViewController.isEditing()) { + int selectedFunctionIndex = m_listController.selectedRow(); + Ion::Storage::Record record = functionStore()->recordAtIndex(selectedFunctionIndex); + return functionStore()->modelForRecord(record)->symbol(); + } return 'x'; } NestedMenuController * App::variableBoxForInputEventHandler(InputEventHandler * textInput) { - VariableBoxController * varBox = container()->variableBoxController(); + VariableBoxController * varBox = AppsContainer::sharedAppsContainer()->variableBoxController(); varBox->setSender(textInput); varBox->lockDeleteEvent(VariableBoxController::Page::Function); return varBox; diff --git a/apps/graph/app.h b/apps/graph/app.h index 795820047..3af4a678d 100644 --- a/apps/graph/app.h +++ b/apps/graph/app.h @@ -2,11 +2,12 @@ #define GRAPH_APP_H #include -#include "cartesian_function_store.h" +#include "continuous_function_store.h" #include "graph/graph_controller.h" #include "list/list_controller.h" #include "values/values_controller.h" #include "../shared/function_app.h" +#include "../shared/interval.h" namespace Graph { @@ -22,20 +23,40 @@ public: public: Snapshot(); App * unpack(Container * container) override; + void reset() override; Descriptor * descriptor() override; - CartesianFunctionStore * functionStore() override; - Shared::InteractiveCurveViewRange * graphRange(); + ContinuousFunctionStore * functionStore() override { return &m_functionStore; } + Shared::InteractiveCurveViewRange * graphRange() { return &m_graphRange; } + Shared::Interval * intervalForType(Shared::ContinuousFunction::PlotType plotType) { + return m_interval + static_cast(plotType); + } private: void tidy() override; - CartesianFunctionStore m_functionStore; + ContinuousFunctionStore m_functionStore; Shared::InteractiveCurveViewRange m_graphRange; + Shared::Interval m_interval[Shared::ContinuousFunction::k_numberOfPlotTypes]; }; - InputViewController * inputViewController() override; - char XNT() override; + static App * app() { + return static_cast(Container::activeApp()); + } + Snapshot * snapshot() const { + return static_cast(::App::snapshot()); + } + bool XNTCanBeOverriden() const override { return false; } + CodePoint XNT() override; NestedMenuController * variableBoxForInputEventHandler(InputEventHandler * textInput) override; - CartesianFunctionStore * functionStore() override { return static_cast(Shared::FunctionApp::functionStore()); } + ContinuousFunctionStore * functionStore() override { return snapshot()->functionStore(); } + Shared::Interval * intervalForType(Shared::ContinuousFunction::PlotType plotType) { + return snapshot()->intervalForType(plotType); + } + ValuesController * valuesController() override { + return &m_valuesController; + } + InputViewController * inputViewController() override { + return &m_inputViewController; + } private: - App(Container * container, Snapshot * snapshot); + App(Snapshot * snapshot); ListController m_listController; ButtonRowController m_listFooter; ButtonRowController m_listHeader; diff --git a/apps/graph/base.de.i18n b/apps/graph/base.de.i18n index 47586b05a..3eed42db5 100644 --- a/apps/graph/base.de.i18n +++ b/apps/graph/base.de.i18n @@ -3,6 +3,14 @@ FunctionAppCapital = "FUNKTIONEN" FunctionTab = "Funktionen" AddFunction = "Funktion hinzufügen" DeleteFunction = "Funktion löschen" +CurveType = "Bildungsgesetz der Kurve" +CartesianType = "Kartesisch " +PolarType = "Polar " +ParametricType = "Parameter " +IntervalT = "t-te Intervall" +IntervalTheta = "θ-te Intervall" +IntervalX = "x-te Intervall" +FunctionDomain = "Plot-Bereich" FunctionColor = "Farbe der Funktion" NoFunction = "Keine Funktion" NoActivatedFunction = "Keine aktive Funktion" diff --git a/apps/graph/base.en.i18n b/apps/graph/base.en.i18n index 18142b32a..c65a3370e 100644 --- a/apps/graph/base.en.i18n +++ b/apps/graph/base.en.i18n @@ -3,6 +3,14 @@ FunctionAppCapital = "FUNCTIONS" FunctionTab = "Functions" AddFunction = "Add function" DeleteFunction = "Delete function" +CurveType = "Curve type" +CartesianType = "Cartesian " +PolarType = "Polar " +ParametricType = "Parametric " +IntervalT = "t interval" +IntervalTheta = "θ interval" +IntervalX = "x interval" +FunctionDomain = "Plot range" FunctionColor = "Function color" NoFunction = "No function" NoActivatedFunction = "No function is turned on" diff --git a/apps/graph/base.es.i18n b/apps/graph/base.es.i18n index fbb763c64..c2200311d 100644 --- a/apps/graph/base.es.i18n +++ b/apps/graph/base.es.i18n @@ -3,6 +3,14 @@ FunctionAppCapital = "FUNCIÓN" FunctionTab = "Funciones" AddFunction = "Agregar una función" DeleteFunction = "Eliminar la función" +CurveType = "Tipo de curva" +CartesianType = "Cartesiano " +PolarType = "Polar " +ParametricType = "Paramétrico " +IntervalT = "Intervalo t" +IntervalTheta = "Intervalo θ" +IntervalX = "Intervalo x" +FunctionDomain = "Rango del gráfico" FunctionColor = "Color de la funcion" NoFunction = "Ninguna función" NoActivatedFunction = "Ninguna función activada" diff --git a/apps/graph/base.fr.i18n b/apps/graph/base.fr.i18n index 2d4af4a84..d95a5ae63 100644 --- a/apps/graph/base.fr.i18n +++ b/apps/graph/base.fr.i18n @@ -3,6 +3,14 @@ FunctionAppCapital = "FONCTIONS" FunctionTab = "Fonctions" AddFunction = "Ajouter une fonction" DeleteFunction = "Supprimer la fonction" +CurveType = "Type de courbe" +CartesianType = "Cartésien " +PolarType = "Polaire " +ParametricType = "Paramétrique " +IntervalT = "Intervalle t" +IntervalTheta = "Intervalle θ" +IntervalX = "Intervalle x" +FunctionDomain = "Domaine de tracé" FunctionColor = "Couleur de la fonction" NoFunction = "Aucune fonction" NoActivatedFunction = "Aucune fonction activée" diff --git a/apps/graph/base.pt.i18n b/apps/graph/base.pt.i18n index 1e33f5c91..9d56d4716 100644 --- a/apps/graph/base.pt.i18n +++ b/apps/graph/base.pt.i18n @@ -3,6 +3,14 @@ FunctionAppCapital = "FUNÇÃO" FunctionTab = "Funções" AddFunction = "Adicionar uma função" DeleteFunction = "Eliminar a função" +CurveType = "Tipo de curva" +CartesianType = "Cartesiano " +PolarType = "Polar " +ParametricType = "Paramétrico " +IntervalT = "Intervalo t" +IntervalTheta = "Intervalo θ" +IntervalX = "Intervalo x" +FunctionDomain = "Intervalo do gráfico" FunctionColor = "Cor da função" NoFunction = "Nenhuma função" NoActivatedFunction = "Sem função ativada" diff --git a/apps/graph/cartesian_function_store.cpp b/apps/graph/cartesian_function_store.cpp deleted file mode 100644 index 341ead7be..000000000 --- a/apps/graph/cartesian_function_store.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "cartesian_function_store.h" -extern "C" { -#include -#include -} -#include - -using namespace Shared; - -namespace Graph { - -Ion::Storage::Record::ErrorStatus CartesianFunctionStore::addEmptyModel() { - Ion::Storage::Record::ErrorStatus error; - CartesianFunction newModel = CartesianFunction::NewModel(&error); - return error; -} - -void CartesianFunctionStore::setMemoizedModelAtIndex(int cacheIndex, Ion::Storage::Record record) const { - assert(cacheIndex >= 0 && cacheIndex < maxNumberOfMemoizedModels()); - m_functions[cacheIndex] = CartesianFunction(record); -} - -ExpressionModelHandle * CartesianFunctionStore::memoizedModelAtIndex(int cacheIndex) const { - assert(cacheIndex >= 0 && cacheIndex < maxNumberOfMemoizedModels()); - return &m_functions[cacheIndex]; -} - -} diff --git a/apps/graph/cartesian_function_store.h b/apps/graph/cartesian_function_store.h deleted file mode 100644 index f2ff11601..000000000 --- a/apps/graph/cartesian_function_store.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef GRAPH_CARTESIAN_FUNCTION_STORE_H -#define GRAPH_CARTESIAN_FUNCTION_STORE_H - -#include "../shared/cartesian_function.h" -#include "../shared/function_store.h" -#include -#include - -namespace Graph { - -class CartesianFunctionStore : public Shared::FunctionStore { -public: - Shared::ExpiringPointer modelForRecord(Ion::Storage::Record record) const { return Shared::ExpiringPointer(static_cast(privateModelForRecord(record))); } - CodePoint symbol() const override { return Shared::CartesianFunction::Symbol(); } - CodePoint unknownSymbol() const override { return UCodePointUnknownX; } -private: - Ion::Storage::Record::ErrorStatus addEmptyModel() override; - const char * modelExtension() const override { return Ion::Storage::funcExtension; } - void setMemoizedModelAtIndex(int cacheIndex, Ion::Storage::Record record) const override; - Shared::ExpressionModelHandle * memoizedModelAtIndex(int cacheIndex) const override; - mutable Shared::CartesianFunction m_functions[k_maxNumberOfMemoizedModels]; -}; - -} - -#endif diff --git a/apps/graph/continuous_function_store.cpp b/apps/graph/continuous_function_store.cpp new file mode 100644 index 000000000..906b1d067 --- /dev/null +++ b/apps/graph/continuous_function_store.cpp @@ -0,0 +1,37 @@ +#include "continuous_function_store.h" +extern "C" { +#include +#include +} +#include + +using namespace Shared; + +namespace Graph { + +bool ContinuousFunctionStore::displaysNonCartesianFunctions(int * nbActiveFunctions) const { + int nbOfActiveFunctions = numberOfActiveFunctions(); + if (nbActiveFunctions != nullptr) { + *nbActiveFunctions = nbOfActiveFunctions; + } + return numberOfActiveFunctionsOfType(ContinuousFunction::PlotType::Cartesian) != nbOfActiveFunctions; +} + +Ion::Storage::Record::ErrorStatus ContinuousFunctionStore::addEmptyModel() { + Ion::Storage::Record::ErrorStatus error; + ContinuousFunction newModel = ContinuousFunction::NewModel(&error); + return error; +} + +ExpressionModelHandle * ContinuousFunctionStore::setMemoizedModelAtIndex(int cacheIndex, Ion::Storage::Record record) const { + assert(cacheIndex >= 0 && cacheIndex < maxNumberOfMemoizedModels()); + m_functions[cacheIndex] = ContinuousFunction(record); + return &m_functions[cacheIndex]; +} + +ExpressionModelHandle * ContinuousFunctionStore::memoizedModelAtIndex(int cacheIndex) const { + assert(cacheIndex >= 0 && cacheIndex < maxNumberOfMemoizedModels()); + return &m_functions[cacheIndex]; +} + +} diff --git a/apps/graph/continuous_function_store.h b/apps/graph/continuous_function_store.h new file mode 100644 index 000000000..fce291049 --- /dev/null +++ b/apps/graph/continuous_function_store.h @@ -0,0 +1,33 @@ +#ifndef GRAPH_CONTINUOUS_FUNCTION_STORE_H +#define GRAPH_CONTINUOUS_FUNCTION_STORE_H + +#include "../shared/function_store.h" +#include "../shared/continuous_function.h" + +namespace Graph { + +class ContinuousFunctionStore : public Shared::FunctionStore { +public: + bool displaysNonCartesianFunctions(int * nbActiveFunctions = nullptr) const; + int numberOfActiveFunctionsOfType(Shared::ContinuousFunction::PlotType plotType) const { + return numberOfModelsSatisfyingTest(&isFunctionActiveOfType, &plotType); + } + Ion::Storage::Record activeRecordOfTypeAtIndex(Shared::ContinuousFunction::PlotType plotType, int i) const { + return recordSatisfyingTestAtIndex(i, &isFunctionActiveOfType, &plotType); + } + Shared::ExpiringPointer modelForRecord(Ion::Storage::Record record) const { return Shared::ExpiringPointer(static_cast(privateModelForRecord(record))); } +private: + Ion::Storage::Record::ErrorStatus addEmptyModel() override; + const char * modelExtension() const override { return Ion::Storage::funcExtension; } + Shared::ExpressionModelHandle * setMemoizedModelAtIndex(int cacheIndex, Ion::Storage::Record record) const override; + Shared::ExpressionModelHandle * memoizedModelAtIndex(int cacheIndex) const override; + static bool isFunctionActiveOfType(Shared::ExpressionModelHandle * model, void * context) { + Shared::ContinuousFunction::PlotType plotType = *static_cast(context); + return isFunctionActive(model, context) && plotType == static_cast(model)->plotType(); + } + mutable Shared::ContinuousFunction m_functions[k_maxNumberOfMemoizedModels]; +}; + +} + +#endif diff --git a/apps/graph/graph/calculation_graph_controller.cpp b/apps/graph/graph/calculation_graph_controller.cpp index e72b21167..292de3dac 100644 --- a/apps/graph/graph/calculation_graph_controller.cpp +++ b/apps/graph/graph/calculation_graph_controller.cpp @@ -1,5 +1,6 @@ #include "calculation_graph_controller.h" #include "../app.h" +#include "../../apps_container.h" using namespace Shared; using namespace Poincare; @@ -19,14 +20,15 @@ CalculationGraphController::CalculationGraphController(Responder * parentRespond void CalculationGraphController::viewWillAppear() { assert(!m_record.isNull()); - Expression::Coordinate2D pointOfInterest = computeNewPointOfInteresetFromAbscissa(m_graphRange->xMin(), 1); - if (std::isnan(pointOfInterest.abscissa)) { + Coordinate2D pointOfInterest = computeNewPointOfInterestFromAbscissa(m_graphRange->xMin(), 1); + if (std::isnan(pointOfInterest.x1())) { m_isActive = false; m_graphView->setCursorView(nullptr); m_graphView->setBannerView(&m_defaultBannerView); } else { m_isActive = true; - m_cursor->moveTo(pointOfInterest.abscissa, pointOfInterest.value); + assert(App::app()->functionStore()->modelForRecord(m_record)->plotType() == Shared::ContinuousFunction::PlotType::Cartesian); + m_cursor->moveTo(pointOfInterest.x1(), pointOfInterest.x1(), pointOfInterest.x2()); m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); m_bannerView->setNumberOfSubviews(Shared::XYBannerView::k_numberOfSubviews); reloadBannerView(); @@ -41,20 +43,18 @@ void CalculationGraphController::setRecord(Ion::Storage::Record record) { } void CalculationGraphController::reloadBannerView() { - reloadBannerViewForCursorOnFunction(m_cursor, m_record, functionStore(), CartesianFunction::Symbol()); + reloadBannerViewForCursorOnFunction(m_cursor, m_record, functionStore(), AppsContainer::sharedAppsContainer()->globalContext()); } -Expression::Coordinate2D CalculationGraphController::computeNewPointOfInteresetFromAbscissa(double start, int direction) { - App * myApp = static_cast(app()); +Coordinate2D CalculationGraphController::computeNewPointOfInterestFromAbscissa(double start, int direction) { double step = m_graphRange->xGridUnit()/10.0; step = direction < 0 ? -step : step; double max = direction > 0 ? m_graphRange->xMax() : m_graphRange->xMin(); - return computeNewPointOfInterest(start, step, max, myApp->localContext()); + return computeNewPointOfInterest(start, step, max, textFieldDelegateApp()->localContext()); } -CartesianFunctionStore * CalculationGraphController::functionStore() const { - App * a = static_cast(app()); - return a->functionStore(); +ContinuousFunctionStore * CalculationGraphController::functionStore() const { + return App::app()->functionStore(); } bool CalculationGraphController::handleLeftRightEvent(Ion::Events::Event event) { @@ -71,11 +71,12 @@ bool CalculationGraphController::handleEnter() { } bool CalculationGraphController::moveCursorHorizontally(int direction) { - Expression::Coordinate2D newPointOfInterest = computeNewPointOfInteresetFromAbscissa(m_cursor->x(), direction); - if (std::isnan(newPointOfInterest.abscissa)) { + Coordinate2D newPointOfInterest = computeNewPointOfInterestFromAbscissa(m_cursor->x(), direction); + if (std::isnan(newPointOfInterest.x1())) { return false; } - m_cursor->moveTo(newPointOfInterest.abscissa, newPointOfInterest.value); + assert(App::app()->functionStore()->modelForRecord(m_record)->plotType() == Shared::ContinuousFunction::PlotType::Cartesian); + m_cursor->moveTo(newPointOfInterest.x1(), newPointOfInterest.x1(), newPointOfInterest.x2()); return true; } diff --git a/apps/graph/graph/calculation_graph_controller.h b/apps/graph/graph/calculation_graph_controller.h index 976a63f6c..a400f56ab 100644 --- a/apps/graph/graph/calculation_graph_controller.h +++ b/apps/graph/graph/calculation_graph_controller.h @@ -5,7 +5,7 @@ #include "banner_view.h" #include "../../shared/simple_interactive_curve_view_controller.h" #include "../../shared/function_banner_delegate.h" -#include "../cartesian_function_store.h" +#include "../continuous_function_store.h" namespace Graph { @@ -20,9 +20,9 @@ protected: float cursorBottomMarginRatio() override { return 0.15f; } BannerView * bannerView() override { return m_bannerView; } void reloadBannerView() override; - Poincare::Expression::Coordinate2D computeNewPointOfInteresetFromAbscissa(double start, int direction); - CartesianFunctionStore * functionStore() const; - virtual Poincare::Expression::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) = 0; + Poincare::Coordinate2D computeNewPointOfInterestFromAbscissa(double start, int direction); + ContinuousFunctionStore * functionStore() const; + virtual Poincare::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) = 0; GraphView * m_graphView; BannerView * m_bannerView; Shared::InteractiveCurveViewRange * m_graphRange; diff --git a/apps/graph/graph/calculation_parameter_controller.cpp b/apps/graph/graph/calculation_parameter_controller.cpp index f7df007b4..050794c3e 100644 --- a/apps/graph/graph/calculation_parameter_controller.cpp +++ b/apps/graph/graph/calculation_parameter_controller.cpp @@ -1,5 +1,6 @@ #include "calculation_parameter_controller.h" #include "graph_controller.h" +#include "../app.h" #include #include @@ -31,46 +32,30 @@ View * CalculationParameterController::view() { return &m_selectableTableView; } +void CalculationParameterController::viewWillAppear() { + m_selectableTableView.reloadData(); +} + void CalculationParameterController::didBecomeFirstResponder() { m_selectableTableView.selectCellAtLocation(0, 0); - app()->setFirstResponder(&m_selectableTableView); + Container::activeApp()->setFirstResponder(&m_selectableTableView); } bool CalculationParameterController::handleEvent(Ion::Events::Event event) { int row = selectedRow(); if (event == Ion::Events::OK || event == Ion::Events::EXE || (event == Ion::Events::Right && row == 0)) { - ViewController * controller = nullptr; - switch(row) { - case 0: - m_preimageParameterController.setRecord(m_record); - controller = &m_preimageParameterController; - break; - case 1: - m_intersectionGraphController.setRecord(m_record); - controller = &m_intersectionGraphController; - break; - case 2: - m_maximumGraphController.setRecord(m_record); - controller = &m_maximumGraphController; - break; - case 3: - m_minimumGraphController.setRecord(m_record); - controller = &m_minimumGraphController; - break; - case 4: - m_rootGraphController.setRecord(m_record); - controller = &m_rootGraphController; - break; - case 5: - m_tangentGraphController.setRecord(m_record); - controller = &m_tangentGraphController; - break; - case 6: - m_integralGraphController.setRecord(m_record); - controller = &m_integralGraphController; - break; - default: - return false; + static ViewController * controllers[] = {&m_preimageParameterController, &m_intersectionGraphController, &m_maximumGraphController, &m_minimumGraphController, &m_rootGraphController, &m_tangentGraphController, &m_integralGraphController}; + int displayIntersection = shouldDisplayIntersection(); + int indexController = row == 0 ? 0 : row + !displayIntersection; + ViewController * controller = controllers[indexController]; + if (row == 0) { + m_preimageParameterController.setRecord(m_record); + } else if (row == 4 + displayIntersection) { + m_tangentGraphController.setRecord(m_record); + } else if (row == 5 + displayIntersection) { + m_integralGraphController.setRecord(m_record); + } else { + static_cast(controller)->setRecord(m_record); } StackViewController * stack = static_cast(parentResponder()); if (row > 0) { @@ -88,9 +73,9 @@ bool CalculationParameterController::handleEvent(Ion::Events::Event event) { return false; } -int CalculationParameterController::numberOfRows() { - constexpr int k_totalNumberOfCells = k_totalNumberOfReusableCells + 1; - return k_totalNumberOfCells; +int CalculationParameterController::numberOfRows() const { + // Inverse row + [optional intersection row] + all other rows (max, min zeros, tangent, integral) + return 1 + shouldDisplayIntersection() + k_totalNumberOfReusableCells - 1; }; KDCoordinate CalculationParameterController::rowHeight(int j) { @@ -120,9 +105,10 @@ int CalculationParameterController::typeAtLocation(int i, int j) { } void CalculationParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { + assert(index >= 0 && index <= numberOfRows()); if (cell != &m_preimageCell) { I18n::Message titles[] = {I18n::Message::Intersection, I18n::Message::Maximum, I18n::Message::Minimum, I18n::Message::Zeros, I18n::Message::Tangent, I18n::Message::Integral}; - static_cast(cell)->setMessage(titles[index - 1]); + static_cast(cell)->setMessage(titles[index - 1 + !shouldDisplayIntersection()]); } } @@ -130,4 +116,12 @@ void CalculationParameterController::setRecord(Ion::Storage::Record record) { m_record = record; } +bool CalculationParameterController::shouldDisplayIntersection() const { + ContinuousFunctionStore * store = App::app()->functionStore(); + int numberOfCartesianFunctions = store->numberOfActiveFunctionsOfType(Shared::ContinuousFunction::PlotType::Cartesian); + // Intersection row is displayed when all functions are cartesian and there are least two of them + // TODO: compute intersections between polar/parametric/cartesian functions? + return numberOfCartesianFunctions > 1 && numberOfCartesianFunctions == store->numberOfActiveFunctions(); +} + } diff --git a/apps/graph/graph/calculation_parameter_controller.h b/apps/graph/graph/calculation_parameter_controller.h index 043f53a7b..89b747a80 100644 --- a/apps/graph/graph/calculation_parameter_controller.h +++ b/apps/graph/graph/calculation_parameter_controller.h @@ -2,7 +2,6 @@ #define GRAPH_CALCULATION_PARAMETER_CONTROLLER_H #include -#include "../cartesian_function_store.h" #include "preimage_parameter_controller.h" #include "tangent_graph_controller.h" #include "extremum_graph_controller.h" @@ -21,8 +20,9 @@ public: View * view() override; const char * title() override; bool handleEvent(Ion::Events::Event event) override; + void viewWillAppear() override; void didBecomeFirstResponder() override; - int numberOfRows() override; + int numberOfRows() const override; KDCoordinate rowHeight(int j) override; HighlightCell * reusableCell(int index, int type) override; int reusableCellCount(int type) override; @@ -30,6 +30,7 @@ public: void willDisplayCellForIndex(HighlightCell * cell, int index) override; void setRecord(Ion::Storage::Record record); private: + bool shouldDisplayIntersection() const; MessageTableCellWithChevron m_preimageCell; constexpr static int k_totalNumberOfReusableCells = 6; MessageTableCell m_cells[k_totalNumberOfReusableCells]; diff --git a/apps/graph/graph/curve_parameter_controller.cpp b/apps/graph/graph/curve_parameter_controller.cpp index ad595ec57..213f5ae51 100644 --- a/apps/graph/graph/curve_parameter_controller.cpp +++ b/apps/graph/graph/curve_parameter_controller.cpp @@ -1,5 +1,6 @@ #include "curve_parameter_controller.h" #include "graph_controller.h" +#include "../app.h" #include #include @@ -9,7 +10,7 @@ namespace Graph { CurveParameterController::CurveParameterController(InputEventHandlerDelegate * inputEventHandlerDelegate, InteractiveCurveViewRange * graphRange, BannerView * bannerView, CurveViewCursor * cursor, GraphView * graphView, GraphController * graphController) : FunctionCurveParameterController(), - m_goToParameterController(this, inputEventHandlerDelegate, graphRange, cursor, I18n::Message::X), + m_goToParameterController(this, inputEventHandlerDelegate, graphRange, cursor), m_graphController(graphController), m_calculationCell(I18n::Message::Compute), m_derivativeCell(I18n::Message::DerivateNumber), @@ -29,8 +30,15 @@ void CurveParameterController::willDisplayCellForIndex(HighlightCell * cell, int } bool CurveParameterController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::OK || event == Ion::Events::EXE || (event == Ion::Events::Right && (selectedRow() == 0 || selectedRow() == 1))) { - switch (selectedRow()) { + int index; + if (shouldDisplayCalculationAndDerivative()) { + index = selectedRow(); + } else { + assert(selectedRow() == 0); + index = 1; + } + if (event == Ion::Events::OK || event == Ion::Events::EXE || (event == Ion::Events::Right && (index == 0 || index == 1))) { + switch (index) { case 0: { m_calculationParameterController.setRecord(m_record); @@ -47,25 +55,42 @@ bool CurveParameterController::handleEvent(Ion::Events::Event event) { return true; } default: + assert(false); return false; } } return false; } -int CurveParameterController::numberOfRows() { - return k_totalNumberOfCells; +int CurveParameterController::numberOfRows() const { + return reusableCellCount(); }; HighlightCell * CurveParameterController::reusableCell(int index) { - assert(index >= 0); - assert(index < k_totalNumberOfCells); + assert(0 <= index && index < reusableCellCount()); HighlightCell * cells[] = {&m_calculationCell, &m_goToCell, &m_derivativeCell}; - return cells[index]; + return cells[cellIndex(index)]; } -int CurveParameterController::reusableCellCount() { - return k_totalNumberOfCells; +int CurveParameterController::reusableCellCount() const { + return 1 + (shouldDisplayCalculationAndDerivative() ? 2 : 0); +} + +void CurveParameterController::viewWillAppear() { + m_selectableTableView.reloadData(); +} + +bool CurveParameterController::shouldDisplayCalculationAndDerivative() const { + Shared::ExpiringPointer f = App::app()->functionStore()->modelForRecord(m_record); + return f->plotType() == ContinuousFunction::PlotType::Cartesian; +} + +int CurveParameterController::cellIndex(int visibleCellIndex) const { + if (shouldDisplayCalculationAndDerivative()) { + return visibleCellIndex; + } + assert(visibleCellIndex == 0); + return 1; } FunctionGoToParameterController * CurveParameterController::goToParameterController() { diff --git a/apps/graph/graph/curve_parameter_controller.h b/apps/graph/graph/curve_parameter_controller.h index 275440db4..d5a587af1 100644 --- a/apps/graph/graph/curve_parameter_controller.h +++ b/apps/graph/graph/curve_parameter_controller.h @@ -14,15 +14,17 @@ public: CurveParameterController(InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * graphRange, BannerView * bannerView, Shared::CurveViewCursor * cursor, GraphView * graphView, GraphController * graphController); const char * title() override; bool handleEvent(Ion::Events::Event event) override; - int numberOfRows() override; + int numberOfRows() const override; HighlightCell * reusableCell(int index) override; - int reusableCellCount() override; + int reusableCellCount() const override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; + void viewWillAppear() override; private: + bool shouldDisplayCalculationAndDerivative() const; + int cellIndex(int visibleCellIndex) const; Shared::FunctionGoToParameterController * goToParameterController() override; Shared::FunctionGoToParameterController m_goToParameterController; GraphController * m_graphController; - constexpr static int k_totalNumberOfCells = 3; MessageTableCellWithChevron m_calculationCell; MessageTableCellWithSwitch m_derivativeCell; CalculationParameterController m_calculationParameterController; diff --git a/apps/graph/graph/extremum_graph_controller.cpp b/apps/graph/graph/extremum_graph_controller.cpp index d442d3e26..6044d07f9 100644 --- a/apps/graph/graph/extremum_graph_controller.cpp +++ b/apps/graph/graph/extremum_graph_controller.cpp @@ -1,7 +1,7 @@ #include "extremum_graph_controller.h" -#include "../app.h" +#include "../../shared/poincare_helpers.h" +#include -using namespace Shared; using namespace Poincare; namespace Graph { @@ -15,7 +15,7 @@ const char * MinimumGraphController::title() { return I18n::translate(I18n::Message::Minimum); } -Expression::Coordinate2D MinimumGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) { +Coordinate2D MinimumGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) { return functionStore()->modelForRecord(m_record)->nextMinimumFrom(start, step, max, context); } @@ -28,7 +28,7 @@ const char * MaximumGraphController::title() { return I18n::translate(I18n::Message::Maximum); } -Expression::Coordinate2D MaximumGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) { +Coordinate2D MaximumGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) { return functionStore()->modelForRecord(m_record)->nextMaximumFrom(start, step, max, context); } diff --git a/apps/graph/graph/extremum_graph_controller.h b/apps/graph/graph/extremum_graph_controller.h index 3fd55107c..1966faf46 100644 --- a/apps/graph/graph/extremum_graph_controller.h +++ b/apps/graph/graph/extremum_graph_controller.h @@ -10,7 +10,7 @@ public: MinimumGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor); const char * title() override; private: - Poincare::Expression::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; + Poincare::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; }; class MaximumGraphController : public CalculationGraphController { @@ -18,7 +18,7 @@ public: MaximumGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor); const char * title() override; private: - Poincare::Expression::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; + Poincare::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; }; } diff --git a/apps/graph/graph/graph_controller.cpp b/apps/graph/graph/graph_controller.cpp index 23e5e2e23..22b754337 100644 --- a/apps/graph/graph/graph_controller.cpp +++ b/apps/graph/graph/graph_controller.cpp @@ -1,16 +1,20 @@ #include "graph_controller.h" #include "../app.h" +using namespace Poincare; using namespace Shared; namespace Graph { +static inline float minFloat(float x, float y) { return x < y ? x : y; } static inline float maxFloat(float x, float y) { return x > y ? x : y; } +static inline double minDouble(double x, double y) { return x < y ? x : y; } +static inline double maxDouble(double x, double y) { return x > y ? x : y; } -GraphController::GraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, CartesianFunctionStore * functionStore, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header) : +GraphController::GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header) : FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, curveViewRange, &m_view, cursor, indexFunctionSelectedByCursor, modelVersion, rangeVersion, angleUnitVersion), m_bannerView(this, inputEventHandlerDelegate, this), - m_view(functionStore, curveViewRange, m_cursor, &m_bannerView, &m_cursorView), + m_view(curveViewRange, m_cursor, &m_bannerView, &m_cursorView), m_graphRange(curveViewRange), m_curveParameterController(inputEventHandlerDelegate, curveViewRange, &m_bannerView, m_cursor, &m_view, this), m_displayDerivativeInBanner(false) @@ -27,73 +31,218 @@ I18n::Message GraphController::emptyMessage() { void GraphController::viewWillAppear() { m_view.drawTangent(false); +#ifdef GRAPH_CURSOR_SPEEDUP + m_cursorView.resetMemoization(); +#endif m_view.setCursorView(&m_cursorView); - m_bannerView.setNumberOfSubviews(Shared::XYBannerView::k_numberOfSubviews + m_displayDerivativeInBanner); FunctionGraphController::viewWillAppear(); - selectFunctionWithCursor(indexFunctionSelectedByCursor()); // update the color of the cursor + selectFunctionWithCursor(indexFunctionSelectedByCursor()); } -bool GraphController::displayDerivativeInBanner() const { - return m_displayDerivativeInBanner; +bool GraphController::defautRangeIsNormalized() const { + return functionStore()->displaysNonCartesianFunctions(); } -void GraphController::setDisplayDerivativeInBanner(bool displayDerivative) { - m_displayDerivativeInBanner = displayDerivative; +void GraphController::interestingFunctionRange(ExpiringPointer f, float tMin, float tMax, float step, float * xm, float * xM, float * ym, float * yM) const { + Poincare::Context * context = textFieldDelegateApp()->localContext(); + const int balancedBound = std::floor((tMax-tMin)/2/step); + for (int j = -balancedBound; j <= balancedBound ; j++) { + float t = (tMin+tMax)/2 + step * j; + Coordinate2D xy = f->evaluateXYAtParameter(t, context); + float x = xy.x1(); + float y = xy.x2(); + if (!std::isnan(x) && !std::isinf(x) && !std::isnan(y) && !std::isinf(y)) { + *xm = minFloat(*xm, x); + *xM = maxFloat(*xM, x); + *ym = minFloat(*ym, y); + *yM = maxFloat(*yM, y); + } + } +} + +void GraphController::interestingRanges(float * xm, float * xM, float * ym, float * yM) const { + float resultxMin = FLT_MAX; + float resultxMax = -FLT_MAX; + float resultyMin = FLT_MAX; + float resultyMax = -FLT_MAX; + assert(functionStore()->numberOfActiveFunctions() > 0); + int functionsCount = 0; + if (functionStore()->displaysNonCartesianFunctions(&functionsCount)) { + for (int i = 0; i < functionsCount; i++) { + ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); + if (f->plotType() == ContinuousFunction::PlotType::Cartesian) { + continue; + } + /* Scan x-range from the middle to the extrema in order to get balanced + * y-range for even functions (y = 1/x). */ + double tMin = f->tMin(); + double tMax = f->tMax(); + assert(!std::isnan(tMin)); + assert(!std::isnan(tMax)); + assert(!std::isnan(f->rangeStep())); + interestingFunctionRange(f, tMin, tMax, f->rangeStep(), &resultxMin, &resultxMax, &resultyMin, &resultyMax); + } + if (resultxMin > resultxMax) { + resultxMin = - Range1D::k_default; + resultxMax = Range1D::k_default; + } + } else { + resultxMin = const_cast(this)->interactiveCurveViewRange()->xMin(); + resultxMax = const_cast(this)->interactiveCurveViewRange()->xMax(); + } + /* In practice, a step smaller than a pixel's width is needed for sampling + * the values of a function. Otherwise some relevant extremal values may be + * missed. */ + const float step = const_cast(this)->curveView()->pixelWidth() / 2; + for (int i = 0; i < functionsCount; i++) { + ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); + if (f->plotType() != ContinuousFunction::PlotType::Cartesian) { + continue; + } + /* Scan x-range from the middle to the extrema in order to get balanced + * y-range for even functions (y = 1/x). */ + assert(!std::isnan(f->tMin())); + assert(!std::isnan(f->tMax())); + double tMin = maxFloat(f->tMin(), resultxMin); + double tMax = minFloat(f->tMax(), resultxMax); + interestingFunctionRange(f, tMin, tMax, step, &resultxMin, &resultxMax, &resultyMin, &resultyMax); + } + if (resultyMin > resultyMax) { + resultyMin = - Range1D::k_default; + resultyMax = Range1D::k_default; + } + + *xm = resultxMin; + *xM = resultxMax; + *ym = resultyMin; + *yM = resultyMax; } float GraphController::interestingXHalfRange() const { float characteristicRange = 0.0f; - TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app(); - for (int i = 0; i < functionStore()->numberOfActiveFunctions(); i++) { - ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); - float fRange = f->expressionReduced(myApp->localContext()).characteristicXRange(*(myApp->localContext()), Poincare::Preferences::sharedPreferences()->angleUnit()); - if (!std::isnan(fRange)) { + Poincare::Context * context = textFieldDelegateApp()->localContext(); + ContinuousFunctionStore * store = functionStore(); + int nbActiveFunctions = store->numberOfActiveFunctions(); + double tMin = INFINITY; + double tMax = -INFINITY; + for (int i = 0; i < nbActiveFunctions; i++) { + ExpiringPointer f = store->modelForRecord(store->activeRecordAtIndex(i)); + float fRange = f->expressionReduced(context).characteristicXRange(context, Poincare::Preferences::sharedPreferences()->angleUnit()); + if (!std::isnan(fRange) && !std::isinf(fRange)) { characteristicRange = maxFloat(fRange, characteristicRange); } + // Compute the combined range of the functions + assert(f->plotType() == ContinuousFunction::PlotType::Cartesian); // So that tMin tMax represents xMin xMax + tMin = minDouble(tMin, f->tMin()); + tMax = maxDouble(tMax, f->tMax()); } - return (characteristicRange > 0.0f ? 1.6f*characteristicRange : InteractiveCurveViewRangeDelegate::interestingXHalfRange()); -} - -int GraphController::estimatedBannerNumberOfLines() const { - return 1 + m_displayDerivativeInBanner; + constexpr float rangeMultiplicator = 1.6f; + if (characteristicRange > 0.0f ) { + return rangeMultiplicator * characteristicRange; + } + float defaultXHalfRange = InteractiveCurveViewRangeDelegate::interestingXHalfRange(); + assert(tMin <= tMax); + if (tMin >= -defaultXHalfRange && tMax <= defaultXHalfRange) { + /* If the combined Range of the functions is smaller than the default range, + * use it. */ + float f = rangeMultiplicator * (float)maxDouble(std::fabs(tMin), std::fabs(tMax)); + return (std::isnan(f) || std::isinf(f)) ? defaultXHalfRange : f; + } + return defaultXHalfRange; } void GraphController::selectFunctionWithCursor(int functionIndex) { FunctionGraphController::selectFunctionWithCursor(functionIndex); - ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(functionIndex)); + ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(functionIndex)); m_cursorView.setColor(f->color()); } -BannerView * GraphController::bannerView() { - return &m_bannerView; -} - void GraphController::reloadBannerView() { + Ion::Storage::Record record = functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor()); + bool displayDerivative = m_displayDerivativeInBanner && + functionStore()->modelForRecord(record)->plotType() == ContinuousFunction::PlotType::Cartesian; + m_bannerView.setNumberOfSubviews(Shared::XYBannerView::k_numberOfSubviews + displayDerivative); FunctionGraphController::reloadBannerView(); - if (functionStore()->numberOfActiveFunctions() == 0 || !m_displayDerivativeInBanner) { + if (!displayDerivative) { return; } - Ion::Storage::Record record = functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor()); - App * myApp = static_cast(app()); - reloadDerivativeInBannerViewForCursorOnFunction(m_cursor, record, myApp); + reloadDerivativeInBannerViewForCursorOnFunction(m_cursor, record); } bool GraphController::moveCursorHorizontally(int direction) { Ion::Storage::Record record = functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor()); - App * myApp = static_cast(app()); - return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, record, myApp); + return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, record); } -InteractiveCurveViewRange * GraphController::interactiveCurveViewRange() { - return m_graphRange; +int GraphController::nextCurveIndexVertically(bool goingUp, int currentSelectedCurve, Poincare::Context * context) const { + int nbOfActiveFunctions = 0; + if (!functionStore()->displaysNonCartesianFunctions(&nbOfActiveFunctions)) { + return FunctionGraphController::nextCurveIndexVertically(goingUp, currentSelectedCurve, context); + } + int nextActiveFunctionIndex = currentSelectedCurve + (goingUp ? -1 : 1); + return nextActiveFunctionIndex >= nbOfActiveFunctions ? -1 : nextActiveFunctionIndex; } -GraphView * GraphController::functionGraphView() { - return &m_view; +double GraphController::defaultCursorT(Ion::Storage::Record record) { + ExpiringPointer function = functionStore()->modelForRecord(record); + if (function->plotType() == ContinuousFunction::PlotType::Cartesian) { + return FunctionGraphController::defaultCursorT(record); + } + return function->tMin(); } -CurveParameterController * GraphController::curveParameterController() { - return &m_curveParameterController; +bool GraphController::shouldSetDefaultOnModelChange() const { + return functionStore()->displaysNonCartesianFunctions(); +} + +void GraphController::jumpToLeftRightCurve(double t, int direction, int functionsCount, Ion::Storage::Record record) { + if (functionsCount == 1) { + return; + } + int nextCurveIndex = -1; + double xDelta = DBL_MAX; + double nextY = 0.0; + double nextT = 0.0; + for (int i = 0; i < functionsCount; i++) { + Ion::Storage::Record currentRecord = functionStore()->activeRecordAtIndex(i); + if (currentRecord == record) { + continue; + } + ExpiringPointer f = functionStore()->modelForRecord(currentRecord); + assert(f->plotType() == ContinuousFunction::PlotType::Cartesian); + /* Select the closest horizontal curve, then the closest vertically, then + * the lowest curve index. */ + double currentTMin = f->tMin(); + double currentTMax = f->tMax(); + assert(!std::isnan(currentTMin)); + assert(!std::isnan(currentTMax)); + if ((direction > 0 && currentTMax > t) + ||(direction < 0 && currentTMin < t)) + { + double currentXDelta = direction > 0 ? + (t >= currentTMin ? 0.0 : currentTMin - t) : + (t <= currentTMax ? 0.0 : t - currentTMax); + assert(currentXDelta >= 0.0); + if (currentXDelta <= xDelta) { + double potentialNextTMin = f->tMin(); + double potentialNextTMax = f->tMax(); + double potentialNextT = maxDouble(potentialNextTMin, minDouble(potentialNextTMax, t)); + Coordinate2D xy = f->evaluateXYAtParameter(potentialNextT, App::app()->localContext()); + if (currentXDelta < xDelta || std::abs(xy.x2() - m_cursor->y()) < std::abs(nextY - m_cursor->y())) { + nextCurveIndex = i; + xDelta = currentXDelta; + nextY = xy.x2(); + nextT = potentialNextT; + } + } + } + } + if (nextCurveIndex < 0) { + return; + } + m_cursor->moveTo(nextT, nextT, nextY); + selectFunctionWithCursor(nextCurveIndex); + return; } } diff --git a/apps/graph/graph/graph_controller.h b/apps/graph/graph/graph_controller.h index b81df43d4..74f2f1105 100644 --- a/apps/graph/graph/graph_controller.h +++ b/apps/graph/graph/graph_controller.h @@ -9,28 +9,36 @@ #include "../../shared/curve_view_cursor.h" #include "../../shared/round_cursor_view.h" #include "../../shared/interactive_curve_view_range.h" -#include "../cartesian_function_store.h" +#include "../continuous_function_store.h" namespace Graph { class GraphController : public Shared::FunctionGraphController, public GraphControllerHelper { public: - GraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, CartesianFunctionStore * functionStore, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header); + GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header); I18n::Message emptyMessage() override; void viewWillAppear() override; - bool displayDerivativeInBanner() const; - void setDisplayDerivativeInBanner(bool displayDerivative); + bool displayDerivativeInBanner() const { return m_displayDerivativeInBanner; } + void setDisplayDerivativeInBanner(bool displayDerivative) { m_displayDerivativeInBanner = displayDerivative; } float interestingXHalfRange() const override; + void interestingRanges(float * xm, float * xM, float * ym, float * yM) const override; private: - int estimatedBannerNumberOfLines() const override; + int estimatedBannerNumberOfLines() const override { return 1 + m_displayDerivativeInBanner; } void selectFunctionWithCursor(int functionIndex) override; - BannerView * bannerView() override; + BannerView * bannerView() override { return &m_bannerView; } void reloadBannerView() override; bool moveCursorHorizontally(int direction) override; - Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override; - GraphView * functionGraphView() override; - CurveParameterController * curveParameterController() override; - CartesianFunctionStore * functionStore() const override { return static_cast(Shared::FunctionGraphController::functionStore()); } + int nextCurveIndexVertically(bool goingUp, int currentSelectedCurve, Poincare::Context * context) const override; + double defaultCursorT(Ion::Storage::Record record) override; + Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; } + GraphView * functionGraphView() override { return &m_view; } + CurveParameterController * curveParameterController() override { return &m_curveParameterController; } + ContinuousFunctionStore * functionStore() const override { return static_cast(Shared::FunctionGraphController::functionStore()); } + bool defautRangeIsNormalized() const override; + void interestingFunctionRange(Shared::ExpiringPointer f, float tMin, float tMax, float step, float * xm, float * xM, float * ym, float * yM) const; + bool shouldSetDefaultOnModelChange() const override; + void jumpToLeftRightCurve(double t, int direction, int functionsCount, Ion::Storage::Record record) override; + Shared::RoundCursorView m_cursorView; BannerView m_bannerView; GraphView m_view; diff --git a/apps/graph/graph/graph_controller_helper.cpp b/apps/graph/graph/graph_controller_helper.cpp index 00adae62c..39e0b35d3 100644 --- a/apps/graph/graph/graph_controller_helper.cpp +++ b/apps/graph/graph/graph_controller_helper.cpp @@ -1,33 +1,58 @@ #include "graph_controller_helper.h" #include "../../shared/function_banner_delegate.h" #include "../app.h" -#include "../../constant.h" #include "../../shared/poincare_helpers.h" +#include using namespace Shared; using namespace Poincare; namespace Graph { -bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, App * app) { - ExpiringPointer function = app->functionStore()->modelForRecord(record); - double xCursorPosition = cursor->x(); - double x = direction > 0 ? xCursorPosition + range->xGridUnit()/numberOfStepsInGradUnit : xCursorPosition - range->xGridUnit()/numberOfStepsInGradUnit; - double y = function->evaluateAtAbscissa(x, app->localContext()); - cursor->moveTo(x, y); +static inline double minDouble(double x, double y) { return x < y ? x : y; } +static inline double maxDouble(double x, double y) { return x > y ? x : y; } + +bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record) { + ExpiringPointer function = App::app()->functionStore()->modelForRecord(record); + double tCursorPosition = cursor->t(); + double t = tCursorPosition; + double tMin = function->tMin(); + double tMax = function->tMax(); + int functionsCount = -1; + if (((direction > 0 && std::abs(t-tMax) < DBL_EPSILON) + || (direction < 0 && std::abs(t-tMin) < DBL_EPSILON)) + && !App::app()->functionStore()->displaysNonCartesianFunctions(&functionsCount)) + { + jumpToLeftRightCurve(t, direction, functionsCount, record); + return true; + } + function = App::app()->functionStore()->modelForRecord(record); // Reload the expiring pointer + double dir = (direction > 0 ? 1.0 : -1.0); + ContinuousFunction::PlotType type = function->plotType(); + if (type == ContinuousFunction::PlotType::Cartesian) { + t += dir * range->xGridUnit()/numberOfStepsInGradUnit; + } else { + assert(type == ContinuousFunction::PlotType::Polar || type == ContinuousFunction::PlotType::Parametric); + t += dir * (tMax-tMin)/k_definitionDomainDivisor; + } + t = maxDouble(tMin, minDouble(tMax, t)); + Coordinate2D xy = function->evaluateXYAtParameter(t, App::app()->localContext()); + cursor->moveTo(t, xy.x1(), xy.x2()); return true; } -void GraphControllerHelper::reloadDerivativeInBannerViewForCursorOnFunction(Shared::CurveViewCursor * cursor, Ion::Storage::Record record, App * app) { - ExpiringPointer function = app->functionStore()->modelForRecord(record); - constexpr size_t bufferSize = FunctionBannerDelegate::k_maxNumberOfCharacters+PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits); +void GraphControllerHelper::reloadDerivativeInBannerViewForCursorOnFunction(Shared::CurveViewCursor * cursor, Ion::Storage::Record record) { + ExpiringPointer function = App::app()->functionStore()->modelForRecord(record); + constexpr size_t bufferSize = FunctionBannerDelegate::k_maxNumberOfCharacters+PrintFloat::charSizeForFloatsWithPrecision(Preferences::LargeNumberOfSignificantDigits); char buffer[bufferSize]; const char * space = " "; - int numberOfChar = function->derivativeNameWithArgument(buffer, bufferSize, CartesianFunction::Symbol()); + int numberOfChar = function->derivativeNameWithArgument(buffer, bufferSize); const char * legend = "="; + assert(numberOfChar <= bufferSize); numberOfChar += strlcpy(buffer+numberOfChar, legend, bufferSize-numberOfChar); - double y = function->approximateDerivative(cursor->x(), app->localContext()); - numberOfChar += PoincareHelpers::ConvertFloatToText(y, buffer + numberOfChar, bufferSize-numberOfChar, Constant::ShortNumberOfSignificantDigits); + double y = function->approximateDerivative(cursor->x(), App::app()->localContext()); + numberOfChar += PoincareHelpers::ConvertFloatToText(y, buffer + numberOfChar, bufferSize-numberOfChar, Preferences::ShortNumberOfSignificantDigits); + assert(numberOfChar <= bufferSize); strlcpy(buffer+numberOfChar, space, bufferSize-numberOfChar); bannerView()->derivativeView()->setText(buffer); bannerView()->reload(); diff --git a/apps/graph/graph/graph_controller_helper.h b/apps/graph/graph/graph_controller_helper.h index 811a99f79..8f8e2a7b7 100644 --- a/apps/graph/graph/graph_controller_helper.h +++ b/apps/graph/graph/graph_controller_helper.h @@ -11,9 +11,12 @@ class App; class GraphControllerHelper { protected: - bool privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, App * app); - void reloadDerivativeInBannerViewForCursorOnFunction(Shared::CurveViewCursor * cursor, Ion::Storage::Record record, App * app); + bool privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record); + void reloadDerivativeInBannerViewForCursorOnFunction(Shared::CurveViewCursor * cursor, Ion::Storage::Record record); virtual BannerView * bannerView() = 0; +private: + static constexpr double k_definitionDomainDivisor = 96.0; + virtual void jumpToLeftRightCurve(double t, int direction, int functionsCount, Ion::Storage::Record record) {} }; } diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index 203598cfb..3a09ba708 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -1,14 +1,14 @@ #include "graph_view.h" -#include +#include "../app.h" +#include using namespace Shared; namespace Graph { -GraphView::GraphView(CartesianFunctionStore * functionStore, InteractiveCurveViewRange * graphRange, - CurveViewCursor * cursor, BannerView * bannerView, View * cursorView) : +GraphView::GraphView(InteractiveCurveViewRange * graphRange, + CurveViewCursor * cursor, Shared::BannerView * bannerView, CursorView * cursorView) : FunctionGraphView(graphRange, cursor, bannerView, cursorView), - m_functionStore(functionStore), m_tangent(false) { } @@ -23,35 +23,56 @@ void GraphView::reload() { void GraphView::drawRect(KDContext * ctx, KDRect rect) const { FunctionGraphView::drawRect(ctx, rect); - for (int i = 0; i < m_functionStore->numberOfActiveFunctions(); i++) { - Ion::Storage::Record record = m_functionStore->activeRecordAtIndex(i); - ExpiringPointer f = m_functionStore->modelForRecord(record);; + ContinuousFunctionStore * functionStore = App::app()->functionStore(); + const int activeFunctionsCount = functionStore->numberOfActiveFunctions(); + for (int i = 0; i < activeFunctionsCount ; i++) { + Ion::Storage::Record record = functionStore->activeRecordAtIndex(i); + ExpiringPointer f = functionStore->modelForRecord(record);; + Shared::ContinuousFunction::PlotType type = f->plotType(); + float tmin = f->tMin(); + float tmax = f->tMax(); + /* The step is a fraction of tmax-tmin. We will evaluate the function at + * every step and if the consecutive dots are close enough, we won't + * evaluate any more dot within the step. We pick a very strange fraction + * denominator to avoid evaluating a periodic function periodically. For + * example, if tstep was (tmax - tmin)/10, the polar function r(θ) = sin(5θ) + * defined on 0..2Ï€ would be evaluated on r(0) = 0, r(Ï€/5) = 0, r(2*Ï€/5) = 0 + * which would lead to no curve at all. With 10.0938275501223, the + * problematic functions are the functions whose period is proportionned to + * 10.0938275501223 which are hopefully rare enough. + * TODO: The drawCurve algorithm should use the derivative function to know + * how fast the function moves... */ + float tstep = (tmax-tmin)/10.0938275501223f; - /* Draw function (color the area under curve of the selected function) */ - if (record == m_selectedRecord) { - drawCurve(ctx, rect, [](float t, void * model, void * context) { - CartesianFunction * f = (CartesianFunction *)model; - Poincare::Context * c = (Poincare::Context *)context; - return f->evaluateAtAbscissa(t, c); - }, f.operator->(), context(), f->color(), true, m_highlightedStart, m_highlightedEnd); - } else { - drawCurve(ctx, rect, [](float t, void * model, void * context) { - CartesianFunction * f = (CartesianFunction *)model; - Poincare::Context * c = (Poincare::Context *)context; - return f->evaluateAtAbscissa(t, c); - }, f.operator->(), context(), f->color()); + // Cartesian + if (type == Shared::ContinuousFunction::PlotType::Cartesian) { + drawCartesianCurve(ctx, rect, tmin, tmax, [](float t, void * model, void * context) { + ContinuousFunction * f = (ContinuousFunction *)model; + Poincare::Context * c = (Poincare::Context *)context; + return f->evaluateXYAtParameter(t, c); + }, f.operator->(), context(), f->color(), record == m_selectedRecord, m_highlightedStart, m_highlightedEnd); + /* Draw tangent */ + if (m_tangent && record == m_selectedRecord) { + float tangentParameter[2]; + tangentParameter[0] = f->approximateDerivative(m_curveViewCursor->x(), context()); + tangentParameter[1] = -tangentParameter[0]*m_curveViewCursor->x()+f->evaluateXYAtParameter(m_curveViewCursor->x(), context()).x2(); + drawCartesianCurve(ctx, rect, -INFINITY, INFINITY, [](float t, void * model, void * context) { + float * tangent = (float *)model; + return Poincare::Coordinate2D(t, tangent[0]*t+tangent[1]); + }, tangentParameter, nullptr, Palette::GreyVeryDark); + } + continue; } - /* Draw tangent */ - if (m_tangent && record == m_selectedRecord) { - float tangentParameter[2]; - tangentParameter[0] = f->approximateDerivative(m_curveViewCursor->x(), context()); - tangentParameter[1] = -tangentParameter[0]*m_curveViewCursor->x()+f->evaluateAtAbscissa(m_curveViewCursor->x(), context()); - drawCurve(ctx, rect, [](float t, void * model, void * context) { - float * tangent = (float *)model; - return tangent[0]*t+tangent[1]; - }, tangentParameter, nullptr, Palette::GreyVeryDark); - } + // Polar or parametric + assert( + type == Shared::ContinuousFunction::PlotType::Polar || + type == Shared::ContinuousFunction::PlotType::Parametric); + drawCurve(ctx, rect, tmin, tmax, tstep, [](float t, void * model, void * context) { + ContinuousFunction * f = (ContinuousFunction *)model; + Poincare::Context * c = (Poincare::Context *)context; + return f->evaluateXYAtParameter(t, c); + }, f.operator->(), context(), false, f->color()); } } diff --git a/apps/graph/graph/graph_view.h b/apps/graph/graph/graph_view.h index 294bb2b3f..c3e190a3a 100644 --- a/apps/graph/graph/graph_view.h +++ b/apps/graph/graph/graph_view.h @@ -2,15 +2,13 @@ #define GRAPH_GRAPH_VIEW_H #include "../../shared/function_graph_view.h" -#include "../cartesian_function_store.h" namespace Graph { class GraphView : public Shared::FunctionGraphView { public: - - GraphView(CartesianFunctionStore * functionStore, Shared::InteractiveCurveViewRange * graphRange, - Shared::CurveViewCursor * cursor, Shared::BannerView * bannerView, View * cursorView); + GraphView(Shared::InteractiveCurveViewRange * graphRange, + Shared::CurveViewCursor * cursor, Shared::BannerView * bannerView, Shared::CursorView * cursorView); void reload() override; void drawRect(KDContext * ctx, KDRect rect) const override; void drawTangent(bool tangent) { m_tangent = tangent; } @@ -20,7 +18,6 @@ public: * of the graph where the area under the curve is colored. */ void setAreaHighlightColor(bool highlightColor) override {}; private: - CartesianFunctionStore * m_functionStore; bool m_tangent; }; diff --git a/apps/graph/graph/integral_graph_controller.cpp b/apps/graph/graph/integral_graph_controller.cpp index 21eb36701..3c781c31d 100644 --- a/apps/graph/graph/integral_graph_controller.cpp +++ b/apps/graph/graph/integral_graph_controller.cpp @@ -40,7 +40,8 @@ Layout IntegralGraphController::createFunctionLayout(ExpiringPointernameWithArgument(buffer, bufferSize-strlen(dx), CartesianFunction::Symbol()); + int numberOfChars = function->nameWithArgument(buffer, bufferSize-strlen(dx)); + assert(numberOfChars <= bufferSize); strlcpy(buffer+numberOfChars, dx, bufferSize-numberOfChars); return LayoutHelper::String(buffer, strlen(buffer), KDFont::SmallFont); } diff --git a/apps/graph/graph/intersection_graph_controller.cpp b/apps/graph/graph/intersection_graph_controller.cpp index 074944f4a..dc520a3b5 100644 --- a/apps/graph/graph/intersection_graph_controller.cpp +++ b/apps/graph/graph/intersection_graph_controller.cpp @@ -1,6 +1,6 @@ #include "intersection_graph_controller.h" -#include "../app.h" #include "../../shared/poincare_helpers.h" +#include using namespace Shared; @@ -18,34 +18,38 @@ const char * IntersectionGraphController::title() { void IntersectionGraphController::reloadBannerView() { CalculationGraphController::reloadBannerView(); - constexpr size_t bufferSize = FunctionBannerDelegate::k_maxNumberOfCharacters+Poincare::PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits); + constexpr size_t bufferSize = FunctionBannerDelegate::k_maxNumberOfCharacters+Poincare::PrintFloat::charSizeForFloatsWithPrecision(Poincare::Preferences::LargeNumberOfSignificantDigits); char buffer[bufferSize]; const char * space = " "; const char * legend = "="; // 'f(x)=g(x)=', keep 2 chars for '=' - ExpiringPointer f = functionStore()->modelForRecord(m_record); - int numberOfChar = f->nameWithArgument(buffer, bufferSize-2, CartesianFunction::Symbol()); + ExpiringPointer f = functionStore()->modelForRecord(m_record); + int numberOfChar = f->nameWithArgument(buffer, bufferSize-2); + assert(numberOfChar <= bufferSize); numberOfChar += strlcpy(buffer+numberOfChar, legend, bufferSize-numberOfChar); // keep 1 char for '='; - ExpiringPointer g = functionStore()->modelForRecord(m_intersectedRecord); - numberOfChar += g->nameWithArgument(buffer+numberOfChar, bufferSize-numberOfChar-1, CartesianFunction::Symbol()); + ExpiringPointer g = functionStore()->modelForRecord(m_intersectedRecord); + numberOfChar += g->nameWithArgument(buffer+numberOfChar, bufferSize-numberOfChar-1); + assert(numberOfChar <= bufferSize); numberOfChar += strlcpy(buffer+numberOfChar, legend, bufferSize-numberOfChar); - numberOfChar += PoincareHelpers::ConvertFloatToText(m_cursor->y(), buffer+numberOfChar, bufferSize-numberOfChar, Constant::MediumNumberOfSignificantDigits); + numberOfChar += PoincareHelpers::ConvertFloatToText(m_cursor->y(), buffer+numberOfChar, bufferSize-numberOfChar, Poincare::Preferences::MediumNumberOfSignificantDigits); + assert(numberOfChar <= bufferSize); strlcpy(buffer+numberOfChar, space, bufferSize-numberOfChar); bannerView()->ordinateView()->setText(buffer); bannerView()->reload(); } -Poincare::Expression::Coordinate2D IntersectionGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) { - Poincare::Expression::Coordinate2D result = {.abscissa = NAN, .value = NAN}; +Poincare::Coordinate2D IntersectionGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) { + // TODO The following three lines should be factored. + Poincare::Coordinate2D result = Poincare::Coordinate2D(NAN, NAN); for (int i = 0; i < functionStore()->numberOfActiveFunctions(); i++) { Ion::Storage::Record record = functionStore()->activeRecordAtIndex(i); if (record != m_record) { - Poincare::Expression e = functionStore()->modelForRecord(record)->expressionReduced(context); - Poincare::Expression::Coordinate2D intersection = functionStore()->modelForRecord(m_record)->nextIntersectionFrom(start, step, max, context, e); - if ((std::isnan(result.abscissa) || std::fabs(intersection.abscissa-start) < std::fabs(result.abscissa-start)) && !std::isnan(intersection.abscissa)) { + ContinuousFunction f = *(functionStore()->modelForRecord(record)); + Poincare::Coordinate2D intersection = functionStore()->modelForRecord(m_record)->nextIntersectionFrom(start, step, max, context, f.expressionReduced(context), f.tMin(), f.tMax()); + if ((std::isnan(result.x1()) || std::fabs(intersection.x1()-start) < std::fabs(result.x1()-start)) && !std::isnan(intersection.x1())) { m_intersectedRecord = record; - result = (std::isnan(result.abscissa) || std::fabs(intersection.abscissa-start) < std::fabs(result.abscissa-start)) ? intersection : result; + result = (std::isnan(result.x1()) || std::fabs(intersection.x1()-start) < std::fabs(result.x1()-start)) ? intersection : result; } } } diff --git a/apps/graph/graph/intersection_graph_controller.h b/apps/graph/graph/intersection_graph_controller.h index b48c73459..dfd69232c 100644 --- a/apps/graph/graph/intersection_graph_controller.h +++ b/apps/graph/graph/intersection_graph_controller.h @@ -2,7 +2,6 @@ #define GRAPH_INTERSECTION_GRAPH_CONTROLLER_H #include "calculation_graph_controller.h" -#include "../cartesian_function_store.h" namespace Graph { @@ -12,7 +11,7 @@ public: const char * title() override; private: void reloadBannerView() override; - Poincare::Expression::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; + Poincare::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; Ion::Storage::Record m_intersectedRecord; }; diff --git a/apps/graph/graph/preimage_graph_controller.cpp b/apps/graph/graph/preimage_graph_controller.cpp index 818de7210..557986002 100644 --- a/apps/graph/graph/preimage_graph_controller.cpp +++ b/apps/graph/graph/preimage_graph_controller.cpp @@ -1,4 +1,6 @@ #include "preimage_graph_controller.h" +#include "../../shared/poincare_helpers.h" +#include namespace Graph { @@ -21,7 +23,7 @@ PreimageGraphController::PreimageGraphController( { } -Poincare::Expression::Coordinate2D PreimageGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) { +Poincare::Coordinate2D PreimageGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) { Poincare::Expression expression = Poincare::Float::Builder(m_image); return functionStore()->modelForRecord(m_record)->nextIntersectionFrom(start, step, max, context, expression); } diff --git a/apps/graph/graph/preimage_graph_controller.h b/apps/graph/graph/preimage_graph_controller.h index b9a553e6a..3de1246f0 100644 --- a/apps/graph/graph/preimage_graph_controller.h +++ b/apps/graph/graph/preimage_graph_controller.h @@ -16,7 +16,7 @@ public: double image() { return m_image; } void setImage(double value) { m_image = value; } private: - Poincare::Expression::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; + Poincare::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; double m_image; }; diff --git a/apps/graph/graph/preimage_parameter_controller.cpp b/apps/graph/graph/preimage_parameter_controller.cpp index 89797dd0b..1c1e519da 100644 --- a/apps/graph/graph/preimage_parameter_controller.cpp +++ b/apps/graph/graph/preimage_parameter_controller.cpp @@ -15,8 +15,7 @@ PreimageParameterController::PreimageParameterController( parentResponder, inputEventHandlerDelegate, graphRange, - cursor, - I18n::Message::Y + cursor ), m_preimageGraphController(preimageGraphController) { @@ -27,6 +26,7 @@ const char * PreimageParameterController::title() { } void PreimageParameterController::viewWillAppear() { + setParameterName(I18n::Message::Y); m_preimageGraphController->setImage(m_cursor->y()); Shared::GoToParameterController::viewWillAppear(); } diff --git a/apps/graph/graph/root_graph_controller.cpp b/apps/graph/graph/root_graph_controller.cpp index 2beebad63..413a5155c 100644 --- a/apps/graph/graph/root_graph_controller.cpp +++ b/apps/graph/graph/root_graph_controller.cpp @@ -1,5 +1,6 @@ #include "root_graph_controller.h" -#include "../app.h" +#include "../../shared/poincare_helpers.h" +#include using namespace Shared; using namespace Poincare; @@ -15,8 +16,8 @@ const char * RootGraphController::title() { return I18n::translate(I18n::Message::Zeros); } -Expression::Coordinate2D RootGraphController::computeNewPointOfInterest(double start, double step, double max, Context * context) { - return {.abscissa = functionStore()->modelForRecord(m_record)->nextRootFrom(start, step, max, context), .value = 0.0}; +Coordinate2D RootGraphController::computeNewPointOfInterest(double start, double step, double max, Context * context) { + return functionStore()->modelForRecord(m_record)->nextRootFrom(start, step, max, context); } } diff --git a/apps/graph/graph/root_graph_controller.h b/apps/graph/graph/root_graph_controller.h index 4db493145..9a952bc2b 100644 --- a/apps/graph/graph/root_graph_controller.h +++ b/apps/graph/graph/root_graph_controller.h @@ -10,7 +10,7 @@ public: RootGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor); const char * title() override; private: - Poincare::Expression::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; + Poincare::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; }; } diff --git a/apps/graph/graph/tangent_graph_controller.cpp b/apps/graph/graph/tangent_graph_controller.cpp index 5f11be357..de88c4fe3 100644 --- a/apps/graph/graph/tangent_graph_controller.cpp +++ b/apps/graph/graph/tangent_graph_controller.cpp @@ -1,6 +1,8 @@ #include "tangent_graph_controller.h" -#include "../../shared/poincare_helpers.h" #include "../app.h" +#include "../../apps_container.h" +#include "../../shared/poincare_helpers.h" +#include using namespace Shared; using namespace Poincare; @@ -34,19 +36,20 @@ void TangentGraphController::didBecomeFirstResponder() { if (curveView()->isMainViewSelected()) { m_bannerView->abscissaValue()->setParentResponder(this); m_bannerView->abscissaValue()->setDelegates(textFieldDelegateApp(), this); - app()->setFirstResponder(m_bannerView->abscissaValue()); + Container::activeApp()->setFirstResponder(m_bannerView->abscissaValue()); } } bool TangentGraphController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { + Shared::TextFieldDelegateApp * myApp = textFieldDelegateApp(); double floatBody; - if (textFieldDelegateApp()->hasUndefinedValue(text, floatBody)) { + if (myApp->hasUndefinedValue(text, floatBody)) { return false; } - App * myApp = static_cast(app()); - ExpiringPointer function = myApp->functionStore()->modelForRecord(m_record); - double y = function->evaluateAtAbscissa(floatBody, textFieldDelegateApp()->localContext()); - m_cursor->moveTo(floatBody, y); + ExpiringPointer function = App::app()->functionStore()->modelForRecord(m_record); + assert(function->plotType() == Shared::ContinuousFunction::PlotType::Cartesian); + double y = function->evaluate2DAtParameter(floatBody, myApp->localContext()).x2(); + m_cursor->moveTo(floatBody, floatBody, y); interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); reloadBannerView(); curveView()->reload(); @@ -62,29 +65,32 @@ void TangentGraphController::reloadBannerView() { if (m_record.isNull()) { return; } - App * myApp = static_cast(app()); - FunctionBannerDelegate::reloadBannerViewForCursorOnFunction(m_cursor, m_record, myApp->functionStore(), CartesianFunction::Symbol()); - GraphControllerHelper::reloadDerivativeInBannerViewForCursorOnFunction(m_cursor, m_record, myApp); - constexpr size_t bufferSize = FunctionBannerDelegate::k_maxNumberOfCharacters+PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits); + FunctionBannerDelegate::reloadBannerViewForCursorOnFunction(m_cursor, m_record, Shared::FunctionApp::app()->functionStore(), AppsContainer::sharedAppsContainer()->globalContext()); + GraphControllerHelper::reloadDerivativeInBannerViewForCursorOnFunction(m_cursor, m_record); + constexpr size_t bufferSize = FunctionBannerDelegate::k_maxNumberOfCharacters + PrintFloat::charSizeForFloatsWithPrecision(Preferences::LargeNumberOfSignificantDigits); char buffer[bufferSize]; + Poincare::Context * context = textFieldDelegateApp()->localContext(); + + constexpr int precision = Preferences::MediumNumberOfSignificantDigits; const char * legend = "a="; int legendLength = strlcpy(buffer, legend, bufferSize); - ExpiringPointer function = myApp->functionStore()->modelForRecord(m_record); - double y = function->approximateDerivative(m_cursor->x(), myApp->localContext()); - PoincareHelpers::ConvertFloatToText(y, buffer + legendLength, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits); + ExpiringPointer function = App::app()->functionStore()->modelForRecord(m_record); + double y = function->approximateDerivative(m_cursor->x(), context); + PoincareHelpers::ConvertFloatToText(y, buffer + legendLength, bufferSize - legendLength, precision); m_bannerView->aView()->setText(buffer); legend = "b="; legendLength = strlcpy(buffer, legend, bufferSize); - y = -y*m_cursor->x()+function->evaluateAtAbscissa(m_cursor->x(), myApp->localContext()); - PoincareHelpers::ConvertFloatToText(y, buffer + legendLength, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits); + Shared::TextFieldDelegateApp * myApp = textFieldDelegateApp(); + assert(function->plotType() == Shared::ContinuousFunction::PlotType::Cartesian); + y = -y*m_cursor->x()+function->evaluate2DAtParameter(m_cursor->x(), myApp->localContext()).x2(); + PoincareHelpers::ConvertFloatToText(y, buffer + legendLength, bufferSize - legendLength, precision); m_bannerView->bView()->setText(buffer); m_bannerView->reload(); } bool TangentGraphController::moveCursorHorizontally(int direction) { - App * myApp = static_cast(app()); - return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, m_record, myApp); + return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, m_record); } bool TangentGraphController::handleEnter() { diff --git a/apps/graph/graph/tangent_graph_controller.h b/apps/graph/graph/tangent_graph_controller.h index e09fbacb0..a1652e0de 100644 --- a/apps/graph/graph/tangent_graph_controller.h +++ b/apps/graph/graph/tangent_graph_controller.h @@ -6,7 +6,6 @@ #include "graph_controller_helper.h" #include "../../shared/simple_interactive_curve_view_controller.h" #include "../../shared/function_banner_delegate.h" -#include "../cartesian_function_store.h" namespace Graph { diff --git a/apps/graph/list/domain_parameter_controller.cpp b/apps/graph/list/domain_parameter_controller.cpp new file mode 100644 index 000000000..326a7eb85 --- /dev/null +++ b/apps/graph/list/domain_parameter_controller.cpp @@ -0,0 +1,106 @@ +#include "domain_parameter_controller.h" +#include +#include "../app.h" +#include + +using namespace Shared; + +namespace Graph { + +DomainParameterController::DomainParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) : + FloatParameterController(parentResponder), + m_domainCells{}, + m_record() +{ + for (int i = 0; i < k_totalNumberOfCell; i++) { + m_domainCells[i].setParentResponder(&m_selectableTableView); + m_domainCells[i].textField()->setDelegates(inputEventHandlerDelegate, this); + } +} + +const char * DomainParameterController::title() { + return I18n::translate(I18n::Message::FunctionDomain); +} + +int DomainParameterController::numberOfRows() const { + return k_totalNumberOfCell+1; +} + +void DomainParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { + if (index == numberOfRows()-1) { + return; + } + MessageTableCellWithEditableText * myCell = (MessageTableCellWithEditableText *)cell; + Shared::ContinuousFunction::PlotType plotType = function()->plotType(); + switch (plotType) { + case Shared::ContinuousFunction::PlotType::Cartesian: + { + I18n::Message labels[k_totalNumberOfCell] = {I18n::Message::XMin, I18n::Message::XMax}; + myCell->setMessage(labels[index]); + break; + } + case Shared::ContinuousFunction::PlotType::Parametric: + { + I18n::Message labels[k_totalNumberOfCell] = {I18n::Message::TMin, I18n::Message::TMax}; + myCell->setMessage(labels[index]); + break; + } + default: + { + assert(plotType == Shared::ContinuousFunction::PlotType::Polar); + I18n::Message labels[k_totalNumberOfCell] = {I18n::Message::ThetaMin, I18n::Message::ThetaMax}; + myCell->setMessage(labels[index]); + break; + } + } + FloatParameterController::willDisplayCellForIndex(cell, index); +} + +int DomainParameterController::reusableParameterCellCount(int type) { + return k_totalNumberOfCell; +} + +HighlightCell * DomainParameterController::reusableParameterCell(int index, int type) { + assert(index >= 0 && index < k_totalNumberOfCell); + return &m_domainCells[index]; +} + +bool DomainParameterController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::Left && stackController()->depth() > 2) { + stackController()->pop(); + return true; + } + return false; +} + +float DomainParameterController::parameterAtIndex(int index) { + return index == 0 ? function()->tMin() : function()->tMax(); +} + +bool DomainParameterController::setParameterAtIndex(int parameterIndex, float f) { + // TODO: what to do if the xmin > xmax? + parameterIndex == 0 ? function()->setTMin(f) : function()->setTMax(f); + return true; +} + +void DomainParameterController::buttonAction() { + StackViewController * stack = stackController(); + stack->pop(); + stack->pop(); +} + +Shared::ExpiringPointer DomainParameterController::function() const { + assert(!m_record.isNull()); + App * myApp = App::app(); + return myApp->functionStore()->modelForRecord(m_record); +} + +FloatParameterController::InfinityTolerance DomainParameterController::infinityAllowanceForRow(int row) const { + Shared::ContinuousFunction::PlotType plotType = function()->plotType(); + if (plotType == Shared::ContinuousFunction::PlotType::Cartesian) { + return row == 0 ? FloatParameterController::InfinityTolerance::MinusInfinity : FloatParameterController::InfinityTolerance::PlusInfinity; + } + return FloatParameterController::InfinityTolerance::None; +} + +} diff --git a/apps/graph/list/domain_parameter_controller.h b/apps/graph/list/domain_parameter_controller.h new file mode 100644 index 000000000..a2d6852fc --- /dev/null +++ b/apps/graph/list/domain_parameter_controller.h @@ -0,0 +1,40 @@ +#ifndef GRAPH_LIST_DOMAIN_PARAMATER_CONTROLLER_H +#define GRAPH_LIST_DOMAIN_PARAMATER_CONTROLLER_H + +#include +#include +#include "../../shared/continuous_function.h" +#include "../../shared/expiring_pointer.h" +#include "../../shared/float_parameter_controller.h" + +namespace Graph { + +class DomainParameterController : public Shared::FloatParameterController { +public: + DomainParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate); + + // ViewController + const char * title() override; + + // ListViewDataSource + int numberOfRows() const override; + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + + void setRecord(Ion::Storage::Record record) { m_record = record; } +private: + constexpr static int k_totalNumberOfCell = 2; + int reusableParameterCellCount(int type) override; + HighlightCell * reusableParameterCell(int index, int type) override; + bool handleEvent(Ion::Events::Event event) override; + bool setParameterAtIndex(int parameterIndex, float f) override; + float parameterAtIndex(int index) override; + void buttonAction() override; + InfinityTolerance infinityAllowanceForRow(int row) const override; + Shared::ExpiringPointer function() const; + MessageTableCellWithEditableText m_domainCells[k_totalNumberOfCell]; + Ion::Storage::Record m_record; +}; + +} + +#endif diff --git a/apps/graph/list/list_controller.cpp b/apps/graph/list/list_controller.cpp index df1bfa9c1..7d52044c0 100644 --- a/apps/graph/list/list_controller.cpp +++ b/apps/graph/list/list_controller.cpp @@ -9,7 +9,7 @@ using namespace Shared; namespace Graph { -ListController::ListController(Responder * parentResponder, ButtonRowController * header, ButtonRowController * footer) : +ListController::ListController(Responder * parentResponder, ButtonRowController * header, ButtonRowController * footer, InputEventHandlerDelegate * inputEventHandlerDelegate) : Shared::FunctionListController(parentResponder, header, footer, I18n::Message::AddFunction), m_functionTitleCells{ //TODO find better initialization TextFieldFunctionTitleCell(this), @@ -19,7 +19,7 @@ ListController::ListController(Responder * parentResponder, ButtonRowController TextFieldFunctionTitleCell(this), }, m_expressionCells{}, - m_parameterController(this, this, I18n::Message::FunctionColor, I18n::Message::DeleteFunction) + m_parameterController(this, this, I18n::Message::FunctionColor, I18n::Message::DeleteFunction, inputEventHandlerDelegate) { for (int i = 0; i < k_maxNumberOfDisplayableRows; i++) { m_expressionCells[i].setLeftMargin(k_expressionMargin); @@ -38,10 +38,10 @@ void ListController::renameSelectedFunction() { computeTitlesColumnWidth(true); selectableTableView()->reloadData(); - static_cast(const_cast(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::AlphaLock); + AppsContainer::sharedAppsContainer()->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::AlphaLock); TextFieldFunctionTitleCell * selectedTitleCell = (TextFieldFunctionTitleCell *)(selectableTableView()->selectedCell()); selectedTitleCell->setHorizontalAlignment(1.0f); - app()->setFirstResponder(selectedTitleCell); + Container::activeApp()->setFirstResponder(selectedTitleCell); selectedTitleCell->setEditing(true); } @@ -49,17 +49,17 @@ bool ListController::textFieldDidFinishEditing(TextField * textField, const char assert(textField != nullptr); // Compute the new name size_t textLength = strlen(text); - size_t argumentLength = Function::k_parenthesedArgumentLength; + size_t argumentLength = UTF8Helper::HasCodePoint(text, UCodePointGreekSmallLetterTheta) ? Function::k_parenthesedThetaArgumentByteLength : Function::k_parenthesedXNTArgumentByteLength; constexpr int maxBaseNameSize = Function::k_maxNameWithArgumentSize; char baseName[maxBaseNameSize]; if (textLength <= argumentLength) { // The user entered an empty name. Use a default function name. - CartesianFunction::DefaultName(baseName, maxBaseNameSize); - size_t defaultNameLength = strlen(baseName); - strlcpy(baseName + defaultNameLength, Function::k_parenthesedArgument, maxBaseNameSize - defaultNameLength); - textField->setText(baseName); - baseName[defaultNameLength] = 0; + ContinuousFunction::DefaultName(baseName, maxBaseNameSize); + /* We don't need to update the textfield edited text here because we are + * sure that the default name is compliant. It will thus lead to the end of + * edition and its content will be reloaded by willDisplayTitleCellAtIndex. */ } else { + assert(argumentLength <= textLength + 1); strlcpy(baseName, text, textLength - argumentLength + 1); } @@ -73,7 +73,7 @@ bool ListController::textFieldDidFinishEditing(TextField * textField, const char // Handle any error if (error == Ion::Storage::Record::ErrorStatus::None) { bool selectTab = false; - textField->setEditing(false, false); + textField->setEditing(false); computeTitlesColumnWidth(); int currentRow = m_selectableTableView.selectedRow(); if (event == Ion::Events::Down && currentRow < numberOfRows() - 1) { @@ -87,29 +87,29 @@ bool ListController::textFieldDidFinishEditing(TextField * textField, const char } m_selectableTableView.selectedCell()->setHighlighted(true); m_selectableTableView.reloadData(); - app()->setFirstResponder(&m_selectableTableView); + Container::activeApp()->setFirstResponder(&m_selectableTableView); if (selectTab) { m_selectableTableView.parentResponder()->handleEvent(event); } - static_cast(const_cast(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default); + AppsContainer::sharedAppsContainer()->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default); return true; } else if (error == Ion::Storage::Record::ErrorStatus::NameTaken) { - app()->displayWarning(I18n::Message::NameTaken); + Container::activeApp()->displayWarning(I18n::Message::NameTaken); } else if (error == Ion::Storage::Record::ErrorStatus::NonCompliantName) { assert(nameError != Function::NameNotCompliantError::None); if (nameError == Function::NameNotCompliantError::CharacterNotAllowed) { - app()->displayWarning(I18n::Message::AllowedCharactersAZaz09); + Container::activeApp()->displayWarning(I18n::Message::AllowedCharactersAZaz09); } else if (nameError == Function::NameNotCompliantError::NameCannotStartWithNumber) { - app()->displayWarning(I18n::Message::NameCannotStartWithNumber); + Container::activeApp()->displayWarning(I18n::Message::NameCannotStartWithNumber); } else { assert(nameError == Function::NameNotCompliantError::ReservedName); - app()->displayWarning(I18n::Message::ReservedName); + Container::activeApp()->displayWarning(I18n::Message::ReservedName); } } else { assert(error == Ion::Storage::Record::ErrorStatus::NotEnoughSpaceAvailable); - app()->displayWarning(I18n::Message::NameTooLong); + Container::activeApp()->displayWarning(I18n::Message::NameTooLong); } - textField->setEditing(true, false); + textField->setEditing(true); return false; } @@ -118,11 +118,11 @@ bool ListController::textFieldDidAbortEditing(TextField * textField) { // Put the name column back to normal size computeTitlesColumnWidth(); selectableTableView()->reloadData(); - ExpiringPointer function = modelStore()->modelForRecord(modelStore()->recordAtIndex(selectedRow())); + ExpiringPointer function = modelStore()->modelForRecord(modelStore()->recordAtIndex(selectedRow())); setFunctionNameInTextField(function, textField); m_selectableTableView.selectedCell()->setHighlighted(true); - app()->setFirstResponder(&m_selectableTableView); - static_cast(const_cast(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default); + Container::activeApp()->setFirstResponder(&m_selectableTableView); + AppsContainer::sharedAppsContainer()->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default); return true; } @@ -165,7 +165,7 @@ void ListController::willDisplayTitleCellAtIndex(HighlightCell * cell, int j) { titleCell->setBaseline(baseline(j)); if (!titleCell->isEditing()) { // Set name and color if the name is not being edited - ExpiringPointer function = modelStore()->modelForRecord(modelStore()->recordAtIndex(j)); + ExpiringPointer function = modelStore()->modelForRecord(modelStore()->recordAtIndex(j)); setFunctionNameInTextField(function, titleCell->textField()); KDColor functionNameColor = function->isActive() ? function->color() : Palette::GreyDark; titleCell->setColor(functionNameColor); @@ -177,16 +177,20 @@ void ListController::willDisplayExpressionCellAtIndex(HighlightCell * cell, int assert(j >= 0 && j < modelStore()->numberOfModels()); Shared::FunctionListController::willDisplayExpressionCellAtIndex(cell, j); FunctionExpressionCell * myCell = (FunctionExpressionCell *)cell; - ExpiringPointer f = modelStore()->modelForRecord(modelStore()->recordAtIndex(j)); + ExpiringPointer f = modelStore()->modelForRecord(modelStore()->recordAtIndex(j)); KDColor textColor = f->isActive() ? KDColorBlack : Palette::GreyDark; myCell->setTextColor(textColor); } -void ListController::setFunctionNameInTextField(ExpiringPointer function, TextField * textField) { +void ListController::setFunctionNameInTextField(ExpiringPointer function, TextField * textField) { assert(textField != nullptr); char bufferName[BufferTextView::k_maxNumberOfChar]; - function->nameWithArgument(bufferName, BufferTextView::k_maxNumberOfChar, modelStore()->symbol()); + function->nameWithArgument(bufferName, BufferTextView::k_maxNumberOfChar); textField->setText(bufferName); } +ContinuousFunctionStore * ListController::modelStore() { + return App::app()->functionStore(); +} + } diff --git a/apps/graph/list/list_controller.h b/apps/graph/list/list_controller.h index 2841df254..12deaf33b 100644 --- a/apps/graph/list/list_controller.h +++ b/apps/graph/list/list_controller.h @@ -4,7 +4,7 @@ #include #include "list_parameter_controller.h" #include "text_field_function_title_cell.h" -#include "../cartesian_function_store.h" +#include "../continuous_function_store.h" #include #include #include @@ -13,7 +13,7 @@ namespace Graph { class ListController : public Shared::FunctionListController, public Shared::TextFieldDelegate { public: - ListController(Responder * parentResponder, ButtonRowController * header, ButtonRowController * footer); + ListController(Responder * parentResponder, ButtonRowController * header, ButtonRowController * footer, InputEventHandlerDelegate * inputEventHandlerDelegate); const char * title() override; void renameSelectedFunction(); // TextFieldDelegate @@ -29,10 +29,8 @@ private: HighlightCell * expressionCells(int index) override; void willDisplayTitleCellAtIndex(HighlightCell * cell, int j) override; void willDisplayExpressionCellAtIndex(HighlightCell * cell, int j) override; - Shared::TextFieldDelegateApp * textFieldDelegateApp() override { - return static_cast(app()); - } - void setFunctionNameInTextField(Shared::ExpiringPointer function, TextField * textField); + void setFunctionNameInTextField(Shared::ExpiringPointer function, TextField * textField); + ContinuousFunctionStore * modelStore() override; TextFieldFunctionTitleCell m_functionTitleCells[k_maxNumberOfDisplayableRows]; Shared::FunctionExpressionCell m_expressionCells[k_maxNumberOfDisplayableRows]; ListParameterController m_parameterController; diff --git a/apps/graph/list/list_parameter_controller.cpp b/apps/graph/list/list_parameter_controller.cpp index 411fcff3d..331808e24 100644 --- a/apps/graph/list/list_parameter_controller.cpp +++ b/apps/graph/list/list_parameter_controller.cpp @@ -1,24 +1,120 @@ #include "list_parameter_controller.h" #include "list_controller.h" +#include "type_helper.h" +#include "../../shared/poincare_helpers.h" +#include "../app.h" +#include #include using namespace Shared; +using namespace Poincare; namespace Graph { +ListParameterController::ListParameterController(ListController * listController, Responder * parentResponder, I18n::Message functionColorMessage, I18n::Message deleteFunctionMessage, InputEventHandlerDelegate * inputEventHandlerDelegate) : + Shared::ListParameterController(parentResponder, functionColorMessage, deleteFunctionMessage), + m_listController(listController), + m_typeCell(), + m_typeParameterController(this), + m_domainParameterController(nullptr, inputEventHandlerDelegate), + m_renameCell(I18n::Message::Rename) +{ + m_selectableTableView.setMargins(Metric::CommonTopMargin, Metric::CommonTopMargin, Metric::CommonBottomMargin, Metric::CommonTopMargin); // Reduce the margins to make te text fit +} + HighlightCell * ListParameterController::reusableCell(int index, int type) { - if (type == 0) { + switch (type) { + case 0: + return &m_typeCell; + case 1: + return &m_functionDomain; + case 2: return &m_renameCell; + default: + return Shared::ListParameterController::reusableCell(index, type - 3); + } +} + +bool ListParameterController::handleEvent(Ion::Events::Event event) { + if (Shared::ListParameterController::handleEvent(event)) { + return true; + } + if (event == Ion::Events::Right) { + int selectedR = selectedRow(); + if (selectedR == 0 || selectedR == 1) { + // Go in the submenu + return handleEnterOnRow(selectedR); + } + } + return false; +} + +char intervalBracket(double value, bool opening) { + return std::isinf(value) == opening ? ']' : '['; +} + +int writeInterval(char * buffer, int bufferSize, double min, double max, int numberOfSignificantDigits, Preferences::PrintFloatMode mode) { + int numberOfChar = 0; + assert(bufferSize-1 > numberOfChar); + buffer[numberOfChar++] = intervalBracket(min, true); + int glyphLengthRequiredForFloat = PrintFloat::glyphLengthForFloatWithPrecision(numberOfSignificantDigits); + PrintFloat::TextLengths minLengths = PrintFloat::ConvertFloatToText(min, buffer+numberOfChar, bufferSize - numberOfChar, glyphLengthRequiredForFloat, numberOfSignificantDigits, mode); + numberOfChar += minLengths.CharLength; + assert(bufferSize > numberOfChar); + numberOfChar += strlcpy(buffer+numberOfChar, ",", bufferSize-numberOfChar); + PrintFloat::TextLengths maxLengths = PrintFloat::ConvertFloatToText(max, buffer+numberOfChar, bufferSize - numberOfChar, glyphLengthRequiredForFloat, numberOfSignificantDigits, mode); + numberOfChar += maxLengths.CharLength; + assert(bufferSize-1 > numberOfChar); + buffer[numberOfChar++] = intervalBracket(max, false); + assert(bufferSize > numberOfChar); + strlcpy(buffer+numberOfChar, " ", bufferSize-numberOfChar); + return minLengths.GlyphLength + maxLengths. GlyphLength + 3 + 1; // Count "[,] " glyphs +} + +void ListParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { + Shared::ListParameterController::willDisplayCellForIndex(cell, index); + if ((cell == &m_typeCell || cell == &m_functionDomain) && !m_record.isNull()) { + App * myApp = App::app(); + assert(!m_record.isNull()); + Shared::ExpiringPointer function = myApp->functionStore()->modelForRecord(m_record); + if (cell == &m_typeCell) { + m_typeCell.setMessage(I18n::Message::CurveType); + int row = static_cast(function->plotType()); + m_typeCell.setSubtitle(PlotTypeHelper::Message(row)); + } else { + assert(cell == &m_functionDomain); + m_functionDomain.setMessage(I18n::Message::FunctionDomain); + double min = function->tMin(); + double max = function->tMax(); + constexpr int bufferSize = BufferTextView::k_maxNumberOfChar; + char buffer[bufferSize]; + int glyphLength = writeInterval(buffer, bufferSize, min, max, Preferences::VeryShortNumberOfSignificantDigits, Preferences::sharedPreferences()->displayMode()); + int numberOfAvailableGlyphs = (m_functionDomain.bounds().width() - m_functionDomain.labelView()->bounds().width() - m_functionDomain.accessoryView()->bounds().width() - TableCell::k_labelMargin - TableCell::k_accessoryMargin)/KDFont::SmallFont->glyphSize().width(); + if (glyphLength > numberOfAvailableGlyphs) { + writeInterval(buffer, bufferSize, min, max, Preferences::VeryShortNumberOfSignificantDigits-1, Preferences::PrintFloatMode::Scientific); + } + m_functionDomain.setAccessoryText(buffer); + } } - return Shared::ListParameterController::reusableCell(index, type - 1); } bool ListParameterController::handleEnterOnRow(int rowIndex) { - if (rowIndex == 0) { + StackViewController * stack = (StackViewController *)(parentResponder()); + switch (rowIndex) { + case 0: + m_typeParameterController.setRecord(m_record); + stack->push(&m_typeParameterController); + return true; + case 1: + m_domainParameterController.setRecord(m_record); + stack->push(&m_domainParameterController); + return true; + case 2: renameFunction(); return true; + default: + return Shared::ListParameterController::handleEnterOnRow(rowIndex - 3); } - return Shared::ListParameterController::handleEnterOnRow(rowIndex-1); } void ListParameterController::renameFunction() { diff --git a/apps/graph/list/list_parameter_controller.h b/apps/graph/list/list_parameter_controller.h index 21d41e376..31c311ba4 100644 --- a/apps/graph/list/list_parameter_controller.h +++ b/apps/graph/list/list_parameter_controller.h @@ -2,6 +2,8 @@ #define GRAPH_LIST_LIST_PARAM_CONTROLLER_H #include +#include "type_parameter_controller.h" +#include "domain_parameter_controller.h" namespace Graph { @@ -9,21 +11,23 @@ class ListController; class ListParameterController : public Shared::ListParameterController { public: - ListParameterController(ListController * listController, Responder * parentResponder, I18n::Message functionColorMessage, I18n::Message deleteFunctionMessage, SelectableTableViewDelegate * tableDelegate = nullptr) : - Shared::ListParameterController(parentResponder, functionColorMessage, deleteFunctionMessage, tableDelegate), - m_listController(listController), - m_renameCell(I18n::Message::Rename) - {} + ListParameterController(ListController * listController, Responder * parentResponder, I18n::Message functionColorMessage, I18n::Message deleteFunctionMessage, InputEventHandlerDelegate * inputEventHandlerDelegate); + bool handleEvent(Ion::Events::Event event) override; // ListViewDataSource HighlightCell * reusableCell(int index, int type) override; + void willDisplayCellForIndex(HighlightCell * cell, int index) override; protected: bool handleEnterOnRow(int rowIndex) override; private: int totalNumberOfCells() const override { - return Shared::ListParameterController::totalNumberOfCells() + 1; + return Shared::ListParameterController::totalNumberOfCells() + 2; } void renameFunction(); ListController * m_listController; + MessageTableCellWithChevronAndMessage m_typeCell; + MessageTableCellWithChevronAndBuffer m_functionDomain; + TypeParameterController m_typeParameterController; + DomainParameterController m_domainParameterController; MessageTableCell m_renameCell; }; diff --git a/apps/graph/list/text_field_function_title_cell.cpp b/apps/graph/list/text_field_function_title_cell.cpp index 56057d888..ecd7a7567 100644 --- a/apps/graph/list/text_field_function_title_cell.cpp +++ b/apps/graph/list/text_field_function_title_cell.cpp @@ -10,7 +10,7 @@ static inline float maxFloat(float x, float y) { return x > y ? x : y; } TextFieldFunctionTitleCell::TextFieldFunctionTitleCell(ListController * listController, Orientation orientation, const KDFont * font) : Shared::FunctionTitleCell(orientation), Responder(listController), - m_textField(Shared::Function::k_parenthesedArgumentLength, this, m_textFieldBuffer, m_textFieldBuffer, k_textFieldBufferSize, nullptr, listController, false, font, 1.0f, 0.5f) + m_textField(Shared::Function::k_parenthesedThetaArgumentByteLength, this, m_textFieldBuffer, k_textFieldBufferSize, k_textFieldBufferSize, nullptr, listController, font, 1.0f, 0.5f) { } @@ -24,10 +24,13 @@ Responder * TextFieldFunctionTitleCell::responder() { } void TextFieldFunctionTitleCell::setEditing(bool editing) { - app()->setFirstResponder(&m_textField); + Container::activeApp()->setFirstResponder(&m_textField); const char * previousText = m_textField.text(); - m_textField.setEditing(true, false); + int extensionLength = UTF8Helper::HasCodePoint(previousText, UCodePointGreekSmallLetterTheta) ? Shared::Function::k_parenthesedThetaArgumentByteLength : Shared::Function::k_parenthesedXNTArgumentByteLength; + m_textField.setExtensionLength(extensionLength); + m_textField.setEditing(true); m_textField.setText(previousText); + m_textField.setDraftTextBufferSize(Poincare::SymbolAbstract::k_maxNameSize+extensionLength); } bool TextFieldFunctionTitleCell::isEditing() const { @@ -44,10 +47,6 @@ void TextFieldFunctionTitleCell::setColor(KDColor color) { m_textField.setTextColor(color); } -void TextFieldFunctionTitleCell::setText(const char * title) { - m_textField.setText(title); -} - void TextFieldFunctionTitleCell::setHorizontalAlignment(float alignment) { assert(alignment >= 0.0f && alignment <= 1.0f); m_textField.setAlignment(alignment, verticalAlignment()); @@ -67,7 +66,7 @@ void TextFieldFunctionTitleCell::layoutSubviews() { void TextFieldFunctionTitleCell::didBecomeFirstResponder() { if (isEditing()) { - app()->setFirstResponder(&m_textField); + Container::activeApp()->setFirstResponder(&m_textField); } } diff --git a/apps/graph/list/text_field_function_title_cell.h b/apps/graph/list/text_field_function_title_cell.h index 68b1cbf25..0d2b95d9b 100644 --- a/apps/graph/list/text_field_function_title_cell.h +++ b/apps/graph/list/text_field_function_title_cell.h @@ -3,7 +3,7 @@ #include #include -#include +#include "text_field_with_max_length_and_extension.h" namespace Graph { @@ -15,7 +15,6 @@ public: TextField * textField() { return &m_textField; } void setEditing(bool editing); bool isEditing() const; - void setText(const char * textContent); void setHorizontalAlignment(float alignment); // FunctionTitleCell @@ -43,7 +42,7 @@ private: constexpr static KDCoordinate k_textFieldRightMargin = 4; constexpr static int k_textFieldBufferSize = Shared::Function::k_maxNameWithArgumentSize; float verticalAlignmentGivenExpressionBaselineAndRowHeight(KDCoordinate expressionBaseline, KDCoordinate rowHeight) const override; - Shared::TextFieldWithExtension m_textField; + TextFieldWithMaxLengthAndExtension m_textField; char m_textFieldBuffer[k_textFieldBufferSize]; }; diff --git a/apps/graph/list/text_field_with_max_length_and_extension.h b/apps/graph/list/text_field_with_max_length_and_extension.h new file mode 100644 index 000000000..b14740788 --- /dev/null +++ b/apps/graph/list/text_field_with_max_length_and_extension.h @@ -0,0 +1,28 @@ +#ifndef GRAPH_TEXT_FIELD_WITH_MAX_LENGTH_AND_EXTENSION_H +#define GRAPH_TEXT_FIELD_WITH_MAX_LENGTH_AND_EXTENSION_H + +#include + +namespace Graph { + +class TextFieldWithMaxLengthAndExtension : public Shared::TextFieldWithExtension { +public: + TextFieldWithMaxLengthAndExtension(size_t extensionLength, + Responder * parentResponder, + char * textBuffer, + size_t textBufferSize, + size_t draftTextBufferSize, + ::InputEventHandlerDelegate * inputEventHandlerDelegate, + ::TextFieldDelegate * delegate = nullptr, + const KDFont * size = KDFont::LargeFont, + float horizontalAlignment = 0.0f, + float verticalAlignment = 0.5f, + KDColor textColor = KDColorBlack, + KDColor backgroundColor = KDColorWhite) : + TextFieldWithExtension(extensionLength, parentResponder, textBuffer, textBufferSize, draftTextBufferSize, inputEventHandlerDelegate, delegate, size, horizontalAlignment, verticalAlignment, textColor, backgroundColor) {} + void setDraftTextBufferSize(size_t size) { m_contentView.setDraftTextBufferSize(size); } +}; + +} + +#endif diff --git a/apps/graph/list/type_helper.cpp b/apps/graph/list/type_helper.cpp new file mode 100644 index 000000000..ee1a1d871 --- /dev/null +++ b/apps/graph/list/type_helper.cpp @@ -0,0 +1,37 @@ +#include "type_helper.h" +#include +#include +#include + +namespace Graph { + +namespace PlotTypeHelper { + +I18n::Message Message(int index) { + assert(0 <= index && index < NumberOfTypes); + static constexpr I18n::Message message[NumberOfTypes] = { + I18n::Message::CartesianType, + I18n::Message::PolarType, + I18n::Message::ParametricType + }; + return message[index]; +} + +Poincare::Layout Layout(int index) { + assert(0 <= index && index < NumberOfTypes); + static constexpr const char * texts[NumberOfTypes] = { + "y=f(x)", + "r=f(θ)", + "[[x][y]]=f(t)" + }; + const char * text = texts[index]; + if (index < 2) { + return Poincare::LayoutHelper::String(text, strlen(text)); + } + Poincare::Expression parametric = Poincare::Expression::Parse(text); + return parametric.createLayout(Poincare::Preferences::PrintFloatMode::Decimal, 1); +} + +} + +} diff --git a/apps/graph/list/type_helper.h b/apps/graph/list/type_helper.h new file mode 100644 index 000000000..0d5aef810 --- /dev/null +++ b/apps/graph/list/type_helper.h @@ -0,0 +1,19 @@ +#ifndef GRAPH_LIST_TYPE_HELPER_H +#define GRAPH_LIST_TYPE_HELPER_H + +#include +#include + +namespace Graph { + +namespace PlotTypeHelper { + +constexpr static int NumberOfTypes = 3; +I18n::Message Message(int index); +Poincare::Layout Layout(int index); + +} + +} + +#endif diff --git a/apps/graph/list/type_parameter_controller.cpp b/apps/graph/list/type_parameter_controller.cpp new file mode 100644 index 000000000..93f4e6143 --- /dev/null +++ b/apps/graph/list/type_parameter_controller.cpp @@ -0,0 +1,73 @@ +#include "type_parameter_controller.h" +#include "type_helper.h" +#include +#include "../app.h" +#include + +namespace Graph { + +TypeParameterController::TypeParameterController(Responder * parentResponder) : + ViewController(parentResponder), + m_selectableTableView(this, this, this, nullptr), + m_record() +{ +} + +void TypeParameterController::didBecomeFirstResponder() { + Container::activeApp()->setFirstResponder(&m_selectableTableView); +} + +bool TypeParameterController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::OK || event == Ion::Events::EXE) { + assert(!m_record.isNull()); + Shared::ContinuousFunction::PlotType plotType = static_cast(selectedRow()); + App * myApp = App::app(); + assert(!m_record.isNull()); + Shared::ExpiringPointer function = myApp->functionStore()->modelForRecord(m_record); + function->setPlotType(plotType, Poincare::Preferences::sharedPreferences()->angleUnit()); + StackViewController * stack = stackController(); + stack->pop(); + stack->pop(); + return true; + } + if (event == Ion::Events::Left && !m_record.isNull()) { + stackController()->pop(); + return true; + } + return false; +} + +const char * TypeParameterController::title() { + return I18n::translate(I18n::Message::CurveType); +} + +void TypeParameterController::viewWillAppear() { + App * myApp = App::app(); + assert(!m_record.isNull()); + Shared::ExpiringPointer function = myApp->functionStore()->modelForRecord(m_record); + int row = static_cast(function->plotType()); + selectCellAtLocation(0, row); + m_selectableTableView.reloadData(); +} + +KDCoordinate TypeParameterController::rowHeight(int j) { + return PlotTypeHelper::Layout(j).layoutSize().height() + 14; +} + +void TypeParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { + assert(0 <= index && index < k_numberOfTypes); + MessageTableCellWithExpression * myCell = static_cast(cell); + myCell->setMessage(PlotTypeHelper::Message(index)); + myCell->setLayout(PlotTypeHelper::Layout(index)); +} + +MessageTableCellWithExpression * TypeParameterController::reusableCell(int index, int type) { + assert(0 <= index && index < reusableCellCount(type)); + return &m_cells[index]; +} + +StackViewController * TypeParameterController::stackController() const { + return static_cast(parentResponder()); +} + +} diff --git a/apps/graph/list/type_parameter_controller.h b/apps/graph/list/type_parameter_controller.h new file mode 100644 index 000000000..be157a1a9 --- /dev/null +++ b/apps/graph/list/type_parameter_controller.h @@ -0,0 +1,44 @@ +#ifndef GRAPH_LIST_TYPE_PARAMATER_CONTROLLER_H +#define GRAPH_LIST_TYPE_PARAMATER_CONTROLLER_H + +#include +#include +#include +#include +#include +#include + +namespace Graph { + +class TypeParameterController : public ViewController, public ListViewDataSource, public SelectableTableViewDataSource { +public: + TypeParameterController(Responder * parentResponder); + + void didBecomeFirstResponder() override; + bool handleEvent(Ion::Events::Event event) override; + + // ViewController + const char * title() override; + View * view() override { return &m_selectableTableView; } + void viewWillAppear() override; + + // ListViewDataSource + int numberOfRows() const override { return k_numberOfTypes; } + KDCoordinate rowHeight(int j) override; + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + MessageTableCellWithExpression * reusableCell(int index, int type) override; + int reusableCellCount(int type) override { return k_numberOfTypes; } + int typeAtLocation(int i, int j) override { return 0; } + + void setRecord(Ion::Storage::Record record) { m_record = record; } +private: + constexpr static int k_numberOfTypes = 3; + StackViewController * stackController() const; + SelectableTableView m_selectableTableView; + MessageTableCellWithExpression m_cells[k_numberOfTypes]; + Ion::Storage::Record m_record; +}; + +} + +#endif diff --git a/apps/graph/values/abscissa_title_cell.cpp b/apps/graph/values/abscissa_title_cell.cpp new file mode 100644 index 000000000..bd174fccd --- /dev/null +++ b/apps/graph/values/abscissa_title_cell.cpp @@ -0,0 +1,23 @@ +#include "abscissa_title_cell.h" +#include + +namespace Graph { + +void AbscissaTitleCell::drawRect(KDContext * ctx, KDRect rect) const { + EvenOddMessageTextCell::drawRect(ctx, rect); + // Draw the separator + if (m_separatorLeft) { + KDRect r = separatorRect(bounds()); + ctx->fillRect(r, Shared::HideableEvenOddEditableTextCell::hideColor()); + } +} + +void AbscissaTitleCell::layoutSubviews() { + m_messageTextView.setFrame(rectWithoutSeparator(bounds())); +} + +void AbscissaTitleCell::didSetSeparator() { + reloadCell(); +} + +} diff --git a/apps/graph/values/abscissa_title_cell.h b/apps/graph/values/abscissa_title_cell.h new file mode 100644 index 000000000..6c786c638 --- /dev/null +++ b/apps/graph/values/abscissa_title_cell.h @@ -0,0 +1,20 @@ +#ifndef GRAPH_ABSCISSA_TITLE_CELL_H +#define GRAPH_ABSCISSA_TITLE_CELL_H + +#include +#include + +namespace Graph { + +class AbscissaTitleCell : public EvenOddMessageTextCell, public Shared::Separable { +public: + AbscissaTitleCell() : EvenOddMessageTextCell(), Separable() {} + void drawRect(KDContext * ctx, KDRect rect) const override; + void layoutSubviews() override; +private: + void didSetSeparator() override; +}; + +} + +#endif diff --git a/apps/graph/values/derivative_parameter_controller.cpp b/apps/graph/values/derivative_parameter_controller.cpp index c7d44b95e..dbd53bd80 100644 --- a/apps/graph/values/derivative_parameter_controller.cpp +++ b/apps/graph/values/derivative_parameter_controller.cpp @@ -18,7 +18,7 @@ DerivativeParameterController::DerivativeParameterController(ValuesController * } void DerivativeParameterController::viewWillAppear() { - functionStore()->modelForRecord(m_record)->derivativeNameWithArgument(m_pageTitle, k_maxNumberOfCharsInTitle, Shared::CartesianFunction::Symbol()); + functionStore()->modelForRecord(m_record)->derivativeNameWithArgument(m_pageTitle, k_maxNumberOfCharsInTitle); } const char * DerivativeParameterController::title() { @@ -31,7 +31,7 @@ View * DerivativeParameterController::view() { void DerivativeParameterController::didBecomeFirstResponder() { selectCellAtLocation(0, 0); - app()->setFirstResponder(&m_selectableTableView); + Container::activeApp()->setFirstResponder(&m_selectableTableView); } bool DerivativeParameterController::handleEvent(Ion::Events::Event event) { @@ -58,7 +58,7 @@ bool DerivativeParameterController::handleEvent(Ion::Events::Event event) { return false; } -int DerivativeParameterController::numberOfRows() { +int DerivativeParameterController::numberOfRows() const { return k_totalNumberOfCell; }; @@ -73,7 +73,7 @@ HighlightCell * DerivativeParameterController::reusableCell(int index) { return cells[index]; } -int DerivativeParameterController::reusableCellCount() { +int DerivativeParameterController::reusableCellCount() const { return k_totalNumberOfCell; } @@ -81,9 +81,8 @@ KDCoordinate DerivativeParameterController::cellHeight() { return Metric::ParameterCellHeight; } -CartesianFunctionStore * DerivativeParameterController::functionStore() { - App * a = static_cast(app()); - return a->functionStore(); +ContinuousFunctionStore * DerivativeParameterController::functionStore() { + return App::app()->functionStore(); } } diff --git a/apps/graph/values/derivative_parameter_controller.h b/apps/graph/values/derivative_parameter_controller.h index bd643b334..10d64bec0 100644 --- a/apps/graph/values/derivative_parameter_controller.h +++ b/apps/graph/values/derivative_parameter_controller.h @@ -2,7 +2,7 @@ #define GRAPH_DERIVATIVE_PARAM_CONTROLLER_H #include -#include "../cartesian_function_store.h" +#include "../continuous_function_store.h" namespace Graph { @@ -17,15 +17,15 @@ public: bool handleEvent(Ion::Events::Event event) override; void viewWillAppear() override; void didBecomeFirstResponder() override; - int numberOfRows() override; + int numberOfRows() const override; KDCoordinate cellHeight() override; HighlightCell * reusableCell(int index) override; - int reusableCellCount() override; + int reusableCellCount() const override; void setRecord(Ion::Storage::Record record) { m_record = record; } private: - CartesianFunctionStore * functionStore(); + ContinuousFunctionStore * functionStore(); #if COPY_COLUMN constexpr static int k_totalNumberOfCell = 2; #else diff --git a/apps/graph/values/function_parameter_controller.cpp b/apps/graph/values/function_parameter_controller.cpp index 2ceca2e65..41d0ec4af 100644 --- a/apps/graph/values/function_parameter_controller.cpp +++ b/apps/graph/values/function_parameter_controller.cpp @@ -8,7 +8,7 @@ using namespace Shared; namespace Graph { FunctionParameterController::FunctionParameterController(ValuesController * valuesController) : - ValuesFunctionParameterController(CartesianFunction::Symbol()), + ValuesFunctionParameterController(), m_displayDerivativeColumn(I18n::Message::DerivativeFunctionColumn), m_valuesController(valuesController) { @@ -38,7 +38,7 @@ bool FunctionParameterController::handleEvent(Ion::Events::Event event) { return false; } -int FunctionParameterController::numberOfRows() { +int FunctionParameterController::numberOfRows() const { return k_totalNumberOfCell; }; @@ -53,7 +53,7 @@ HighlightCell * FunctionParameterController::reusableCell(int index) { return cells[index]; } -int FunctionParameterController::reusableCellCount() { +int FunctionParameterController::reusableCellCount() const { return k_totalNumberOfCell; } @@ -69,9 +69,8 @@ void FunctionParameterController::willDisplayCellForIndex(HighlightCell * cell, } } -ExpiringPointer FunctionParameterController::function() { - App * a = static_cast(app()); - return a->functionStore()->modelForRecord(m_record); +ExpiringPointer FunctionParameterController::function() { + return App::app()->functionStore()->modelForRecord(m_record); } } diff --git a/apps/graph/values/function_parameter_controller.h b/apps/graph/values/function_parameter_controller.h index 01f80ce32..18cc916cb 100644 --- a/apps/graph/values/function_parameter_controller.h +++ b/apps/graph/values/function_parameter_controller.h @@ -2,7 +2,7 @@ #define GRAPH_FUNCTION_PARAM_CONTROLLER_H #include "../../shared/expiring_pointer.h" -#include "../../shared/cartesian_function.h" +#include "../../shared/continuous_function.h" #include "../../shared/values_function_parameter_controller.h" namespace Graph { @@ -13,13 +13,13 @@ class FunctionParameterController : public Shared::ValuesFunctionParameterContro public: FunctionParameterController(ValuesController * valuesController); bool handleEvent(Ion::Events::Event event) override; - int numberOfRows() override; + int numberOfRows() const override; HighlightCell * reusableCell(int index) override; - int reusableCellCount() override; + int reusableCellCount() const override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; void viewWillAppear() override; private: - Shared::ExpiringPointer function(); + Shared::ExpiringPointer function(); #if COPY_COLUMN constexpr static int k_totalNumberOfCell = 2; #else diff --git a/apps/graph/values/interval_parameter_selector_controller.cpp b/apps/graph/values/interval_parameter_selector_controller.cpp new file mode 100644 index 000000000..67c2f57a1 --- /dev/null +++ b/apps/graph/values/interval_parameter_selector_controller.cpp @@ -0,0 +1,112 @@ +#include "interval_parameter_selector_controller.h" +#include "../../shared/interval_parameter_controller.h" +#include "../app.h" +#include +#include + +namespace Graph { + +IntervalParameterSelectorController::IntervalParameterSelectorController() : + ViewController(nullptr), + m_selectableTableView(this, this, this) +{ +} + +const char * IntervalParameterSelectorController::title() { + return I18n::translate(I18n::Message::IntervalSet); +} + +void IntervalParameterSelectorController::viewDidDisappear() { + /* Deselect the table properly because it needs to be relayouted the next time + * it appears: the number of rows might change according to the plot type. */ + m_selectableTableView.deselectTable(false); + m_selectableTableView.setFrame(KDRectZero); +} + +void IntervalParameterSelectorController::didBecomeFirstResponder() { + if (selectedRow() < 0) { + selectCellAtLocation(0, 0); + } + Container::activeApp()->setFirstResponder(&m_selectableTableView); +} + +bool IntervalParameterSelectorController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { + StackViewController * stack = (StackViewController *)parentResponder(); + Shared::IntervalParameterController * controller = App::app()->valuesController()->intervalParameterController(); + Shared::ContinuousFunction::PlotType plotType = plotTypeAtRow(selectedRow()); + controller->setTitle(messageForType(plotType)); + setStartEndMessages(controller, plotType); + controller->setInterval(App::app()->intervalForType(plotType)); + stack->push(controller); + return true; + } + return false; +} + +int IntervalParameterSelectorController::numberOfRows() const { + int rowCount = 0; + int plotTypeIndex = 0; + Shared::ContinuousFunction::PlotType plotType; + while (plotTypeIndex < Shared::ContinuousFunction::k_numberOfPlotTypes) { + plotType = static_cast(plotTypeIndex); + bool plotTypeIsShown = App::app()->functionStore()->numberOfActiveFunctionsOfType(plotType) > 0; + rowCount += plotTypeIsShown; + plotTypeIndex++; + } + return rowCount; +} + +HighlightCell * IntervalParameterSelectorController::reusableCell(int index) { + assert(0 <= index && index < reusableCellCount()); + return m_intervalParameterCell + index; +} + +int IntervalParameterSelectorController::reusableCellCount() const { + return Shared::ContinuousFunction::k_numberOfPlotTypes; +} + +void IntervalParameterSelectorController::willDisplayCellForIndex(HighlightCell * cell, int index) { + assert(0 <= index && index < numberOfRows()); + Shared::ContinuousFunction::PlotType plotType = plotTypeAtRow(index); + static_cast(cell)->setMessage(messageForType(plotType)); +} + +Shared::ContinuousFunction::PlotType IntervalParameterSelectorController::plotTypeAtRow(int j) const { + int rowCount = 0; + int plotTypeIndex = 0; + Shared::ContinuousFunction::PlotType plotType; + while (plotTypeIndex < Shared::ContinuousFunction::k_numberOfPlotTypes) { + plotType = static_cast(plotTypeIndex); + bool plotTypeIsShown = App::app()->functionStore()->numberOfActiveFunctionsOfType(plotType) > 0; + if (plotTypeIsShown && rowCount == j) { + break; + } + rowCount += plotTypeIsShown; + plotTypeIndex++; + } + assert(rowCount == j); + return plotType; +} + +I18n::Message IntervalParameterSelectorController::messageForType(Shared::ContinuousFunction::PlotType plotType) { + constexpr I18n::Message message[Shared::ContinuousFunction::k_numberOfPlotTypes] = { + I18n::Message::IntervalX, + I18n::Message::IntervalTheta, + I18n::Message::IntervalT + }; + return message[static_cast(plotType)]; +} + +void IntervalParameterSelectorController::setStartEndMessages(Shared::IntervalParameterController * controller, Shared::ContinuousFunction::PlotType plotType) { + if (plotType == Shared::ContinuousFunction::PlotType::Cartesian) { + controller->setStartEndMessages(I18n::Message::XStart, I18n::Message::XEnd); + } else if (plotType == Shared::ContinuousFunction::PlotType::Polar) { + controller->setStartEndMessages(I18n::Message::ThetaStart, I18n::Message::ThetaEnd); + } else { + assert(plotType == Shared::ContinuousFunction::PlotType::Parametric); + controller->setStartEndMessages(I18n::Message::TStart, I18n::Message::TEnd); + } +} + +} diff --git a/apps/graph/values/interval_parameter_selector_controller.h b/apps/graph/values/interval_parameter_selector_controller.h new file mode 100644 index 000000000..eef6e1b6a --- /dev/null +++ b/apps/graph/values/interval_parameter_selector_controller.h @@ -0,0 +1,33 @@ +#ifndef GRAPH_INTERVAL_PARAMETER_SELECTOR_CONTROLLER +#define GRAPH_INTERVAL_PARAMETER_SELECTOR_CONTROLLER + +#include +#include +#include "../../shared/continuous_function.h" + +namespace Graph { + +class IntervalParameterSelectorController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { +public: + IntervalParameterSelectorController(); + const char * title() override; + View * view() override { return &m_selectableTableView; } + void viewDidDisappear() override; + bool handleEvent(Ion::Events::Event event) override; + void didBecomeFirstResponder() override; + int numberOfRows() const override; + KDCoordinate cellHeight() override { return Metric::ParameterCellHeight; } + int reusableCellCount() const override; + HighlightCell * reusableCell(int index) override; + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + void setStartEndMessages(Shared::IntervalParameterController * controller, Shared::ContinuousFunction::PlotType plotType); +private: + Shared::ContinuousFunction::PlotType plotTypeAtRow(int j) const; + I18n::Message messageForType(Shared::ContinuousFunction::PlotType plotType); + MessageTableCellWithChevron m_intervalParameterCell[Shared::ContinuousFunction::k_numberOfPlotTypes]; + SelectableTableView m_selectableTableView; +}; + +} + +#endif diff --git a/apps/graph/values/values_controller.cpp b/apps/graph/values/values_controller.cpp index 85310c9ca..ad48293ee 100644 --- a/apps/graph/values/values_controller.cpp +++ b/apps/graph/values/values_controller.cpp @@ -1,62 +1,148 @@ #include "values_controller.h" #include +#include "../../shared/poincare_helpers.h" #include "../../constant.h" +#include "../app.h" using namespace Shared; using namespace Poincare; namespace Graph { -ValuesController::ValuesController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, Interval * interval, ButtonRowController * header) : - Shared::ValuesController(parentResponder, inputEventHandlerDelegate, header, I18n::Message::X, &m_intervalParameterController, interval), +// Constructors + +ValuesController::ValuesController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header) : + Shared::ValuesController(parentResponder, header), + m_selectableTableView(this), m_functionTitleCells{}, m_floatCells{}, + m_abscissaTitleCells{}, + m_abscissaCells{}, m_functionParameterController(this), - m_intervalParameterController(this, inputEventHandlerDelegate, m_interval), - m_derivativeParameterController(this) + m_intervalParameterController(this, inputEventHandlerDelegate), + m_derivativeParameterController(this), + m_setIntervalButton(this, I18n::Message::IntervalSet, Invocation([](void * context, void * sender) { + ValuesController * valuesController = (ValuesController *) context; + StackViewController * stack = ((StackViewController *)valuesController->stackController()); + IntervalParameterSelectorController * intervalSelectorController = valuesController->intervalParameterSelectorController(); + if (intervalSelectorController->numberOfRows() == 1) { + IntervalParameterController * intervalController = valuesController->intervalParameterController(); + intervalController->setInterval(valuesController->intervalAtColumn(0)); + int i = 1; + intervalSelectorController->setStartEndMessages(intervalController, valuesController->plotTypeAtColumn(&i)); + stack->push(intervalController); + return true; + } + stack->push(intervalSelectorController); + return true; + }, this), k_font) { - for (int i = 0; i < k_maxNumberOfFunctions; i++) { + for (int i = 0; i < k_maxNumberOfDisplayableFunctions; i++) { m_functionTitleCells[i].setOrientation(FunctionTitleCell::Orientation::HorizontalIndicator); m_functionTitleCells[i].setFont(KDFont::SmallFont); } + setupSelectableTableViewAndCells(inputEventHandlerDelegate); + m_selectableTableView.setDelegate(this); } -bool ValuesController::handleEvent(Ion::Events::Event event) { - if ((event == Ion::Events::OK || event == Ion::Events::EXE) && selectedRow() == 0 - && selectedColumn()>0 && isDerivativeColumn(selectedColumn())) { - configureDerivativeFunction(); - return true; +// TableViewDataSource + +KDCoordinate ValuesController::columnWidth(int i) { + ContinuousFunction::PlotType plotType = plotTypeAtColumn(&i); + if (i == 0) { + return k_abscissaCellWidth; } - return Shared::ValuesController::handleEvent(event); + if (i > 0 && plotType == ContinuousFunction::PlotType::Parametric) { + return k_parametricCellWidth; + } + return k_cellWidth; +} + +KDCoordinate ValuesController::cumulatedWidthFromIndex(int i) { + return TableViewDataSource::cumulatedWidthFromIndex(i); +} + +int ValuesController::indexFromCumulatedWidth(KDCoordinate offsetX) { + return TableViewDataSource::indexFromCumulatedWidth(offsetX); } void ValuesController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { - Shared::ValuesController::willDisplayCellAtLocation(cell, i, j); - // The cell is the abscissa title cell: - if (j == 0 && i == 0) { - EvenOddMessageTextCell * mytitleCell = (EvenOddMessageTextCell *)cell; - mytitleCell->setMessage(I18n::Message::X); + // Handle hidden cells + int typeAtLoc = typeAtLocation(i,j); + if (typeAtLoc == k_editableValueCellType) { + StoreCell * storeCell = (StoreCell *)cell; + storeCell->setSeparatorLeft(i > 0); + } + + const int numberOfElementsInCol = numberOfElementsInColumn(i); + if (j > numberOfElementsInCol + 1) { + if (typeAtLoc == k_notEditableValueCellType || typeAtLoc == k_editableValueCellType) { + Shared::Hideable * hideableCell = hideableCellFromType(cell, typeAtLoc); + hideableCell->setHide(true); + hideableCell->reinit(); + } + return; + } else { + if (typeAtLoc == k_notEditableValueCellType || typeAtLoc == k_editableValueCellType) { + hideableCellFromType(cell, typeAtLoc)->setHide(false); + } + } + if (j == numberOfElementsInCol+1) { + static_cast(cell)->setEven(j%2 == 0); + if (typeAtLoc == k_notEditableValueCellType || typeAtLoc == k_editableValueCellType) { + hideableCellFromType(cell, typeAtLoc)->reinit(); + } return; } - // The cell is a function title cell: - if (j == 0 && i > 0) { + + Shared::ValuesController::willDisplayCellAtLocation(cell, i, j); + + if (typeAtLoc == k_abscissaTitleCellType) { + AbscissaTitleCell * myTitleCell = (AbscissaTitleCell *)cell; + myTitleCell->setMessage(valuesParameterMessageAtColumn(i)); + myTitleCell->setSeparatorLeft(i > 0); + return; + } + if (typeAtLoc == k_functionTitleCellType) { Shared::BufferFunctionTitleCell * myFunctionCell = (Shared::BufferFunctionTitleCell *)cell; const size_t bufferNameSize = Shared::Function::k_maxNameWithArgumentSize + 1; char bufferName[bufferNameSize]; - bool isDerivative = isDerivativeColumn(i); - /* isDerivativeColumn uses expiring pointers, so "function" must be created - * after the isDerivativeColumn call, else it will expire. */ - Shared::ExpiringPointer function = functionStore()->modelForRecord(recordAtColumn(i)); + bool isDerivative = false; + Ion::Storage::Record record = recordAtColumn(i, &isDerivative); + Shared::ExpiringPointer function = functionStore()->modelForRecord(record); + myFunctionCell->setHorizontalAlignment(0.5f); if (isDerivative) { - function->derivativeNameWithArgument(bufferName, bufferNameSize, CartesianFunction::Symbol()); + function->derivativeNameWithArgument(bufferName, bufferNameSize); } else { - function->nameWithArgument(bufferName, bufferNameSize, CartesianFunction::Symbol()); + function->nameWithArgument(bufferName, bufferNameSize); } myFunctionCell->setText(bufferName); myFunctionCell->setColor(function->color()); + return; } } +int ValuesController::typeAtLocation(int i, int j) { + plotTypeAtColumn(&i); + return Shared::ValuesController::typeAtLocation(i, j); +} + +// SelectableTableViewDelegate + +void ValuesController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { + if (withinTemporarySelection) { + return; + } + const int i = selectedColumn(); + const int j = selectedRow(); + const int numberOfElementsInCol = numberOfElementsInColumn(i); + if (j > 1 + numberOfElementsInCol) { + selectCellAtLocation(i, 1 + numberOfElementsInCol); + } +} + +// AlternateEmptyViewDelegate + I18n::Message ValuesController::emptyMessage() { if (functionStore()->numberOfDefinedModels() == 0) { return I18n::Message::NoFunction; @@ -64,104 +150,234 @@ I18n::Message ValuesController::emptyMessage() { return I18n::Message::NoActivatedFunction; } -IntervalParameterController * ValuesController::intervalParameterController() { - return &m_intervalParameterController; +// Values controller + +void ValuesController::setStartEndMessages(Shared::IntervalParameterController * controller, int column) { + int c = column+1; + m_intervalParameterSelectorController.setStartEndMessages(controller, plotTypeAtColumn(&c)); } +// Number of columns memoization + +void ValuesController::updateNumberOfColumns() const { + for (int plotTypeIndex = 0; plotTypeIndex < ContinuousFunction::k_numberOfPlotTypes; plotTypeIndex++) { + m_numberOfValuesColumnsForType[plotTypeIndex] = 0; + } + for (int i = 0; i < functionStore()->numberOfActiveFunctions(); i++) { + Ion::Storage::Record record = functionStore()->activeRecordAtIndex(i); + ExpiringPointer f = functionStore()->modelForRecord(record); + int plotTypeIndex = static_cast(f->plotType()); + m_numberOfValuesColumnsForType[plotTypeIndex] += numberOfColumnsForRecord(record); + } + m_numberOfColumns = 0; + for (int plotTypeIndex = 0; plotTypeIndex < ContinuousFunction::k_numberOfPlotTypes; plotTypeIndex++) { + // Count abscissa column if the sub table does exist + m_numberOfColumns += numberOfColumnsForPlotType(plotTypeIndex); + } +} + +// Model getters + Ion::Storage::Record ValuesController::recordAtColumn(int i) { - assert(i > 0); + bool isDerivative = false; + return recordAtColumn(i, &isDerivative); +} + +Ion::Storage::Record ValuesController::recordAtColumn(int i, bool * isDerivative) { + assert(typeAtLocation(i, 0) == k_functionTitleCellType); + ContinuousFunction::PlotType plotType = plotTypeAtColumn(&i); int index = 1; - for (int k = 0; k < functionStore()->numberOfDefinedModels(); k++) { - Ion::Storage::Record record = functionStore()->definedRecordAtIndex(k); - ExpiringPointer f = functionStore()->modelForRecord(record); - if (f->isActive()) { - if (i == index) { - return record; - } - index++; - if (f->displayDerivative()) { - if (i == index) { - return record; - } - index++; - } + for (int k = 0; k < functionStore()->numberOfActiveFunctionsOfType(plotType); k++) { + Ion::Storage::Record record = functionStore()->activeRecordOfTypeAtIndex(plotType, k); + const int numberOfColumnsForCurrentRecord = numberOfColumnsForRecord(record); + if (index <= i && i < index + numberOfColumnsForCurrentRecord) { + ExpiringPointer f = functionStore()->modelForRecord(record); + *isDerivative = i != index && f->plotType() == ContinuousFunction::PlotType::Cartesian; + return record; } + index += numberOfColumnsForCurrentRecord; } assert(false); return nullptr; } -bool ValuesController::isDerivativeColumn(int i) { - assert(i >= 1); - int index = 1; - for (int k = 0; k < functionStore()->numberOfDefinedModels(); k++) { - ExpiringPointer f = functionStore()->modelForRecord(functionStore()->definedRecordAtIndex(k)); - if (f->isActive()) { - if (i == index) { - return false; - } - index++; - if (f->displayDerivative()) { - if (i == index) { - return true; - } - index++; - } +Shared::Interval * ValuesController::intervalAtColumn(int columnIndex) { + return App::app()->intervalForType(plotTypeAtColumn(&columnIndex)); +} + +// Number of columns + +int ValuesController::numberOfColumnsForAbscissaColumn(int column) { + return numberOfColumnsForPlotType((int)plotTypeAtColumn(&column)); +} + +int ValuesController::numberOfColumnsForRecord(Ion::Storage::Record record) const { + ExpiringPointer f = functionStore()->modelForRecord(record); + ContinuousFunction::PlotType plotType = f->plotType(); + return 1 + + (plotType == ContinuousFunction::PlotType::Cartesian && f->displayDerivative()); +} + +int ValuesController::numberOfColumnsForPlotType(int plotTypeIndex) const { + return m_numberOfValuesColumnsForType[plotTypeIndex] + (m_numberOfValuesColumnsForType[plotTypeIndex] > 0); // Count abscissa column if there is one +} + +int ValuesController::numberOfAbscissaColumnsBeforeColumn(int column) { + int result = 0; + int plotType = column < 0 ? Shared::ContinuousFunction::k_numberOfPlotTypes : (int)plotTypeAtColumn(&column) + 1; + for (int plotTypeIndex = 0; plotTypeIndex < plotType; plotTypeIndex++) { + result += (m_numberOfValuesColumnsForType[plotTypeIndex] > 0); + } + return result; +} + +int ValuesController::numberOfValuesColumns() { + return m_numberOfColumns - numberOfAbscissaColumnsBeforeColumn(-1); +} + +ContinuousFunction::PlotType ValuesController::plotTypeAtColumn(int * i) const { + int plotTypeIndex = 0; + while (plotTypeIndex < ContinuousFunction::k_numberOfPlotTypes && *i >= numberOfColumnsForPlotType(plotTypeIndex)) { + *i -= numberOfColumnsForPlotType(plotTypeIndex++); + } + assert(plotTypeIndex < ContinuousFunction::k_numberOfPlotTypes); + return static_cast(plotTypeIndex); +} + +// Function evaluation memoization + +int ValuesController::valuesColumnForAbsoluteColumn(int column) { + return column - numberOfAbscissaColumnsBeforeColumn(column); +} + +int ValuesController::absoluteColumnForValuesColumn(int column) { + int abscissaColumns = 0; + int valuesColumns = 0; + int plotTypeIndex = 0; + do { + abscissaColumns++; + assert(plotTypeIndex < Shared::ContinuousFunction::k_numberOfPlotTypes); + valuesColumns += m_numberOfValuesColumnsForType[plotTypeIndex++]; + } while (valuesColumns <= column); + return column + abscissaColumns; +} + +void ValuesController::fillMemoizedBuffer(int column, int row, int index) { + double abscissa = intervalAtColumn(column)->element(row-1); // Subtract the title row from row to get the element index + bool isDerivative = false; + double evaluationX = NAN; + double evaluationY = NAN; + Ion::Storage::Record record = recordAtColumn(column, &isDerivative); + Shared::ExpiringPointer function = functionStore()->modelForRecord(record); + Poincare::Context * context = textFieldDelegateApp()->localContext(); + bool isParametric = function->plotType() == ContinuousFunction::PlotType::Parametric; + if (isDerivative) { + evaluationY = function->approximateDerivative(abscissa, context); + } else { + Poincare::Coordinate2D eval = function->evaluate2DAtParameter(abscissa, context); + evaluationY = eval.x2(); + if (isParametric) { + evaluationX = eval.x1(); } } - assert(false); - return false; + char * buffer = memoizedBufferAtIndex(index); + int numberOfChar = 0; + if (isParametric) { + assert(numberOfChar < k_valuesCellBufferSize-1); + buffer[numberOfChar++] = '('; + numberOfChar += PoincareHelpers::ConvertFloatToText(evaluationX, buffer+numberOfChar, k_valuesCellBufferSize - numberOfChar, Preferences::LargeNumberOfSignificantDigits); + assert(numberOfChar < k_valuesCellBufferSize-1); + buffer[numberOfChar++] = ';'; + } + numberOfChar += PoincareHelpers::ConvertFloatToText(evaluationY, buffer+numberOfChar, k_valuesCellBufferSize - numberOfChar, Preferences::LargeNumberOfSignificantDigits); + if (isParametric) { + assert(numberOfChar+1 < k_valuesCellBufferSize-1); + buffer[numberOfChar++] = ')'; + buffer[numberOfChar] = 0; + } } -void ValuesController::configureDerivativeFunction() { - m_derivativeParameterController.setRecord(recordAtColumn(selectedColumn())); - StackViewController * stack = stackController(); - stack->push(&m_derivativeParameterController); +// Parameter controllers + +ViewController * ValuesController::functionParameterController() { + bool isDerivative = false; + Ion::Storage::Record record = recordAtColumn(selectedColumn(), &isDerivative); + if (functionStore()->modelForRecord(record)->plotType() != ContinuousFunction::PlotType::Cartesian) { + return nullptr; + } + if (isDerivative) { + m_derivativeParameterController.setRecord(record); + return &m_derivativeParameterController; + } + m_functionParameterController.setRecord(record); + return &m_functionParameterController; } -int ValuesController::maxNumberOfCells() { - return k_maxNumberOfCells; +I18n::Message ValuesController::valuesParameterMessageAtColumn(int columnIndex) const { + return ContinuousFunction::ParameterMessageForPlotType(plotTypeAtColumn(&columnIndex)); } -int ValuesController::maxNumberOfFunctions() { - return k_maxNumberOfFunctions; +// Cells & View + +Shared::Hideable * ValuesController::hideableCellFromType(HighlightCell * cell, int type) { + if (type == k_notEditableValueCellType) { + Shared::HideableEvenOddBufferTextCell * myCell = static_cast(cell); + return static_cast(myCell); + } + assert(type == k_editableValueCellType); + Shared::StoreCell * myCell = static_cast(cell); + return static_cast(myCell); } Shared::BufferFunctionTitleCell * ValuesController::functionTitleCells(int j) { - assert(j >= 0 && j < k_maxNumberOfFunctions); + assert(j >= 0 && j < k_maxNumberOfDisplayableFunctions); return &m_functionTitleCells[j]; } EvenOddBufferTextCell * ValuesController::floatCells(int j) { - assert(j >= 0 && j < k_maxNumberOfCells); + assert(j >= 0 && j < k_maxNumberOfDisplayableCells); return &m_floatCells[j]; } -FunctionParameterController * ValuesController::functionParameterController() { - return &m_functionParameterController; + +/* ValuesController::ValuesSelectableTableView */ + +int writeMatrixBrakets(char * buffer, const int bufferSize, int type) { + /* Write the double brackets required in matrix notation. + * - type == 1: "[[" + * - type == 0: "][" + * - type == -1: "]]" + */ + int currentChar = 0; + assert(currentChar < bufferSize-1); + buffer[currentChar++] = type < 0 ? '[' : ']'; + assert(currentChar < bufferSize-1); + buffer[currentChar++] = type <= 0 ? '[' : ']'; + return currentChar; } -double ValuesController::evaluationOfAbscissaAtColumn(double abscissa, int columnIndex) { - TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app(); - bool isDerivative = isDerivativeColumn(columnIndex); - /* isDerivativeColumn uses expiring pointers, so "function" must be created - * after the isDerivativeColumn call, else it will expire. */ - Shared::ExpiringPointer function = functionStore()->modelForRecord(recordAtColumn(columnIndex)); - if (isDerivative) { - return function->approximateDerivative(abscissa, myApp->localContext()); - } - return function->evaluateAtAbscissa(abscissa, myApp->localContext()); -} - -void ValuesController::updateNumberOfColumns() { - int result = 1; - for (int i = 0; i < functionStore()->numberOfActiveFunctions(); i++) { - ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); - if (f->isActive()) { - result += 1 + f->displayDerivative(); +bool ValuesController::ValuesSelectableTableView::handleEvent(Ion::Events::Event event) { + bool handledEvent = SelectableTableView::handleEvent(event); + if (handledEvent && (event == Ion::Events::Copy || event == Ion::Events::Cut)) { + const char * text = Clipboard::sharedClipboard()->storedText(); + if (text[0] == '(') { + constexpr int bufferSize = 2*PrintFloat::k_maxFloatCharSize + 6; // "[[a][b]]" gives 6 characters in addition to the 2 floats + char buffer[bufferSize]; + int currentChar = 0; + currentChar += writeMatrixBrakets(buffer + currentChar, bufferSize - currentChar, -1); + assert(currentChar < bufferSize-1); + size_t semiColonPosition = UTF8Helper::CopyUntilCodePoint(buffer+currentChar, TextField::maxBufferSize() - currentChar, text+1, ';'); + currentChar += semiColonPosition; + currentChar += writeMatrixBrakets(buffer + currentChar, bufferSize - currentChar, 0); + assert(currentChar < bufferSize-1); + currentChar += UTF8Helper::CopyUntilCodePoint(buffer+currentChar, TextField::maxBufferSize() - currentChar, text+1+semiColonPosition+1, ')'); + currentChar += writeMatrixBrakets(buffer + currentChar, bufferSize - currentChar, 1); + assert(currentChar < bufferSize-1); + buffer[currentChar] = 0; + Clipboard::sharedClipboard()->store(buffer); } } - m_numberOfColumns = result; + return handledEvent; } } diff --git a/apps/graph/values/values_controller.h b/apps/graph/values/values_controller.h index 6b55d1e57..cf6893246 100644 --- a/apps/graph/values/values_controller.h +++ b/apps/graph/values/values_controller.h @@ -1,42 +1,133 @@ #ifndef GRAPH_VALUES_CONTROLLER_H #define GRAPH_VALUES_CONTROLLER_H -#include "../cartesian_function_store.h" +#include "../continuous_function_store.h" #include "../../shared/buffer_function_title_cell.h" +#include "../../shared/hideable_even_odd_buffer_text_cell.h" #include "../../shared/values_controller.h" #include "../../shared/interval_parameter_controller.h" +#include "../../shared/store_cell.h" +#include "abscissa_title_cell.h" +#include "interval_parameter_selector_controller.h" #include "derivative_parameter_controller.h" #include "function_parameter_controller.h" namespace Graph { -class ValuesController : public Shared::ValuesController { +class ValuesController : public Shared::ValuesController, public SelectableTableViewDelegate { public: - ValuesController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::Interval * interval, ButtonRowController * header); - bool handleEvent(Ion::Events::Event event) override; + ValuesController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header); + + // TableViewDataSource + KDCoordinate columnWidth(int i) override; + KDCoordinate cumulatedWidthFromIndex(int i) override; + int indexFromCumulatedWidth(KDCoordinate offsetX) override; void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; + int typeAtLocation(int i, int j) override; + + // SelectableTableViewDelegate + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false) override; + + // ButtonRowDelegate + Button * buttonAtIndex(int index, ButtonRowController::Position position) const override { + return const_cast