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
[](https://gitlab.com/joachim2lefournis/Omega/pipelines)
[](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