mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-18 16:27:34 +01:00
Merge branch 'master' into lavaos
This commit is contained in:
@@ -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
104
.github/workflows/ci-workflow.yml
vendored
Normal 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
3
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
build
|
||||
/output/
|
||||
build/device/**/*.pyc
|
||||
|
||||
26
.travis.yml
26
.travis.yml
@@ -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
112
Makefile
@@ -1,11 +1,13 @@
|
||||
include scripts/config.mak
|
||||
include build/config.mak
|
||||
|
||||
# Disable default Make rules
|
||||
.SUFFIXES:
|
||||
|
||||
object_for = $(addprefix $(BUILD_DIR)/,$(addsuffix .o,$(basename $(1))))
|
||||
|
||||
default: $(BUILD_DIR)/epsilon.$(EXE)
|
||||
# Define the default recipe
|
||||
|
||||
default:
|
||||
|
||||
# Define a standard rule helper
|
||||
# If passed a last parameter value of with_local_version, we also define an
|
||||
@@ -18,23 +20,51 @@ define rule_label
|
||||
endef
|
||||
|
||||
define rule_for
|
||||
$(addprefix $$(BUILD_DIR)/,$(strip $(2))): $(strip $(3)) | $$$$(@D)/.
|
||||
@ echo "$(shell printf "%-8s" $(strip $(1)))$$(@:$$(BUILD_DIR)/%=%)"
|
||||
$(Q) $(4)
|
||||
ifeq ($(strip $(5)),with_local_version)
|
||||
$(addprefix $$(BUILD_DIR)/,$(strip $(2))): $(addprefix $$(BUILD_DIR)/,$(strip $(3)))
|
||||
@ echo "$(shell printf "%-8s" $(strip $(1)))$$(@:$$(BUILD_DIR)/%=%)"
|
||||
$(Q) $(4)
|
||||
endif
|
||||
$(addprefix $$(BUILD_DIR)/,$(strip $(2))): $(strip $(3)) | $$$$(@D)/.
|
||||
@ echo "$(shell printf "%-8s" $(strip $(1)))$$(@:$$(BUILD_DIR)/%=%)"
|
||||
$(Q) $(4)
|
||||
endef
|
||||
|
||||
.PHONY: info
|
||||
info:
|
||||
@echo "EPSILON_VERSION = $(EPSILON_VERSION)"
|
||||
@echo "EPSILON_ONBOARDING_APP = $(EPSILON_ONBOARDING_APP)"
|
||||
@echo "EPSILON_BOOT_PROMPT = $(EPSILON_BOOT_PROMPT)"
|
||||
@echo "EPSILON_APPS = $(EPSILON_APPS)"
|
||||
@echo "EPSILON_I18N = $(EPSILON_I18N)"
|
||||
@echo "PLATFORM" = $(PLATFORM)
|
||||
@echo "DEBUG" = $(DEBUG)
|
||||
@echo "EPSILON_GETOPT" = $(EPSILON_GETOPT)
|
||||
@echo "ESCHER_LOG_EVENTS_BINARY" = $(ESCHER_LOG_EVENTS_BINARY)
|
||||
@echo "QUIZ_USE_CONSOLE" = $(QUIZ_USE_CONSOLE)
|
||||
@echo "ION_STORAGE_LOG" = $(ION_STORAGE_LOG)
|
||||
@echo "POINCARE_TREE_LOG" = $(POINCARE_TREE_LOG)
|
||||
@echo "POINCARE_TESTS_PRINT_EXPRESSIONS" = $(POINCARE_TESTS_PRINT_EXPRESSIONS)
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "Device targets"
|
||||
@echo " make epsilon_flash"
|
||||
@echo " make epsilon.dfu"
|
||||
@echo " make epsilon.onboarding.dfu"
|
||||
@echo " make epsilon.onboarding.update.dfu"
|
||||
@echo " make epsilon.onboarding.beta.dfu"
|
||||
@echo " make flasher.light.bin"
|
||||
@echo " make flasher.verbose.dfu"
|
||||
@echo " make bench.ram.bin"
|
||||
@echo " make bench.flash.bin"
|
||||
@echo " make binpack"
|
||||
@echo ""
|
||||
@echo "Simulator targets"
|
||||
@echo " make PLATFORM=simulator"
|
||||
@echo " make PLATFORM=simulator TARGET=android"
|
||||
@echo " make PLATFORM=simulator TARGET=ios"
|
||||
@echo " make PLATFORM=simulator TARGET=macos"
|
||||
@echo " make PLATFORM=simulator TARGET=web"
|
||||
@echo " make PLATFORM=simulator TARGET=windows"
|
||||
|
||||
# Since we're building out-of-tree, we need to make sure the output directories
|
||||
# are created, otherwise the receipes will fail (e.g. gcc will fail to create
|
||||
@@ -50,9 +80,9 @@ $(BUILD_DIR)%/.:
|
||||
# To make objects dependent on their directory, we need a second expansion
|
||||
.SECONDEXPANSION:
|
||||
|
||||
# Each sub-Makefile can either add sources to the $(src) variable or define a
|
||||
# new executable target. The $(src) variable lists the sources that will be
|
||||
# built and linked to every executable being generated.
|
||||
# Each sub-Makefile can either add sources to $(%_src) variables or define a
|
||||
# new executable target. The $(%_src) variables list the sources that can be
|
||||
# built and linked to executables being generated.
|
||||
|
||||
ifeq ($(USE_LIBA),0)
|
||||
include liba/Makefile.bridge
|
||||
@@ -68,65 +98,29 @@ include python/Makefile
|
||||
include escher/Makefile
|
||||
# Executable Makefiles
|
||||
include apps/Makefile
|
||||
include scripts/struct_layout/Makefile
|
||||
include scripts/scenario/Makefile
|
||||
include build/struct_layout/Makefile
|
||||
include build/scenario/Makefile
|
||||
include quiz/Makefile # Quiz needs to be included at the end
|
||||
|
||||
objs = $(call object_for,$(src))
|
||||
.SECONDARY: $(objs)
|
||||
all_src = $(apps_all_src) $(escher_src) $(ion_all_src) $(kandinsky_src) $(liba_src) $(libaxx_src) $(poincare_src) $(python_src) $(epsilon_src) $(runner_src) $(ion_target_device_flasher_light_src) $(ion_target_device_flasher_verbose_src) $(ion_target_device_bench_src) $(tests_src)
|
||||
all_objs = $(call object_for,$(all_src))
|
||||
.SECONDARY: $(all_objs)
|
||||
|
||||
# Load source-based dependencies
|
||||
# Compilers can generate Makefiles that states the dependencies of a given
|
||||
# objet to other source and headers. This serve no purpose for a clean build,
|
||||
# but allows correct yet optimal incremental builds.
|
||||
-include $(objs:.o=.d)
|
||||
-include $(all_objs:.o=.d)
|
||||
|
||||
# Load platform-specific targets
|
||||
# We include them before the standard ones to give them precedence.
|
||||
-include scripts/targets.$(PLATFORM).mak
|
||||
# Define main and shortcut targets
|
||||
include build/targets.mak
|
||||
|
||||
# Define rules for executables
|
||||
# Those can be built directly with make executable.exe as a shortcut. They also
|
||||
# depends on $(objs)
|
||||
# Fill in the default recipe
|
||||
DEFAULT ?= $(BUILD_DIR)/epsilon.$(EXE)
|
||||
default: $(DEFAULT)
|
||||
|
||||
executables = epsilon test flasher
|
||||
|
||||
define rules_for_executable
|
||||
$$(BUILD_DIR)/$(1).$$(EXE): $$(objs)
|
||||
.PHONY: $(1).$$(EXE)
|
||||
$(1).$$(EXE): $$(BUILD_DIR)/$(1).$$(EXE)
|
||||
endef
|
||||
|
||||
$(foreach executable,$(executables),$(eval $(call rules_for_executable,$(executable))))
|
||||
|
||||
# Define standard compilation rules
|
||||
|
||||
$(eval $(call rule_for, \
|
||||
AS, %.o, %.s, \
|
||||
$$(CC) $$(SFLAGS) -c $$< -o $$@ \
|
||||
))
|
||||
|
||||
$(eval $(call rule_for, \
|
||||
CC, %.o, %.c, \
|
||||
$$(CC) $$(SFLAGS) $$(CFLAGS) -c $$< -o $$@, \
|
||||
with_local_version \
|
||||
))
|
||||
|
||||
$(eval $(call rule_for, \
|
||||
CXX, %.o, %.cpp, \
|
||||
$$(CXX) $$(SFLAGS) $$(CXXFLAGS) -c $$< -o $$@, \
|
||||
with_local_version \
|
||||
))
|
||||
|
||||
$(eval $(call rule_for, \
|
||||
OCC, %.o, %.m, \
|
||||
$$(CC) $$(SFLAGS) $$(CFLAGS) -c $$< -o $$@ \
|
||||
))
|
||||
|
||||
$(eval $(call rule_for, \
|
||||
LD, %.$$(EXE), , \
|
||||
$$(LD) $$^ $$(LDFLAGS) -o $$@ \
|
||||
))
|
||||
# Load standard build rules
|
||||
include build/rules.mk
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<h1 align="center">Omega x Epsilon 11.2.0</h1>
|
||||
<h1 align="center">Omega x Epsilon 12.0.0</h1>
|
||||
|
||||
[](https://gitlab.com/joachim2lefournis/Omega/pipelines)
|
||||
[](https://creativecommons.org/licenses/by-nc-sa/4.0/)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
7
apps/apps_container_launch_default.cpp
Normal file
7
apps/apps_container_launch_default.cpp
Normal 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);
|
||||
}
|
||||
5
apps/apps_container_launch_on_boarding.cpp
Normal file
5
apps/apps_container_launch_on_boarding.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
#include "apps_container.h"
|
||||
|
||||
App::Snapshot * AppsContainer::initialAppSnapshot() {
|
||||
return onBoardingAppSnapshot();
|
||||
}
|
||||
23
apps/apps_container_prompt_beta.cpp
Normal file
23
apps/apps_container_prompt_beta.cpp
Normal 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;
|
||||
8
apps/apps_container_prompt_none.cpp
Normal file
8
apps/apps_container_prompt_none.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "apps_container.h"
|
||||
|
||||
I18n::Message AppsContainer::k_promptMessages[] = {};
|
||||
|
||||
KDColor AppsContainer::k_promptColors[] = {};
|
||||
|
||||
int AppsContainer::k_promptNumberOfMessages = 0;
|
||||
|
||||
19
apps/apps_container_prompt_update.cpp
Normal file
19
apps/apps_container_prompt_update.cpp
Normal 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;
|
||||
@@ -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
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
class AppsContainerStorage : public AppsContainer {
|
||||
public:
|
||||
static AppsContainerStorage * sharedContainer();
|
||||
AppsContainerStorage();
|
||||
int numberOfApps() override;
|
||||
App::Snapshot * appSnapshotAtIndex(int index) override;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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\
|
||||
)
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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\
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -90,7 +90,7 @@ void ConsoleLineCell::layoutSubviews() {
|
||||
}
|
||||
|
||||
void ConsoleLineCell::didBecomeFirstResponder() {
|
||||
app()->setFirstResponder(&m_scrollableView);
|
||||
Container::activeApp()->setFirstResponder(&m_scrollableView);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -31,7 +31,7 @@ View * EditorView::subviewAtIndex(int index) {
|
||||
}
|
||||
|
||||
void EditorView::didBecomeFirstResponder() {
|
||||
app()->setFirstResponder(&m_textArea);
|
||||
Container::activeApp()->setFirstResponder(&m_textArea);
|
||||
}
|
||||
|
||||
void EditorView::layoutSubviews() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ KDSize ScriptNameCell::minimalSizeForOptimalDisplay() const {
|
||||
}
|
||||
|
||||
void ScriptNameCell::didBecomeFirstResponder() {
|
||||
app()->setFirstResponder(&m_textField);
|
||||
Container::activeApp()->setFirstResponder(&m_textField);
|
||||
}
|
||||
|
||||
void ScriptNameCell::layoutSubviews() {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#include "constant.h"
|
||||
|
||||
constexpr int Constant::LargeNumberOfSignificantDigits;
|
||||
constexpr int Constant::MediumNumberOfSignificantDigits;
|
||||
constexpr int Constant::ShortNumberOfSignificantDigits;
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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\
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
37
apps/graph/continuous_function_store.cpp
Normal file
37
apps/graph/continuous_function_store.cpp
Normal 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];
|
||||
}
|
||||
|
||||
}
|
||||
33
apps/graph/continuous_function_store.h
Normal file
33
apps/graph/continuous_function_store.h
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
106
apps/graph/list/domain_parameter_controller.cpp
Normal file
106
apps/graph/list/domain_parameter_controller.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
40
apps/graph/list/domain_parameter_controller.h
Normal file
40
apps/graph/list/domain_parameter_controller.h
Normal 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
|
||||
@@ -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
Reference in New Issue
Block a user