Merge branch 'master' into lavaos

This commit is contained in:
Quentin Guidée
2019-10-27 11:44:35 +01:00
2315 changed files with 59412 additions and 28221 deletions

View File

@@ -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"

104
.github/workflows/ci-workflow.yml vendored Normal file
View File

@@ -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

3
.gitignore vendored
View File

@@ -1 +1,2 @@
build
/output/
build/device/**/*.pyc

View File

@@ -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

112
Makefile
View File

@@ -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:

View File

@@ -1,4 +1,4 @@
<h1 align="center">Omega x Epsilon 11.2.0</h1>
<h1 align="center">Omega x Epsilon 12.0.0</h1>
[![Gitlab pipeline status](https://img.shields.io/gitlab/pipeline/joachim2lefournis/Omega/lavaos?logo=gitlab&style=for-the-badge)](https://gitlab.com/joachim2lefournis/Omega/pipelines)
[![License: CC BY-NC-SA 4.0](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg?logo=creative%20commons&style=for-the-badge)](https://creativecommons.org/licenses/by-nc-sa/4.0/)

View File

@@ -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)

View File

@@ -1,4 +1,5 @@
#include "apps_container.h"
#include "apps_container_storage.h"
#include "global_preferences.h"
#include <ion.h>
#include <poincare/init.h>
@@ -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);
}
}

View File

@@ -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 <ion/events.h>
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;

View File

@@ -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);
}

View File

@@ -0,0 +1,5 @@
#include "apps_container.h"
App::Snapshot * AppsContainer::initialAppSnapshot() {
return onBoardingAppSnapshot();
}

View File

@@ -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;

View File

@@ -0,0 +1,8 @@
#include "apps_container.h"
I18n::Message AppsContainer::k_promptMessages[] = {};
KDColor AppsContainer::k_promptColors[] = {};
int AppsContainer::k_promptNumberOfMessages = 0;

View File

@@ -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;

View File

@@ -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

View File

@@ -9,7 +9,6 @@
class AppsContainerStorage : public AppsContainer {
public:
static AppsContainerStorage * sharedContainer();
AppsContainerStorage();
int numberOfApps() override;
App::Snapshot * appSnapshotAtIndex(int index) override;

View File

@@ -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;
}

View File

@@ -3,14 +3,11 @@
#include <escher.h>
class AppsContainer;
class BatteryTimer : public Timer {
public:
BatteryTimer(AppsContainer * container);
BatteryTimer();
private:
bool fire() override;
AppsContainer * m_container;
};
#endif

View File

@@ -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;

View File

@@ -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\
)

View File

@@ -1,5 +1,4 @@
#include "app.h"
#include "../apps_container.h"
#include "calculation_icon.h"
#include <apps/i18n.h>
#include <poincare/symbol.h>
@@ -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();
}
}

View File

@@ -27,14 +27,16 @@ public:
void tidy() override;
CalculationStore m_calculationStore;
};
static App * app() {
return static_cast<App *>(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;
};

View File

@@ -1,7 +1,6 @@
#include "calculation.h"
#include "calculation_store.h"
#include "../shared/poincare_helpers.h"
#include <poincare/symbol.h>
#include <poincare/exception_checkpoint.h>
#include <poincare/undefined.h>
#include <poincare/unreal.h>
#include <string.h>
@@ -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<const char *>(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<Calculation *>(const_cast<char *>(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<double>(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<double>(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;
}
}

View File

@@ -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
};
}

View File

@@ -1,88 +1,154 @@
#include "calculation_store.h"
#include <assert.h>
#include "../shared/poincare_helpers.h"
#include <poincare/rational.h>
#include <poincare/symbol.h>
#include <poincare/undefined.h>
#include <assert.h>
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<Calculation> 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<Calculation>(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<Calculation> 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<Calculation>(reinterpret_cast<Calculation *>(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<Calculation> calcI = calculationAtIndex(i);
char * nextCalc = reinterpret_cast<char *>(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<Calculation> 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<char *>(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<Calculation *>(const_cast<char *>(calculationsStart));
int calculationIndex = 0;
while (calculationIndex < m_numberOfCalculations - 1) {
c = c->next();
calculationIndex++;
}
return reinterpret_cast<const char *>(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<Calculation> 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();
}
}

View File

@@ -2,23 +2,77 @@
#define CALCULATION_CALCULATION_STORE_H
#include "calculation.h"
#include <apps/shared/expiring_pointer.h>
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<Calculation> calculationAtIndex(int i);
Shared::ExpiringPointer<Calculation> 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<Calculation *>(const_cast<char *>(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<Calculation> 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];
};
}

View File

@@ -1,6 +1,5 @@
#include "edit_expression_controller.h"
#include "app.h"
#include "../apps_container.h"
#include <ion/display.h>
#include <poincare/preferences.h>
#include <assert.h>
@@ -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);

View File

@@ -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;

View File

@@ -1,6 +1,5 @@
#include "history_controller.h"
#include "app.h"
#include "../apps_container.h"
#include <assert.h>
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> 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<Calculation> 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> calculation = calculationAtIndex(j);
return calculation->height(App::app()->localContext(), j == selectedRow() && selectedSubviewType() == SubviewType::Output) + 4 * Metric::CommonSmallMargin;
}
int HistoryController::typeAtLocation(int i, int j) {

View File

@@ -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<Calculation> calculationAtIndex(int i);
CalculationSelectableTableView * selectableTableView();
void historyViewCellDidChangeSelection() override;
constexpr static int k_maxNumberOfDisplayedRows = 5;

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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\

View File

@@ -1,5 +1,4 @@
#include "app.h"
#include "../apps_container.h"
#include "code_icon.h"
#include <apps/i18n.h>
#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<char *>(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()

View File

@@ -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<App *>(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;

View File

@@ -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"

View File

@@ -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 *>(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<char *>(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<ConsoleController *>(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<App *>(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

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;
};

View File

@@ -90,7 +90,7 @@ void ConsoleLineCell::layoutSubviews() {
}
void ConsoleLineCell::didBecomeFirstResponder() {
app()->setFirstResponder(&m_scrollableView);
Container::activeApp()->setFirstResponder(&m_scrollableView);
}
}

View File

@@ -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<App *>(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 *>(app())->variableBoxController();
VariableBoxController * varBox = App::app()->variableBoxController();
varBox->loadFunctionsAndVariables();
return varBox;
}
InputEventHandlerDelegateApp * EditorController::inputEventHandlerDelegateApp() {
return static_cast<App *>(app());
}
StackViewController * EditorController::stackController() {
return static_cast<StackViewController *>(parentResponder());
}

View File

@@ -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;

View File

@@ -31,7 +31,7 @@ View * EditorView::subviewAtIndex(int index) {
}
void EditorView::didBecomeFirstResponder() {
app()->setFirstResponder(&m_textArea);
Container::activeApp()->setFirstResponder(&m_textArea);
}
void EditorView::layoutSubviews() {

View File

@@ -36,7 +36,7 @@ MenuController::MenuController(Responder * parentResponder, App * pythonDelegate
}
ConsoleController * MenuController::consoleController() {
return static_cast<App *>(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<ScriptNameCell *>(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<AppsContainer *>(const_cast<Container *>(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<ScriptNameCell *>(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<AppsContainer *>(const_cast<Container *>(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<AppsContainer *>(const_cast<Container *>(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default);
AppsContainer::sharedAppsContainer()->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default);
return true;
}

View File

@@ -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; }

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -48,7 +48,7 @@ bool SandboxController::handleEvent(Ion::Events::Event event) {
}
void SandboxController::redrawWindow() {
static_cast<AppsContainer *>(const_cast<Container *>(app()->container()))->redrawWindow();
AppsContainer::sharedAppsContainer()->redrawWindow();
}
}

View File

@@ -26,7 +26,7 @@ KDSize ScriptNameCell::minimalSizeForOptimalDisplay() const {
}
void ScriptNameCell::didBecomeFirstResponder() {
app()->setFirstResponder(&m_textField);
Container::activeApp()->setFirstResponder(&m_textField);
}
void ScriptNameCell::layoutSubviews() {

View File

@@ -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; }

View File

@@ -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) {

View File

@@ -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:

View File

@@ -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 *>(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;
}

View File

@@ -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;

View File

@@ -1,5 +0,0 @@
#include "constant.h"
constexpr int Constant::LargeNumberOfSignificantDigits;
constexpr int Constant::MediumNumberOfSignificantDigits;
constexpr int Constant::ShortNumberOfSignificantDigits;

View File

@@ -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();
};

View File

@@ -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() {

View File

@@ -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:

View File

@@ -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;
};

View File

@@ -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\

View File

@@ -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;

View File

@@ -2,11 +2,12 @@
#define GRAPH_APP_H
#include <escher.h>
#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<size_t>(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<App *>(Container::activeApp());
}
Snapshot * snapshot() const {
return static_cast<Snapshot *>(::App::snapshot());
}
bool XNTCanBeOverriden() const override { return false; }
CodePoint XNT() override;
NestedMenuController * variableBoxForInputEventHandler(InputEventHandler * textInput) override;
CartesianFunctionStore * functionStore() override { return static_cast<CartesianFunctionStore *>(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;

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -1,28 +0,0 @@
#include "cartesian_function_store.h"
extern "C" {
#include <assert.h>
#include <stddef.h>
}
#include <ion.h>
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];
}
}

View File

@@ -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 <stdint.h>
#include <escher.h>
namespace Graph {
class CartesianFunctionStore : public Shared::FunctionStore {
public:
Shared::ExpiringPointer<Shared::CartesianFunction> modelForRecord(Ion::Storage::Record record) const { return Shared::ExpiringPointer<Shared::CartesianFunction>(static_cast<Shared::CartesianFunction *>(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

View File

@@ -0,0 +1,37 @@
#include "continuous_function_store.h"
extern "C" {
#include <assert.h>
#include <stddef.h>
}
#include <ion.h>
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];
}
}

View File

@@ -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<Shared::ContinuousFunction> modelForRecord(Ion::Storage::Record record) const { return Shared::ExpiringPointer<Shared::ContinuousFunction>(static_cast<Shared::ContinuousFunction *>(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<Shared::ContinuousFunction::PlotType *>(context);
return isFunctionActive(model, context) && plotType == static_cast<Shared::ContinuousFunction *>(model)->plotType();
}
mutable Shared::ContinuousFunction m_functions[k_maxNumberOfMemoizedModels];
};
}
#endif

View File

@@ -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<double> 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 *>(app());
Coordinate2D<double> 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 *>(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<double> 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;
}

View File

@@ -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<double> computeNewPointOfInterestFromAbscissa(double start, int direction);
ContinuousFunctionStore * functionStore() const;
virtual Poincare::Coordinate2D<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) = 0;
GraphView * m_graphView;
BannerView * m_bannerView;
Shared::InteractiveCurveViewRange * m_graphRange;

View File

@@ -1,5 +1,6 @@
#include "calculation_parameter_controller.h"
#include "graph_controller.h"
#include "../app.h"
#include <assert.h>
#include <cmath>
@@ -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<CalculationGraphController *>(controller)->setRecord(m_record);
}
StackViewController * stack = static_cast<StackViewController *>(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<MessageTableCell *>(cell)->setMessage(titles[index - 1]);
static_cast<MessageTableCell *>(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();
}
}

View File

@@ -2,7 +2,6 @@
#define GRAPH_CALCULATION_PARAMETER_CONTROLLER_H
#include <escher.h>
#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];

View File

@@ -1,5 +1,6 @@
#include "curve_parameter_controller.h"
#include "graph_controller.h"
#include "../app.h"
#include <apps/i18n.h>
#include <assert.h>
@@ -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<ContinuousFunction> 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() {

View File

@@ -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;

View File

@@ -1,7 +1,7 @@
#include "extremum_graph_controller.h"
#include "../app.h"
#include "../../shared/poincare_helpers.h"
#include <poincare/serialization_helper.h>
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<double> 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<double> MaximumGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) {
return functionStore()->modelForRecord(m_record)->nextMaximumFrom(start, step, max, context);
}

View File

@@ -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<double> 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<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override;
};
}

View File

@@ -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<ContinuousFunction> 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<float> 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<ContinuousFunction> 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<GraphController *>(this)->interactiveCurveViewRange()->xMin();
resultxMax = const_cast<GraphController *>(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<GraphController *>(this)->curveView()->pixelWidth() / 2;
for (int i = 0; i < functionsCount; i++) {
ExpiringPointer<ContinuousFunction> 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<CartesianFunction> 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<ContinuousFunction> 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<CartesianFunction> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(functionIndex));
ExpiringPointer<ContinuousFunction> 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 *>(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 *>(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<ContinuousFunction> 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<ContinuousFunction> 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<double> 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;
}
}

View File

@@ -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<CartesianFunctionStore *>(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<ContinuousFunctionStore *>(Shared::FunctionGraphController::functionStore()); }
bool defautRangeIsNormalized() const override;
void interestingFunctionRange(Shared::ExpiringPointer<Shared::ContinuousFunction> 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;

View File

@@ -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 <poincare/preferences.h>
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<CartesianFunction> 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<ContinuousFunction> 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<double> 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<CartesianFunction> 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<ContinuousFunction> 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<double>(y, buffer + numberOfChar, bufferSize-numberOfChar, Constant::ShortNumberOfSignificantDigits);
double y = function->approximateDerivative(cursor->x(), App::app()->localContext());
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(y, buffer + numberOfChar, bufferSize-numberOfChar, Preferences::ShortNumberOfSignificantDigits);
assert(numberOfChar <= bufferSize);
strlcpy(buffer+numberOfChar, space, bufferSize-numberOfChar);
bannerView()->derivativeView()->setText(buffer);
bannerView()->reload();

View File

@@ -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) {}
};
}

View File

@@ -1,14 +1,14 @@
#include "graph_view.h"
#include <float.h>
#include "../app.h"
#include <assert.h>
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<CartesianFunction> 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<ContinuousFunction> 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<float>(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());
}
}

View File

@@ -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;
};

View File

@@ -40,7 +40,8 @@ Layout IntegralGraphController::createFunctionLayout(ExpiringPointer<Shared::Fun
constexpr size_t bufferSize = SymbolAbstract::k_maxNameSize+5; // f(x)dx
char buffer[bufferSize];
const char * dx = "dx";
int numberOfChars = function->nameWithArgument(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);
}

View File

@@ -1,6 +1,6 @@
#include "intersection_graph_controller.h"
#include "../app.h"
#include "../../shared/poincare_helpers.h"
#include <poincare/preferences.h>
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<CartesianFunction> f = functionStore()->modelForRecord(m_record);
int numberOfChar = f->nameWithArgument(buffer, bufferSize-2, CartesianFunction::Symbol());
ExpiringPointer<ContinuousFunction> 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<CartesianFunction> g = functionStore()->modelForRecord(m_intersectedRecord);
numberOfChar += g->nameWithArgument(buffer+numberOfChar, bufferSize-numberOfChar-1, CartesianFunction::Symbol());
ExpiringPointer<ContinuousFunction> 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<double>(m_cursor->y(), buffer+numberOfChar, bufferSize-numberOfChar, Constant::MediumNumberOfSignificantDigits);
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(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<double> IntersectionGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) {
// TODO The following three lines should be factored.
Poincare::Coordinate2D<double> result = Poincare::Coordinate2D<double>(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<double> 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;
}
}
}

View File

@@ -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<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override;
Ion::Storage::Record m_intersectedRecord;
};

View File

@@ -1,4 +1,6 @@
#include "preimage_graph_controller.h"
#include "../../shared/poincare_helpers.h"
#include <poincare/serialization_helper.h>
namespace Graph {
@@ -21,7 +23,7 @@ PreimageGraphController::PreimageGraphController(
{
}
Poincare::Expression::Coordinate2D PreimageGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) {
Poincare::Coordinate2D<double> PreimageGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) {
Poincare::Expression expression = Poincare::Float<double>::Builder(m_image);
return functionStore()->modelForRecord(m_record)->nextIntersectionFrom(start, step, max, context, expression);
}

View File

@@ -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<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override;
double m_image;
};

View File

@@ -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();
}

View File

@@ -1,5 +1,6 @@
#include "root_graph_controller.h"
#include "../app.h"
#include "../../shared/poincare_helpers.h"
#include <poincare/serialization_helper.h>
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<double> RootGraphController::computeNewPointOfInterest(double start, double step, double max, Context * context) {
return functionStore()->modelForRecord(m_record)->nextRootFrom(start, step, max, context);
}
}

View File

@@ -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<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override;
};
}

View File

@@ -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 <poincare/preferences.h>
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 *>(app());
ExpiringPointer<CartesianFunction> function = myApp->functionStore()->modelForRecord(m_record);
double y = function->evaluateAtAbscissa(floatBody, textFieldDelegateApp()->localContext());
m_cursor->moveTo(floatBody, y);
ExpiringPointer<ContinuousFunction> 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 *>(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<CartesianFunction> function = myApp->functionStore()->modelForRecord(m_record);
double y = function->approximateDerivative(m_cursor->x(), myApp->localContext());
PoincareHelpers::ConvertFloatToText<double>(y, buffer + legendLength, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits);
ExpiringPointer<ContinuousFunction> function = App::app()->functionStore()->modelForRecord(m_record);
double y = function->approximateDerivative(m_cursor->x(), context);
PoincareHelpers::ConvertFloatToText<double>(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<double>(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<double>(y, buffer + legendLength, bufferSize - legendLength, precision);
m_bannerView->bView()->setText(buffer);
m_bannerView->reload();
}
bool TangentGraphController::moveCursorHorizontally(int direction) {
App * myApp = static_cast<App *>(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() {

View File

@@ -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 {

View File

@@ -0,0 +1,106 @@
#include "domain_parameter_controller.h"
#include <apps/i18n.h>
#include "../app.h"
#include <assert.h>
using namespace Shared;
namespace Graph {
DomainParameterController::DomainParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) :
FloatParameterController<float>(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<Shared::ContinuousFunction> DomainParameterController::function() const {
assert(!m_record.isNull());
App * myApp = App::app();
return myApp->functionStore()->modelForRecord(m_record);
}
FloatParameterController<float>::InfinityTolerance DomainParameterController::infinityAllowanceForRow(int row) const {
Shared::ContinuousFunction::PlotType plotType = function()->plotType();
if (plotType == Shared::ContinuousFunction::PlotType::Cartesian) {
return row == 0 ? FloatParameterController<float>::InfinityTolerance::MinusInfinity : FloatParameterController<float>::InfinityTolerance::PlusInfinity;
}
return FloatParameterController<float>::InfinityTolerance::None;
}
}

View File

@@ -0,0 +1,40 @@
#ifndef GRAPH_LIST_DOMAIN_PARAMATER_CONTROLLER_H
#define GRAPH_LIST_DOMAIN_PARAMATER_CONTROLLER_H
#include <escher/message_table_cell_with_expression.h>
#include <ion/storage.h>
#include "../../shared/continuous_function.h"
#include "../../shared/expiring_pointer.h"
#include "../../shared/float_parameter_controller.h"
namespace Graph {
class DomainParameterController : public Shared::FloatParameterController<float> {
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<Shared::ContinuousFunction> function() const;
MessageTableCellWithEditableText m_domainCells[k_totalNumberOfCell];
Ion::Storage::Record m_record;
};
}
#endif

View File

@@ -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<AppsContainer *>(const_cast<Container *>(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<AppsContainer *>(const_cast<Container *>(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> function = modelStore()->modelForRecord(modelStore()->recordAtIndex(selectedRow()));
ExpiringPointer<ContinuousFunction> function = modelStore()->modelForRecord(modelStore()->recordAtIndex(selectedRow()));
setFunctionNameInTextField(function, textField);
m_selectableTableView.selectedCell()->setHighlighted(true);
app()->setFirstResponder(&m_selectableTableView);
static_cast<AppsContainer *>(const_cast<Container *>(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> function = modelStore()->modelForRecord(modelStore()->recordAtIndex(j));
ExpiringPointer<ContinuousFunction> 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<Function> f = modelStore()->modelForRecord(modelStore()->recordAtIndex(j));
ExpiringPointer<ContinuousFunction> f = modelStore()->modelForRecord(modelStore()->recordAtIndex(j));
KDColor textColor = f->isActive() ? KDColorBlack : Palette::GreyDark;
myCell->setTextColor(textColor);
}
void ListController::setFunctionNameInTextField(ExpiringPointer<Function> function, TextField * textField) {
void ListController::setFunctionNameInTextField(ExpiringPointer<ContinuousFunction> 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();
}
}

Some files were not shown because too many files have changed in this diff Show More