diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 30b227d0415..3c42afa59aa 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,14 +1,29 @@ --- name: Bug report -about: Omega is not working like it should? Let us know! +about: Upsilon is not working like it should? Let us know! title: '' -labels: Bug, Triage +labels: 'Status: Triage, Type: Bug' assignees: '' --- -#### Describe the bug +**Describe the bug** +A clear and concise description of what the bug is. +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error -#### Environment - - Omega Version: {go to settings > about > Omega Version and type the version here} +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Device (please complete the following information):** + - The device on which you're running Upsilon (computer, n0110, n0100, etc...) + - Upsilon Version: [go to settings > about > Upsilon Version and type the version here] + - Upsilon commit: [settings > about > click one time on epsilon version] diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 3ba13e0cec6..00000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1 +0,0 @@ -blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index eee03aa6ac5..340a7684f13 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,10 +1,17 @@ --- name: Feature request -about: Suggest an idea for an improvement of Omega +about: Suggest an idea for Upsilon title: '' -labels: Feature, Triage +labels: 'Status: Triage, Type: Feature' assignees: '' --- -#### What I want to see in the next version of Omega +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md index 38b47c71fbd..30134e27332 100644 --- a/.github/ISSUE_TEMPLATE/other.md +++ b/.github/ISSUE_TEMPLATE/other.md @@ -1,8 +1,8 @@ --- name: Other -about: A question? A problem? ... +about: A question? A problem? … title: '' -labels: Triage +labels: 'Status: Triage' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/problems-during-installation.md b/.github/ISSUE_TEMPLATE/problems-during-installation.md index 347b6d9574a..329de3b67af 100644 --- a/.github/ISSUE_TEMPLATE/problems-during-installation.md +++ b/.github/ISSUE_TEMPLATE/problems-during-installation.md @@ -1,19 +1,19 @@ --- name: Problems during installation -about: Need help to install Omega? +about: Need help to install Upsilon? title: '' -labels: Installation issue, Triage +labels: 'Status: Triage, Type: Installation issue' assignees: '' --- -#### Describe the problem +**Describe the problem** -#### Logs +**Logs** ``` Copy/paste the logs here (If you have some) ``` -#### Environment - - Omega Version: {go to settings > about > Omega Version and type the version here} +**Environment** + - Upsilon Version: {go to settings > about > Upsilon Version and type the version here} diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 7f8c4832175..e2fd55ddd37 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -8,31 +8,90 @@ on: triggerIos: description: 'Run iOS tests' required: true - default: 'no' + default: 'yes' triggerMacos: description: 'Run macOS tests' required: true - default: 'no' + default: 'yes' trigger3DS: description: 'Run 3DS tests' required: true - default: 'no' - + default: 'yes' jobs: + fxcg: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install curl git python3 build-essential cmake pkg-config -y + - name: Get latest gint commit hash + run: | + LATEST_COMMIT_HASH=$(curl --silent https://gitea.planet-casio.com/api/v1/repos/Lephenixnoir/gint/branches/master | jq -r .commit.id) + echo "Latest commit hash is: $LATEST_COMMIT_HASH" + echo "LATEST_COMMIT_HASH=$LATEST_COMMIT_HASH" >> $GITHUB_OUTPUT + id: get-latest-commit-hash + - name: Cache gint/fxsdk installation + id: cache-gint + uses: actions/cache@v3 + with: + path: | + ~/.local/*/* + !~/.local/share/containers + key: ${{ runner.os }}-gint-${{ steps.get-latest-commit-hash.outputs.LATEST_COMMIT_HASH }} + - name: Install gint/fxsdk + if: steps.cache-gint.outputs.cache-hit != 'true' + env: + URL: "https://gitea.planet-casio.com/Lephenixnoir/GiteaPC/archive/master.tar.gz" + run: | + export PATH="~/.local/bin:$PATH" + cd "$(mktemp -d)" + curl "$URL" -o giteapc-master.tar.gz + tar -xzf giteapc-master.tar.gz + cd giteapc + python3 giteapc.py install Lephenixnoir/GiteaPC -y + sudo apt-get install python3-pil libusb-1.0-0-dev libudev-dev libsdl2-dev libpng-dev libudisks2-dev libglib2.0-dev libmpfr-dev libmpc-dev libppl-dev -y + giteapc install Lephenixnoir/fxsdk:noudisks2 Lephenixnoir/sh-elf-binutils Lephenixnoir/sh-elf-gcc -y + giteapc install Lephenixnoir/OpenLibm Vhex-Kernel-Core/fxlibc Lephenixnoir/sh-elf-gcc -y + giteapc install Lephenixnoir/gint -y + - name: Add fxsdk to PATH + run: echo "~/.local/bin" >> $GITHUB_PATH + - run: make -j2 PLATFORM=simulator TARGET=fxcg + - id: 'auth' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' + - id: 'upload-directory' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/upload-cloud-storage@v0' + with: + path: 'output/release/simulator/fxcg/epsilon.g3a' + destination: 'upsilon-binfiles.appspot.com/dev/simulator/' + parent: false + - uses: actions/upload-artifact@master + with: + name: epsilon.g3a + path: output/release/simulator/fxcg/epsilon.g3a nintendo_3ds: - if: github.event.inputs.trigger3DS == 'yes' + if: github.event.inputs.trigger3DS == 'yes' || github.event.inputs.trigger3DS == '' runs-on: ubuntu-latest + container: devkitpro/devkitarm:latest steps: - - run: wget https://github.com/devkitPro/pacman/releases/download/v1.0.2/devkitpro-pacman.amd64.deb -O /tmp/devkitpro-pacman.deb - - run: yes | sudo dpkg -i /tmp/devkitpro-pacman.deb - - run: yes | sudo dkp-pacman -Syu --needed devkitARM 3dstools libctru - - run: echo ::set-env name=DEVKITPRO::/opt/devkitpro - - run: echo ::set-env name=DEVKITARM::/opt/devkitpro/devkitARM - - run: echo ::set-env name=PATH::$DEVKITPRO/tools/bin:$DEVKITARM/bin:$PATH - - uses: actions/checkout@v1 with: submodules: true + - run: sudo apt-get update + - run: sudo apt-get install build-essential imagemagick libfreetype6-dev libjpeg-dev libpng-dev pkg-config -y + - run: yes | sudo dkp-pacman -S --needed devkitARM 3dstools libctru + - run: wget https://github.com/3DSGuy/Project_CTR/releases/download/makerom-v0.18.3/makerom-v0.18.3-ubuntu_x86_64.zip + - run: unzip makerom-v0.18.3-ubuntu_x86_64.zip + - run: rm makerom-v0.18.3-ubuntu_x86_64.zip + - run: chmod +x ./makerom + - run: echo "PATH=.:$PATH" >> $GITHUB_ENV - run: make -j2 PLATFORM=simulator TARGET=3ds - run: make -j2 PLATFORM=simulator TARGET=3ds epsilon.cia - uses: actions/upload-artifact@master @@ -49,7 +108,21 @@ jobs: - uses: actions/checkout@v2 with: submodules: 'recursive' - - run: make -j2 PLATFORM=simulator TARGET=android + - run: wget -nv https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip + - run: unzip -q android-ndk-r21e-linux-x86_64.zip + - run: make -j2 PLATFORM=simulator TARGET=android NDK_PATH=./android-ndk-r21e + - id: 'auth' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' + - id: 'upload-file' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/upload-cloud-storage@v0' + with: + path: 'output/release/simulator/android/epsilon.apk' + destination: 'upsilon-binfiles.appspot.com/dev/simulator/' + parent: false - uses: actions/upload-artifact@master with: name: epsilon-android.apk @@ -91,6 +164,18 @@ jobs: - run: mv output/release/device/n0100/flasher.light.bin final-output/flasher.light.bin - run: find final-output/ -type f -exec bash -c "shasum -a 256 -b {} > {}.sha256" \; - run: tar cvfz binpack-n0100.tgz final-output/* + - id: 'auth' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon'}} + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' + - id: 'upload-directory' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon'}} + uses: 'google-github-actions/upload-cloud-storage@v0' + with: + path: 'final-output/' + destination: 'upsilon-binfiles.appspot.com/dev/n100/' + parent: false - uses: actions/upload-artifact@master with: name: epsilon-binpack-n0100.tgz @@ -103,16 +188,29 @@ jobs: - uses: actions/checkout@v2 with: submodules: 'recursive' - - run: make -j2 epsilon.dfu - - run: make -j2 epsilon.onboarding.dfu - - run: make -j2 epsilon.onboarding.update.dfu - - run: make -j2 epsilon.onboarding.beta.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 - - run: make -j2 binpack + - run: make -j2 MODEL=n0110 epsilon.dfu + - run: make -j2 MODEL=n0110 epsilon.onboarding.dfu + - run: make -j2 MODEL=n0110 epsilon.onboarding.update.dfu + - run: make -j2 MODEL=n0110 epsilon.onboarding.beta.dfu + - run: make -j2 MODEL=n0110 flasher.light.dfu + - run: make -j2 MODEL=n0110 flasher.verbose.dfu + # We don't need bench as it is used only in factory + # - run: make -j2 bench.ram.dfu + # - run: make -j2 bench.flash.dfu + - run: make -j2 MODEL=n0110 binpack - run: cp output/release/device/n0110/binpack-n0110-`git rev-parse HEAD | head -c 7`.tgz output/release/device/n0110/binpack-n0110.tgz + - id: 'auth' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' + - id: 'upload-directory' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/upload-cloud-storage@v0' + with: + path: 'output/release/device/n0110/binpack/' + destination: 'upsilon-binfiles.appspot.com/dev/n110/' + parent: false - uses: actions/upload-artifact@master with: name: epsilon-binpack-n0110.tgz @@ -121,17 +219,36 @@ jobs: runs-on: ubuntu-latest steps: - run: sudo apt-get install build-essential imagemagick libfreetype6-dev libjpeg-dev libpng-dev pkg-config - - uses: numworks/setup-arm-toolchain@2020-q2 + - uses: numworks/setup-arm-toolchain@2022-08 - uses: actions/checkout@v2 with: submodules: 'recursive' - - run: make -j2 bootloader.dfu - - run: make MODEL=bootloader -j2 epsilon.A.dfu epsilon.B.dfu - - run: make MODEL=bootloader -j2 epsilon.onboarding.A.dfu epsilon.onboarding.B.dfu - - run: make MODEL=bootloader -j2 epsilon.onboarding.update.A.dfu epsilon.onboarding.update.B.dfu - - run: make MODEL=bootloader -j2 epsilon.onboarding.beta.A.dfu epsilon.onboarding.beta.B.dfu + - run: make -j2 MODEL=n0110 bootloader + - run: make -j2 epsilon.A.dfu epsilon.B.dfu + - run: make -j2 epsilon.onboarding.A.dfu + - run: make -j2 epsilon.onboarding.B.dfu + - run: make -j2 epsilon.onboarding.update.A.dfu + - run: make -j2 epsilon.onboarding.update.B.dfu + - run: make -j2 epsilon.onboarding.beta.A.dfu + - run: make -j2 epsilon.onboarding.beta.B.dfu - run: make -j2 binpack - run: cp output/release/device/bootloader/binpack-bootloader-`git rev-parse HEAD | head -c 7`.tgz output/release/device/bootloader/binpack-bootloader.tgz + - run: cp output/release/device/n0110/bootloader.bin output/release/device/bootloader/binpack/ + - run: cp output/release/device/n0110/bootloader.bin output/release/device/bootloader/ + - run: cd output/release/device/bootloader && for binary in *.bin; do shasum -a 256 -b binpack/${binary} > binpack/${binary}.sha256;done + - run: cd output/release/device/bootloader && tar cvfz binpack-bootloader.tgz binpack/* + - id: 'auth' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' + - id: 'upload-directory' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/upload-cloud-storage@v0' + with: + path: 'output/release/device/bootloader/binpack/' + destination: 'upsilon-binfiles.appspot.com/dev/n110/' + parent: false - uses: actions/upload-artifact@master with: name: epsilon-binpack-bootloader.tgz @@ -150,6 +267,18 @@ jobs: - run: make -j2 PLATFORM=simulator - run: make -j2 PLATFORM=simulator test.exe - run: cmd /c output\release\simulator\windows\test.exe --headless + - id: 'auth' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' + - id: 'upload-file' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/upload-cloud-storage@v0' + with: + path: 'output/release/simulator/windows/epsilon.exe' + destination: 'upsilon-binfiles.appspot.com/dev/simulator/' + parent: false - uses: actions/upload-artifact@master with: name: epsilon-windows.exe @@ -157,15 +286,27 @@ jobs: web: runs-on: ubuntu-latest steps: - - uses: numworks/setup-emscripten@v1 + - uses: numworks/setup-emscripten@master with: - sdk: latest-upstream + sdk: latest - uses: actions/checkout@v2 with: submodules: 'recursive' - run: make -j2 PLATFORM=simulator TARGET=web - run: make -j2 PLATFORM=simulator TARGET=web test.js - run: node output/release/simulator/web/test.js --headless + - id: 'auth' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' + - id: 'upload-file' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/upload-cloud-storage@v0' + with: + path: 'output/release/simulator/web/epsilon.js' + destination: 'upsilon-binfiles.appspot.com/dev/simulator/' + parent: false - uses: actions/upload-artifact@master with: name: epsilon-web.zip @@ -180,12 +321,24 @@ jobs: - run: make -j2 PLATFORM=simulator - run: make -j2 PLATFORM=simulator test.bin - run: output/release/simulator/linux/test.bin --headless + - id: 'auth' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/auth@v0' + with: + credentials_json: '${{secrets.GOOGLE_CREDENTIALS}}' + - id: 'upload-file' + if: ${{ github.event_name == 'push' && github.ref_name == 'upsilon-dev' && github.repository == 'UpsilonNumworks/Upsilon' }} + uses: 'google-github-actions/upload-cloud-storage@v0' + with: + path: 'output/release/simulator/linux/epsilon.bin' + destination: 'upsilon-binfiles.appspot.com/dev/simulator/' + parent: false - uses: actions/upload-artifact@master with: name: epsilon-linux.bin path: output/release/simulator/linux/epsilon.bin macos: - if: github.event.inputs.triggerMacos == 'yes' + if: github.event.inputs.triggerMacos == 'yes' || github.event.inputs.triggerMacos == '' runs-on: macOS-latest steps: - run: brew install numworks/tap/epsilon-sdk @@ -200,7 +353,7 @@ jobs: name: epsilon-macos.zip path: output/release/simulator/macos/epsilon.app ios: - if: github.event.inputs.triggerIos == 'yes' + if: github.event.inputs.triggerIos == 'yes' || github.event.inputs.triggerIos == '' runs-on: macOS-latest steps: - run: brew install numworks/tap/epsilon-sdk diff --git a/.github/workflows/metric-workflow.yml b/.github/workflows/metric-workflow.yml index 0cca6ba5360..d7ec925fccf 100644 --- a/.github/workflows/metric-workflow.yml +++ b/.github/workflows/metric-workflow.yml @@ -16,7 +16,7 @@ jobs: ref: ${{ github.event.pull_request.base.sha }} path: base - name: Build base - run: make -j2 -C base epsilon.elf + run: make -j2 -C base MODEL=n0110 epsilon.elf - name: Checkout PR head uses: actions/checkout@v2 with: @@ -24,14 +24,13 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} path: head - name: Build head - run: make -j2 -C head epsilon.elf + run: make -j2 -C head MODEL=n0110 epsilon.elf - name: Retrieve binary size analysis id: binary_size run: echo "::set-output name=table::$(python3 head/build/metrics/binary_size.py base/output/release/device/n0110/epsilon.elf head/output/release/device/n0110/epsilon.elf --labels Base Head --sections .text .rodata .bss .data --custom 'Total (RAM)' .data .bss --custom 'Total (ROM)' .text .rodata .data --escape)" - name: Add comment uses: actions/github-script@v3.0.0 with: - github-token: ${{ secrets.OMEGA_ROBOT_TOKEN }} script: | await github.issues.createComment({ owner: context.repo.owner, diff --git a/.gitignore b/.gitignore index 8645c08d24b..56cb4179582 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ epsilon.map .vscode .DS_Store .gradle +.idea/ +.vs +.cache/ +compile_commands.json diff --git a/.gitmodules b/.gitmodules index 02641c8d556..a535fa5dadf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "apps/rpn"] path = apps/rpn - url = https://github.com/Omega-Numworks/Omega-RPN.git + url = https://github.com/UpsilonNumworks/Upsilon-RPN.git [submodule "apps/atomic"] path = apps/atomic - url = https://github.com/Omega-Numworks/Omega-Atomic.git + url = https://github.com/UpsilonNumworks/atomic diff --git a/Makefile b/Makefile index 67f626c9a71..b3705e7f476 100644 --- a/Makefile +++ b/Makefile @@ -12,20 +12,40 @@ include build/toolchain.$(TOOLCHAIN).mak include build/variants.mak include build/helpers.mk -ifeq (${MODEL},n0110) - apps_list = ${EPSILON_APPS} +ifeq (${MODEL}, n0100) + ifeq ($(filter reader,$(apps_list)),) + $(warning reader app included, removing it on n0100. ) + EPSILON_APPS := $(filter-out reader,$(EPSILON_APPS)) + endif + ifneq ($(words $(EPSILON_I18N)), 1) + $(warning Only use 1 language on n0100, defaulting to en. ) + EPSILON_I18N := en + endif + ifeq ($(INCLUDE_ULAB), 1) + $(warning Removing uLab on n0100. ) + INCLUDE_ULAB := 0 + endif +endif + +ifeq ($(filter reader,$(apps_list)),) + HAS_READER := 1 +endif + +# Remove the external apps for the n0100 +ifeq (${MODEL}, n0100) + apps_list = $(foreach i, ${EPSILON_APPS}, $(if $(filter external, $(i)),,$(i))) else - ifeq (${MODEL},bootloader) apps_list = ${EPSILON_APPS} - else - apps_list = $(foreach i, ${EPSILON_APPS}, $(if $(filter external, $(i)),,$(i))) - endif endif ifdef FORCE_EXTERNAL apps_list = ${EPSILON_APPS} endif +ifeq ($(INCLUDE_ULAB), 1) + SFLAGS += -DINCLUDE_ULAB +endif + ifdef HOME_DISPLAY_EXTERNALS ifneq ($(filter external,$(apps_list)),) SFLAGS += -DHOME_DISPLAY_EXTERNALS @@ -73,6 +93,7 @@ help: @echo " make PLATFORM=simulator TARGET=web" @echo " make PLATFORM=simulator TARGET=windows" @echo " make PLATFORM=simulator TARGET=3ds" + @echo " make PLATFORM=simulator TARGET=fxcg" .PHONY: doc doc: @@ -86,7 +107,7 @@ print-%: @echo $*\'s origin is $(origin $*) # 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 +# are created, otherwise the recipes will fail (e.g. gcc will fail to create # "output/foo/bar.o" because the directory "output/foo" doesn't exist). # We need to mark those directories as precious, otherwise Make will try to get # rid of them upon completion (and fail, since those folders won't be empty). @@ -107,6 +128,7 @@ ifndef USE_LIBA endif ifeq ($(USE_LIBA),0) include liba/Makefile.bridge +include libaxx/Makefile.bridge else SFLAGS += -ffreestanding -nostdinc -nostdlib include liba/Makefile @@ -191,3 +213,13 @@ clean_run: cleanandcompile .PHONY: run run: compile ${MAKE} start + +.PHONY: translations +translations: + @echo "TRANSLATIONS" + $(Q) ${PYTHON} build/utilities/translate.py + +.PHONY: translations_clean +translations_clean: + @echo "TRANSLATIONS CLEAN" + $(Q) ${PYTHON} build/utilities/translations_clean.py diff --git a/README.fr.md b/README.fr.md index d7b1093d4b7..5a734af7a63 100644 --- a/README.fr.md +++ b/README.fr.md @@ -1,168 +1,474 @@ -

+

cc by-nc-sa 4.0 - Issues + Issues
- Discord + Discord

> Don't understand french ? speak english ? here's the [english README](./README.md) ! ## À propos -Omega est un fork d'Epsilon, l'OS de Numworks tournant sur les calculatrices du même nom, qui apporte beaucoup de fonctionnalités en plus. Omega est fait pour ceux qui aimeraient ajouter certaines fonctionnalités ayant été rejetées par Numworks à leurs calculatrices (pour des raisons 100% compréhensibles !). [Essayez en ligne](https://getomega.web.app/simulator). +Upsilon est un fork d'Omega, un fork d'Epsilon, l'OS de Numworks tournant sur les calculatrices du même nom, qui apporte beaucoup de fonctionnalités en plus, mais qui fut archivé et fermé pour des raisons légales après un changement de politique de Numworks. Upsilon est fait pour ceux qui aimeraient voir un futur pour les OS créées par les utilisateurs pour Numworks, même après l'arrèt du projet initial. ### Quelques fonctionnalités supplémentaires -- Retour du calcul littéral -- Une application RPN -- Application Externes -- Des thèmes -- Python amélioré (module os, méthode open...) -- Un tableau périodique et toutes les masses molaires des éléments dans la toolbox -- *Ainsi que d'autres à découvrir...* [Changelogs complets](https://github.com/Omega-Numworks/Omega/wiki/Changelog) | [Fonctionnalités princpales & captures d'écran](https://github.com/Omega-Numworks/Omega/wiki/Main-features). -## Installation +- Un module python kandinsky amélioré +- Un support pour fonds d'écrans personnalisés +- Des applications externes +- Un thème Upsilon +- La surcharge des opérateurs en python +- Un tableau périodique légèrement amélioré +- L'utilisation possible du signe "=" dans les calculs +- *Ainsi que tout ce qui a été ajouté sur Omega, et bien plus...* [Changelogs complets d'Omega](https://github.com/Omega-Numworks/Omega/wiki/Changelog) | [Fonctionnalités principales d'Omega & captures d'écran](https://github.com/Omega-Numworks/Omega/wiki/Main-features). -### Automatique +## Installation -Vous pouvez installer Omega automatiquement depuis [notre site](https://getomega.web.app/) sur la page "installer". +### Site web -

Omega Banner Discord

+Rendez-vous sur le [site d'Upsilon](https://getupsilon.web.app/) à la section "Installer". +Si votre calculatrice est reconnue, qu'elle contient une version d'Epsilon inférieure à 16 et que votre navigateur accepte WebUSB, la page vous proposera d'installer Upsilon. +Ne débranchez votre calculatrice qu'une fois l'installation terminée. ### Manuelle -Tout d'abord, suivez **la première étape** [ici](https://www.numworks.com/resources/engineering/software/build/), puis : + *Vous pouvez vous référer à ce [site internet](https://www.numworks.com/resources/engineering/software/build/)pour la première étape si vous avez des erreurs* + +### 1. Installation du SDK + +
+ +
+ +1.1 Linux + +
+ +
+ +Debian ou Ubuntu + +
+ +Il suffit juste d'installer les dépendances en tapant ces commandes dans un Terminal en mode super-utilisateur. + +```bash +apt-get install build-essential git imagemagick libx11-dev libxext-dev libfreetype6-dev libpng-dev libjpeg-dev pkg-config gcc-arm-none-eabi binutils-arm-none-eabi +``` + +C'est fait! Vous pouvez aller à l'étape 2. + +
+ +
+ +
+ +Fedora + +
+ +Installez toutes les dépendances grâce à cette commande: + +```bash +dnf install make automake gcc gcc-c++ kernel-devel git ImageMagick libX11-devel libXext-devel freetype-devel libpng-devel libjpeg-devel pkg-config arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++ +``` + +
+ +
+ +
+ +Nix/Nixos + +
+ +Installez toutes les dépendances grâce à cette commande: +```bash +nix-env -p gcc libpng libjpeg xorg.libX11 pkg-config freetype xorg.libXext python3 imagemagick python310Packages.lz4 python310Packages.pypng python310Packages.pypng gcc-arm-embedded +``` + +
+ +
+ +
+ +
+ +1.2 Mac + +
+ +Il est recommandé d'utiliser [Homebrew](https://brew.sh/). Une fois intsallé, utilisez: + +```bash +brew install numworks/tap/epsilon-sdk +``` + +Et toutes les dépendances seront installées. + +
+ +Vous pouvez aller à l'étape 2. + +
+ +
+ +
+ +1.3 Windows + +[Git](http://git-scm.com) doit être installé. + +
+ +
+ +Avec Msys2/Mingw (Supportés par Numwoks bien qu'il y ait beaucoup de bugs) + +L'environnement de compilation [Msys2](https://www.msys2.org/) est recommandé par Numworks pour obtenir la plupart des outils requis facilement. C'est ici que vous allez copier-colletoutes lecommandes de ce tutoriel. Une fois installé, copier-coller ces deux commandes dans le terminal: + +```bash +pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config mingw-w64-x86_64-libusb git make python +echo "export PATH=/mingw64/bin:$PATH" >> .bashrc +``` + +Ensuite, vous devrez installer [GCC toolchain for ARM](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads). Quand il vouest demandde choisir u dossier d'installation, choisissez `C:\msys64\home\User\gcc-arm\`. Il vous faudra ensuite ajouter ce dossier à votre $PATH. Tapez juste: + +```bash +echo "export PATH=$PATH:$HOME/gcc-arm/bin" >> .bashrc +``` + +Redémarrez votre terminal et vous pouvez aller à l'étape 2! + +
+ +
+ +Avec WSL 2 + +WSL est un système qui virtualise un environnement GNU/Linux dans Windows. + +Votre version de windows doit être >= 1903. + +#### Installation de WSL + +1. Apuyez simulatanément sur les touches "windows" et "x" puis cliquez sur "Powershell administrateur". Entrez ensuite ceci dans la nouvelle fenêtre: + +```powershell +dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart +``` + +Cette commande active WSL + +```powershell +dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart +``` + +Cette commande permet d'autoriser le démarrage des machines signées par Microsoft. + +2. Redémarrez votre ordinateur. + +3. Téléchargez [ce fichier](https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi) et suivez les instructions d'installation. + +4. Ouvrez votre fenêtre powershell comme avant et tapez: + +```powershell +wsl --set-default-version 2 +``` + +5. téléchargez [Ubuntu](https://www.microsoft.com/store/apps/9n6svws3rx71) depuis le Microsoft store. Vous pouvez aussi installer [Debian](https://www.microsoft.com/store/productI9MSVKQC78PK6). + +WSL est maintenant installé. + +6. Installez maintenant la version pour ARM de GCC. +```bash +sudo apt-get install build-essential git imagemagick libx11-dev libxext-dev libfreetype6-dev libpng-dev libjpeg-dev pkg-config gcc-arm-none-eabi binutils-arm-none-eabi +``` + +### Installation d'usbipd pour connecter la calculatrice à WSL (facultatif) + +Pour connecter la calculatrice, il faut installer cet [outil](https://github.com/dorssel/usbipd-win/releases/download/v1.3.0/usbipd-win_1.3.0.msi). Il permet de connecter des périphériques par internet. Suivez les instructions pour installer. + +#### Ubuntu + +1. Dans un terminal WSL Ubuntu, tapez: + +```bash +sudo apt install linux-tools-5.4.0-77-generic hwdata +``` + +2. Editez /etc/sudoers pour que l'on puisse utiliser la commande usbip. Sur Ubutu, cele est fait de cette manière: + +```bash +sudo visudo +``` + +3. Ajoutez `/usr/lib/linux-tools/5.4.0-77-generic` au début du secure_path. Après édition, la ligne devrait ressembler à: +`Defaults secure_path="/usr/lib/linux-tools/5.4.0-77-generic:/usr/local/sbin:..."` + +#### Debian + +1.Si vous utiliser Debian, utilisez cette commande: + +```bash +sudo apt install usbip hwdata usbutils +``` + +### Pour connecter la calculatrice à WSL + +1. Ouvrez encore un powershell en mode administrateur et tapez: + +```powershell + usbipd wsl list +``` + +Ceci va lister les périphériques USB connectés à l'ordinateur. Reagrdez le BUSID de votre "Numworks Calculator". + +2. Maintenant, lancez cette commande en remplçant par celui de votre caculatrice: + +```powershell +usbipd wsl attach --busid +``` + +Le mot de passe de votre machine WSL vous sera demandé. + +Vous pouvez aller à l'étape 2. + +
+ +
+ +
+ +### 2. Récupérer le code source + +Le code source est disponible dans une repository git. Récupérez-le de cette manière: + +```bash +git clone --recursive https://github.com/UpsilonNumworks/Upsilon.git +cd Upsilon +git checkout upsilon-dev +``` + +
+ +### 3. Choisissez le système à compiler
- Modèle n0100 -(note : vous pouvez changer `EPSILON_I18N=fr` en `en`, `nl`, `pt`, `it`, `de`, `es` ou `hu`). +Model n0100 + +(note: vous pouvez changer l'argument `EPSILON_I18N=en` avec `fr`, `nl`, `pt`, `it`, `de`, `es` or `hu`). ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega -git checkout omega-master make MODEL=n0100 clean -make MODEL=n0100 EPSILON_I18N=fr OMEGA_USERNAME="{Votre nom ici, 15 caractères max}" -j4 +make MODEL=n0100 EPSILON_I18N=en OMEGA_USERNAME="{Votre nom, maximum 15 caractères}" -j4 +``` + +Maintenant, lancez soit: + +```bash make MODEL=n0100 epsilon_flash ``` -Important : N'oubliez pas l'argument `--recursive`, Omega a besoin de sous-modules. -Vous pouvez aussi changer le nombre de processus parallèles pendant la compilation en changeant la valeur suivant `-j`. +pour directement flasher la calculatrice après avoir appuyé simultanément sur `reset` et `6` et avoir branché la calculatrice à l'ordinateur. + +
+ +soit: + +```bash +make MODEL=n0100 OMEGA_USERNAME="" binpack -j4 +``` + +pour compiler les binpacks que vous pouvez distribuer et flasher depuis le [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/).
- Modèle n0110 + +Model n0110 + +Le bootloader vous permet d'installer firmware dans des "slots" séparés. Dans ce cas les applications externes ne pourront pas utiliser toute la mémoire mais la moitié. Si un seul slot est utilisé, le bootloader permettra d'utiliser toute la mémoire. Sans bootloader, les apps external peuvent utiliser toute la mémoire. + +
+Bootloader + +Votre calculatrice doit être flashé avec le bootloader d'[Upsilon](https://getupsilon.web.app) ou d'[Omega](https://getomega.dev). +Compilez avec: ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega -git checkout omega-master make clean -make OMEGA_USERNAME="{Votre nom ici, 15 caractères max}" -j4 -make epsilon_flash +make OMEGA_USERNAME="{Votre nom, max 15 caractères}" -j4 +``` + +Ensuite lancez soit: + +```bash +make epsilon.A_flash +``` + +pour flasher le slot actuel ou pour flasher par le flasher du booloader avec RESET, puis 4 (flash) et 1 (flash slots) pour flasher n'importe quel slot. + +
+ +soit: + +```bash +make OMEGA_USERNAME="{Votre nom, max 15 caractères}" binpack -j4 +``` + +pour compiler les binpacks que vous pouvez distribuer et flasher depuis le [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). Vous les trouverez dans `output/release/device/bootloader/`. +
+ + +
+ +Model n0110 sans bootloader (obsolète, utilisez le bootloader à la place pour la protection contre Epsilon) +Compilez avec: + +```bash +make MODEL=n0110 clean +make MODEL=n0110 OMEGA_USERNAME="{Votre nom, max 15 caractères}" -j4 +``` + +Ensuite lancez soit: + +```bash +make MODEL=n0110 epsilon_flash ``` -Important : N'oubliez pas l'argument `--recursive`, Omega a besoin de sous-modules. -Vous pouvez aussi changer le nombre de processus parallèles pendant la compilation en changeant la valeur suivant `-j`. +pour directement flasher la calculatrice après avoir appuyé simultanément sur `RESET` et `6` et avoir branché la calculatrice à l'ordinateur. +
+soit: + +```bash +make MODEL=n0110 OMEGA_USERNAME="{Votre nom, max 15 caractères}" binpack -j4 +``` + +pour compiler les binpacks que vous pouvez distribuer et flasher depuis le [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). Vous les trouverez dans `output/release/device/n0110/`.
+
+ +
- Fichiers binaires - -Ces fichiers peuvent être utilisés pour distribuer Omega (pour que tout le monde puisse le flasher via [Webdfu_Numworks](https://ti-planet.github.io/webdfu_numworks/)). +Simulateur Natif + +Lancez cette commande: ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega -git checkout omega-master make clean -make MODEL=n0100 OMEGA_USERNAME="" -j8 -make MODEL=n0100 OMEGA_USERNAME="" binpack -j8 -make OMEGA_USERNAME="" -j8 -make OMEGA_USERNAME="" binpack -j8 ``` +Vous pouvez soit choisir d'utiliser la commmande qui détectera automatiquement votre plateforme: +```bash +make PLATFORM=simulator +``` +Ou choisir une commande qui correspond à votre plateforme: +```bash +make PLATFORM=simulator TARGET=android +make PLATFORM=simulator TARGET=ios +make PLATFORM=simulator TARGET=macos +make PLATFORM=simulator TARGET=web +make PLATFORM=simulator TARGET=windows +make PLATFORM=simulator TARGET=3ds +``` + +Vous trouverez les fichiers du simulateur dans `output/release/simulator/`. -Important : N'oubliez pas l'argument `--recursive`, Omega a besoin de sous-modules. -Vous pouvez aussi changer le nombre de processus parallèles pendant la compilation en changeant la valeur suivant `-j`. -
+
- Simulateur web - + +Simulateur web + D'abord, installez emsdk : ```bash git clone https://github.com/emscripten-core/emsdk.git cd emsdk -./emsdk install latest-fastcomp -./emsdk activate latest-fastcomp +./emsdk install 1.40.1 +./emsdk activate 1.40.1 source emsdk_env.sh ``` -Puis, compilez Omega : +Puis, compilez Upsilon : ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega -git checkout omega-master make clean -make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Votre nom ici, 15 caractères max}" -j4 +make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Votre nom, maximum 15 caractères}" -j4 ``` Le simulateur se trouve dans `output/release/simulator/web/simulator.zip` -Important : N'oubliez pas l'argument `--recursive`, Omega a besoin de sous-modules. -Vous pouvez aussi changer le nombre de processus parallèles pendant la compilation en changeant la valeur suivant `-j`. -
- Simulateur 3DS - -Vous aurez besoin de devkitPro et de devkitARM disponible dans votre `$PATH` (instructions [ici](https://devkitpro.org/wiki/Getting_Started) (en anglais)) + +Simulateur pour 3DS + +Il vous faut devkitPro et devkitARM installés et dans votre path (les instructions sont [ici](https://devkitpro.org/wiki/Getting_Started)) ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega -git checkout --recursive omega-dev +git clone --recursive https://github.com/UpsilonNumworks/Upsilon.git +cd Upsilon +git checkout --recursive upsilon-dev make PLATFORM=simulator TARGET=3ds -j ``` -Vous pouvez ensuite copier epsilon.3dsx sur une carte SD pour l'exécuter depuis le HBC ou utiliser 3dslink pour le lancer via le réseau : +Vous pouvez ensuite mettre epsilon.3dsx sur une carte SDpour le lancer depuis le HBC ou utilisez 3dslink pour le lancer via le réseau: ```bash -3dslink output/release/simulator/3ds/epsilon.3dsx -a +3dslink output/release/simulator/3ds/epsilon.3dsx -a <3DS' IP ADDRESS> ```
-Si vous avez besoin d'aide, n'hésitez pas à rejoindre notre serveur discord : https://discord.gg/X2TWhh9 +
-

Omega Banner Discord

+Important: n'oubliez pas l'argument `--recursive` Parce qu'Upsilon dépend de submodules. +Aussi, vous pouvez changer le nombre de processus de compilation en parallèles en changeant le nombre après l'argument `-j`. +N'oubliez pas de mettre votre nom à la place `{Votre nom, maximum 15 caractères}`.Si vous n'en voulez pas, enlevez l'argument `OMEGA_USERNAME`. + +Si vous avez besoin d'aide, n'hésitez pas à rejoindre notre serveur discord : + +

Omega Banner Discord

--- +## Liens utiles + +- [Upsilon external (pour installer des applications supplémentaires et des fonds d'écran)](https://upsilonnumworks.github.io/Upsilon-External/) +- [Documentation d'ulab](https://micropython-ulab.readthedocs.io/en/latest/) + ## Contribution -Pour contribuer, merci de lire le [Wiki](https://github.com/Omega-Numworks/Omega/wiki/Contributing) +Pour contribuer, merci de lire le [Wiki d'Omega](https://github.com/Omega-Numworks/Omega/wiki/Contributing), les mêmes règles s'appliquent ici. + +## Les autres projets -## Nos autres projets +Les anciens projets d'Omega, avant sa fermeture, qui ont été utilisés pour ce projet -* [Omega Themes](https://github.com/Omega-Numworks/Omega-Themes) -* [Omega Website](https://github.com/Omega-Numworks/Omega-Website) -* [Omega RPN `APP`](https://github.com/Omega-Numworks/Omega-RPN) -* [Omega Atomic `APP`](https://github.com/Omega-Numworks/Omega-Atomic) -* [Omega Design](https://github.com/Omega-Numworks/Omega-Design) -* [Omega Discord Bot](https://github.com/Omega-Numworks/Omega-Discord-Bot) -* [Omega App Template `BETA`](https://github.com/Omega-Numworks/Omega-App-Template) -* [External Apps](https://github.com/Omega-Numworks/External-Apps) +- [Omega Themes](https://github.com/Omega-Numworks/Omega-Themes) +- [Omega Website](https://github.com/Omega-Numworks/Omega-Website) +- [Omega RPN `APP`](https://github.com/Omega-Numworks/Omega-RPN) +- [Omega Atomic `APP`](https://github.com/Omega-Numworks/Omega-Atomic) +- [Omega Design](https://github.com/Omega-Numworks/Omega-Design) +- [Omega Discord Bot](https://github.com/Omega-Numworks/Omega-Discord-Bot) +- [Omega App Template `BETA`](https://github.com/Omega-Numworks/Omega-App-Template) +- [External Apps](https://github.com/Omega-Numworks/External-Apps) ## À propos d'Epsilon +Upsilon est un fork d'Omega, visant a continuer le projet des OS utilisateurs pour Numworks + Omega est un fork d'Epsilon, un système d'exploitation performant pour calculatrices graphiques. Il inclut huit applications pour les mathématiques de lycée et d'études supérieurs Vous pouvez essayer Epsilon depuis votre navigateur sur le [simulateur en ligne](https://www.numworks.com/simulator/). @@ -173,5 +479,6 @@ NumWorks est une marque déposée de NumWorks SAS, 24 Rue Godot de Mauroy, 75009 Nintendo est Nintendo 3DS sont des marques déposées de Nintendo of America Inc, 4600 150th Ave NE, Redmond, WA 98052, Etats-Unis. NumWorks SAS et Nintendo of America Inc ne sont en aucun cas associés avec ce projet. -* NumWorks Epsilon est disponible sous [Lisense CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). -* Omega est disponible sous [Lisense CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). +- NumWorks Epsilon est disponible sous [Lisense CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). +- Omega est disponible sous [Lisense CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). +- Upsilon est disponible sous [Lisense CC BY-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). diff --git a/README.md b/README.md index c9b6597d405..f9fbad9f88b 100644 --- a/README.md +++ b/README.md @@ -1,137 +1,425 @@ -

+

cc by-nc-sa 4.0 - Issues + Issues
- Discord + Discord

-> Vous ne comprenez pas l'anglais ? vous êtes francophone ? Regardez le [*LISEZ-MOI* français](./README.fr.md) ! +> Vous ne comprenez pas l'anglais ? Vous êtes francophone ? Regardez le [*LISEZ-MOI* français](./README.fr.md) ! ## About -Omega is a fork of Numworks' Epsilon, the OS that runs on their calculator, which brings many features to it. Omega is for the people who want to add features to the calculator, but cannot because they have been rejected by Numworks (for reasons that are 100% understandable!). [Try it online](https://getomega.web.app/simulator). +Upsilon is a fork of Omega, an user-made OS that runs on the Numworks calculator, which brings many features to it, but was discontinued because of a policy change from Numworks. Upsilon is for the people who want to see a future for user-made OSes for Numworks, even after the closure and archiving of Omega. ### Some new features -- Adding symbolic calculation back into the calculator -- An app for RPN -- Exernal apps -- A theme engine -- New python features (os module, open method...) -- A periodic table app + all of the molar masses for the elements in the toolbox -- *And much more to discover...* [Complete changelog](https://github.com/Omega-Numworks/Omega/wiki/Changelog) | [Main new features + screenshots](https://github.com/Omega-Numworks/Omega/wiki/Main-features). -## Installation +- Enhancements for the Kandinsky python module +- Support for wallpapers +- External apps +- A custom theme +- Operator overload for python +- Improvements for the Periodic table application +- *And everything that has been added to Omega before its termination!* [See Omega's changelog here](https://github.com/Omega-Numworks/Omega/wiki/Changelog) | [Main Omega features + screenshots](https://github.com/Omega-Numworks/Omega/wiki/Main-features). + +
-### Automatic +## Installation -You can install Omega automatically on our website [here](https://getomega.web.app/) in the "install" page. +### Installer -

Omega Banner Discord

+Go to the [Upsilon website](https://getupsilon.web.app/) to the "Install" section. +If your calculator is recognized, contains a version of Epsilon lower than 16 and your browser accepts WebUSB, the page will suggest you to install Upsilon. +Do not disconnect your calculator until the installation is complete. ### Manual -First of all, follow **step 1** [here](https://www.numworks.com/resources/engineering/software/build/). Then: + *You can refer to this [website](https://www.numworks.com/resources/engineering/software/build/) for the first step if you get errors.* + +### 1. Install SDK + +
+ +
+ +1.1 Linux + +
+ +
+ +Debian or Ubuntu + +
+ +You just have to install dependencies by running these command with superuser privileges in a Terminal: + +```bash +apt-get install build-essential git imagemagick libx11-dev libxext-dev libfreetype6-dev libpng-dev libjpeg-dev pkg-config gcc-arm-none-eabi binutils-arm-none-eabi +``` + +And there you can go to step 2! + +
+ +
+ +
+ +Fedora + +
+ +To install all dependencies: + +```bash +dnf install make automake gcc gcc-c++ kernel-devel git ImageMagick libX11-devel libXext-devel freetype-devel libpng-devel libjpeg-devel pkg-config arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++ +``` + +
+ +
- Model n0100 + +Nix or Nixos + +
+ +To install all dependencies: + +```bash +nix-shell -p gcc libpng libjpeg xorg.libX11 pkg-config freetype xorg.libXext python3 imagemagick python310Packages.lz4 python310Packages.pypng python310Packages.pypng gcc-arm-embedded +``` + +
+ +
+ +
+ +
+ +
+ +1.2 Mac + +
+ +It's recommended to use [Homebrew](https://brew.sh/). Once it's installed, just run: + +```bash +brew install numworks/tap/epsilon-sdk +``` + +and it will install all dependencies. + +
+ +And there you can go to step 2! + +
+ +
+ +
+ +1.3 Windows + +
+ +
+ +With Msys2/Mingw (officialized by numworks but with a lot of bugs) + +[Msys2](https://www.msys2.org/) environment is recommended by Numworks to get most of the required tools on Windows easily. It's where you'll paste all the commands of this tutorial. Once it'sinstalled, paste these commands into the Msys2 terminal. + +```bash +pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config mingw-w64-x86_64-libusb git make python +echo "export PATH=/mingw64/bin:$PATH" >> .bashrc +``` + +Next, you'll need to install the [GCC toolchain for ARM](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads). When prompted for aninstall location, choose `C:\msys64\home\User\gcc-arm\`. You'll then need to add this folder to your $PATH. Just enter: + +```bash +echo "export PATH=$PATH:$HOME/gcc-arm/bin" >> .bashrc +``` + +Just restart terminal and you can go to step 2! + +
+ +
+ +With WSL 2 + +You need a windows version >= 1903. + +#### WSL Installation + +1. Use simultaneously win + X keys and then click on "admin powershell". + +```powershell +dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart +``` + +This command activate WSL functionalities. + +```powershell +dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart +``` + +This one allows virtual machines developed by Microsoft. + +2. Restart your computer. + +3. Download [this file](https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi) and follow instructions. + +4. Now open powershell admin like before and type: + +```powershell +wsl --set-default-version 2 +``` +5. Download [Ubuntu](https://www.microsoft.com/store/apps/9n6svws3rx71) from Microsoft store. + +WSL is now installed. + +6. Then Install GCC cross compiler for ARM. +```bash +apt-get install build-essential git imagemagick libx11-dev libxext-dev libfreetype6-dev libpng-dev libjpeg-dev pkg-config gcc-arm-none-eabi binutils-arm-none-eabi +``` + +### Usbipd installation to connect your calculator +If you want to connect to the calculator, you have to connect to install this [tool](https://github.com/dorssel/usbipd-win/releases/download/v1.3.0/usbipd-win_1.3.0.msi). This will allow you toconnect WSL to the calculator through internet. Follow the on screen information to install. +#### Ubuntu +1. In a WSL Ubuntu command prompt, type: +```bash +sudo apt install linux-tools-5.4.0-77-generic hwdata +``` + +2. Edit /etc/sudoers so that root can find the usbip command. On Ubuntu, run this command. + +```bash +sudo visudo +``` + +3. Add `/usr/lib/linux-tools/5.4.0-77-generic` to the beginning of secure_path. After editing, the line should look similar to this. +`Defaults secure_path="/usr/lib/linux-tools/5.4.0-77-generic:/usr/local/sbin:..."` + +#### Debian + +1. If you use debian for your WSL distro, use this command instead: + +```bash +sudo apt install usbip hwdata usbutils +``` + +And that's all for installation and set up. + +### To connect your calculator + +1. Open an Admin powershell and type: + +```powershell + usbipd wsl list +``` + +This will list your usb devices connected. Look at the BUSID column and remember the one for your calculator (it should be called "Numworks Calculator"). +2. Now run this command replacing `` by your calculator's usb port id: + +```powershell +usbipd wsl attach --busid +``` + +It will ask you to type your wsl's password and will connect your calculator to WSL. + +You can now go to step 2! + +
+ +
+ +
+ +### 2. Set up repo + +Clone repo and use 'upsilon-dev' branch by pasting these two commands: + +```bash +git clone --recursive https://github.com/UpsilonNumworks/Upsilon.git +cd Upsilon +git checkout upsilon-dev +``` + +
+ +### 3. Choose the target + +
+ +Model n0100 (note: you can change the `EPSILON_I18N=en` flag to `fr`, `nl`, `pt`, `it`, `de`, `es` or `hu`). ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega -git checkout omega-master make MODEL=n0100 clean -make MODEL=n0100 EPSILON_I18N=en OMEGA_USERNAME="{Your name, max 15 characters}" -j4 +make MODEL=n0100 EPSILON_I18N=en OMEGA_USERNAME="{Your name, max 15 characters}" -j(nproc) +``` + +Now, run either: + +```bash make MODEL=n0100 epsilon_flash ``` -Important: Don't forget the `--recursive` tag, because Omega relies on submodules. -Also, you can change the number of processes that run in parallel during the build by changing the value of the `-j` flag. - +to directly flash the calculator after pressing simultaneously `reset` and `6` buttons and plugging in. + +
+ +or: + +```bash +make MODEL=n0100 OMEGA_USERNAME="" binpack -j(nproc) +``` + +to make binpack which you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). Binpacks are a great way to share a custom build of Upsilonto friends. +
- Model n0110 + +Model n0110 + +The bootloader allows you to install 2 firmware in separated "slots". If so, external apps won't have all the space but half. Bootloader will allow use of all of the memory if only one slot is flashed. In legacy mode, external apps use all the space available. + +
+Bootloader + +Your calculator must already have been flashed with [Upsilon](https://getupsilon.web.app)'s or [Omega](https://getomega.dev)'s bootloader. +Then, build with: ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega -git checkout omega-master make clean -make OMEGA_USERNAME="{Your name, max 15 characters}" -j4 -make epsilon_flash +make OMEGA_USERNAME="{Your name, max 15 characters}" -j(nproc) ``` -Important: Don't forget the `--recursive` tag, because Omega relies on submodules. -Also, you can change the number of processes that run in parallel during the build by changing the value of the `-j` flag. - +Now, run either: + +```bash +make epsilon.A_flash +``` + +to directly flash the calculator into the current slot, or thought bootloader's slot flasher with RESET, then 4 (flash), and 1 (flash slots) for other slots. + +
+ +or: + +```bash +make OMEGA_USERNAME="" binpack -j(nproc) +``` + +to make binpack which you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0110/). You'll find them at `output/release/device/bootloader/`. Binpacks are a great way to share a custom build of Upsilon to friends.
+ +
- Bin files - -These can be used to distribute Omega (so that it can be flashed by anyone with [Webdfu_Numworks](https://ti-planet.github.io/webdfu_numworks/)). +Model N0110 legacy (deprecated, use bootloader instead for Epsilon protection) ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega -git checkout omega-master +make MODEL=n0110 clean +make MODEL=n0110 OMEGA_USERNAME="{Your name, max 15 characters}" -j(nproc) +``` + +Now, run either: + +```bash +make MODEL=n0110 epsilon_flash +``` + +to directly flash the calculator after pressing simultaneously `reset` and `6` buttons and plugging in. + +
+ +or: + +```bash +make MODEL=n0110 OMEGA_USERNAME="" binpack -j(nproc) +``` + +to make binpack which you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0110/). You'll find them at `output/release/device/bootloader/`. Binpacks are a great way to share a custom build of Upsilon to friends. +
+ +
+ + +
+ +Native simulator + +Run this command: +```bash make clean -make MODEL=n0100 OMEGA_USERNAME="" -j8 -make MODEL=n0100 OMEGA_USERNAME="" binpack -j8 -make OMEGA_USERNAME="" -j8 -make OMEGA_USERNAME="" binpack -j8 +``` +You can either build using the following command that will automatically detect your platform: +```bash +make PLATFORM=simulator +``` +or, choose the command corresponding to your platform: +```bash +make PLATFORM=simulator TARGET=android +make PLATFORM=simulator TARGET=ios +make PLATFORM=simulator TARGET=macos +make PLATFORM=simulator TARGET=web +make PLATFORM=simulator TARGET=windows +make PLATFORM=simulator TARGET=3ds ``` -Important: Don't forget the `--recursive` tag, because Omega relies on submodules. -Also, you can change the number of processes that run in parallel during the build by changing the value of the `-j` flag. - +You'll find simulator files in `output/release/simulator/`. +
+
- Web simulator - + +Web simulator + First, install emsdk : ```bash git clone https://github.com/emscripten-core/emsdk.git cd emsdk -./emsdk install latest-fastcomp -./emsdk activate latest-fastcomp +./emsdk install 1.40.1 +./emsdk activate 1.40.1 source emsdk_env.sh ``` -Then, compile Omega : +Then, compile Upsilon : ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega -git checkout omega-master make clean -make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Your name, max 15 characters}" -j4 +make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Your name, max 15 characters}" -j$(nproc) ``` The simulator is now in `output/release/simulator/web/simulator.zip` -Important: Don't forget the `--recursive` tag, because Omega relies on submodules. -Also, you can change the number of processes that run in parallel during the build by changing the value of the `-j` flag. -
- 3DS Simulator - + +3DS Simulator + You need devkitPro and devkitARM installed and in your path (instructions [here](https://devkitpro.org/wiki/Getting_Started)) ```bash -git clone --recursive https://github.com/Omega-Numworks/Omega.git -cd Omega -git checkout --recursive omega-dev -make PLATFORM=simulator TARGET=3ds -j +git clone --recursive https://github.com/UpsilonNumworks/Upsilon.git +cd Upsilon +git checkout upsilon-dev +make PLATFORM=simulator TARGET=3ds -j(nproc) ``` + You can then put epsilon.3dsx on a SD card to run it from the HBC or use 3dslink to launch it over the network: ```bash @@ -140,28 +428,62 @@ You can then put epsilon.3dsx on a SD card to run it from the HBC or use 3dslink
-If you need help, you can join our Discord server here : https://discord.gg/X2TWhh9 +
+ +
+ Casio fx-CG-series Port + +First, install gint and fxsdk along with a cross compiler for the calculator. There are instructions for this (in French, but Google Translate works well enough) [here](https://www.planet-casio.com/Fr/forums/topic16614-last-giteapc-installer-et-mettre-a-jour-automatiquement-des-projets-gitea.html). + +Next: +```bash +git clone --recursive https://github.com/UpsilonNumworks/Upsilon.git +cd Omega +git checkout upsilon-dev +make PLATFORM=simulator TARGET=fxcg -j$(nproc) +``` +Then copy the file at `./output/release/simulator/fxcg/epsilon.g3a` to the calculator over USB. + +
+ +Important: Don't forget the `--recursive` tag, because Upsilon relies on submodules. +Also, you can change the number of processes that run in parallel during the build by changing the value of the `-j` flag. +Don't forget to put your pseudo instead of `{your pseudo, max 15 char}`. If you don't want one, just remove the `OMEGA_USERNAME=""` argument. + +
+ +If you need help, you can join our Discord server here : + +

Omega Banner Discord

-

Omega Banner Discord

--- +## Useful links + +- [Upsilon external (to install additional apps and wallpapers)](https://upsilonnumworks.github.io/Upsilon-External/) +- [Ulab documentation](https://micropython-ulab.readthedocs.io/en/latest/) + ## Contributing -To contribute, please refer to the [Wiki](https://github.com/Omega-Numworks/Omega/wiki/Contributing) +To contribute, please refer to [Omega's Wiki](https://github.com/Omega-Numworks/Omega/wiki/Contributing), the same rules apply here. ## Related repositories -* [Omega Themes](https://github.com/Omega-Numworks/Omega-Themes) -* [Omega Website](https://github.com/Omega-Numworks/Omega-Website) -* [Omega RPN `APP`](https://github.com/Omega-Numworks/Omega-RPN) -* [Omega Atomic `APP`](https://github.com/Omega-Numworks/Omega-Atomic) -* [Omega Design](https://github.com/Omega-Numworks/Omega-Design) -* [Omega Discord Bot](https://github.com/Omega-Numworks/Omega-Discord-Bot) -* [Omega App Template `BETA`](https://github.com/Omega-Numworks/Omega-App-Template) -* [External Apps](https://github.com/Omega-Numworks/External-Apps) +Here are the main links toward Omega's different websites and repositories, that have been used for the creation of Upsilon. + +- [Omega Themes](https://github.com/Omega-Numworks/Omega-Themes) +- [Omega Website](https://github.com/Omega-Numworks/Omega-Website) +- [Omega RPN `APP`](https://github.com/Omega-Numworks/Omega-RPN) +- [Omega Atomic `APP`](https://github.com/Omega-Numworks/Omega-Atomic) +- [Omega Design](https://github.com/Omega-Numworks/Omega-Design) +- [Omega Discord Bot](https://github.com/Omega-Numworks/Omega-Discord-Bot) +- [Omega App Template `BETA`](https://github.com/Omega-Numworks/Omega-App-Template) +- [External Apps](https://github.com/Omega-Numworks/External-Apps) ## About Epsilon +Upsilon is a fork of Omega, after the project's discontinuation. + Omega is a fork of Epsilon, a high-performance graphing calculator operating system. It includes eight apps that cover the high school mathematics curriculum. You can try Epsilon straight from your browser in the [online simulator](https://www.numworks.com/simulator/). @@ -170,7 +492,9 @@ You can try Epsilon straight from your browser in the [online simulator](https:/ NumWorks is a registered trademark of NumWorks SAS, 24 Rue Godot de Mauroy, 75009 Paris, France. Nintendo and Nintendo 3DS are registered trademarks of Nintendo of America Inc, 4600 150th Ave NE, Redmond, WA 98052, USA. -NumWorks SAS and Nintendo of America Inc aren't associated in any shape or form with this project. +Casio is a registered trademark of Casio Computer Co., Ltd. CORPORATION JAPAN 6-2, Hon-machi 1-chome Shibuya-ku, Tokyo JAPAN 151-8543. +NumWorks SAS, Nintendo of America Inc and Casio aren't associated in any shape or form with this project. -* NumWorks Epsilon is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). -* Omega is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). +- NumWorks Epsilon is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). +- Omega is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). +- Upsilon is released under a [CC BY-NC-SA License](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode). diff --git a/apps/Makefile b/apps/Makefile index 820ff690ce3..b2e67a83283 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -23,7 +23,7 @@ ifneq ($(strip $(apps_missing)),) $(foreach i, $(SUBMODULES_APPS), $(if $(call app_equals, $(filter $(i), $(apps_missing)), $(i)), $(eval miss_modules=1))) ifeq ($(miss_modules), 1) - PLS_IGNORE := $(shell >&2 printf "\nSome submodules apps seem to be missing. To download them, assumming you git clone'd the repo, do\n") + PLS_IGNORE := $(shell >&2 printf "\nSome submodules apps seem to be missing. To download them, assuming you git clone'd the repo, do\n") PLS_IGNORE := $(shell >&2 printf " git submodule init\n") PLS_IGNORE := $(shell >&2 printf " git submodule update\n\n") endif @@ -50,6 +50,7 @@ apps_src += $(addprefix apps/,\ exam_mode_configuration_official.cpp:+official \ exam_mode_configuration_non_official.cpp:-official \ global_preferences.cpp \ + host_filemanager.cpp \ i18n.py \ lock_view.cpp \ main.cpp \ @@ -58,7 +59,9 @@ apps_src += $(addprefix apps/,\ math_variable_box_empty_controller.cpp \ shift_alpha_lock_view.cpp \ suspend_timer.cpp \ + timer_manager.cpp \ title_bar_view.cpp \ + xnt_loop.cpp \ ) tests_src += $(addprefix apps/,\ @@ -81,7 +84,7 @@ $(call object_for,apps/apps_container_storage.cpp apps/apps_container.cpp apps/m country_preferences = apps/country_preferences.csv language_preferences = apps/language_preferences.csv -# The header is refered to as so make sure it's findable this way +# The header is referred to as so make sure it's findable this way SFLAGS += -I$(BUILD_DIR) i18n_files += $(addprefix apps/language_,$(addsuffix .universal.i18n, $(EPSILON_I18N))) @@ -117,6 +120,7 @@ apps_tests_src = $(app_calculation_test_src) $(app_code_test_src) $(app_graph_te apps_tests_src += $(addprefix apps/,\ alternate_empty_nested_menu_controller.cpp \ global_preferences.cpp \ + dummy_timer_manager.cpp \ ) ifeq ($(THEME_REPO),local) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index acf3feb383d..b01c388e204 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -29,7 +29,7 @@ AppsContainer::AppsContainer() : m_globalContext(), m_variableBoxController(), m_examPopUpController(this), - m_promptController(k_promptMessages, k_promptColors, k_promptNumberOfMessages), + m_promptController(k_promptMessages, k_promptFGColors, k_promptBGColors, k_promptNumberOfMessages), m_batteryTimer(), m_suspendTimer(), m_backlightDimmingTimer(), @@ -37,7 +37,8 @@ AppsContainer::AppsContainer() : m_homeSnapshot(), m_onBoardingSnapshot(), m_hardwareTestSnapshot(), - m_usbConnectedSnapshot() + m_usbConnectedSnapshot(), + m_startAppSnapshot() { m_emptyBatteryWindow.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height), false); // #if __EMSCRIPTEN__ @@ -47,7 +48,7 @@ AppsContainer::AppsContainer() : * poincareCircuitBreaker is run. This means either whitelisting all Epsilon * (which makes bigger files to download and slower execution), or * whitelisting all the symbols (that's a big amount of symbols to find and - * quite painy to maintain). + * quite paint to maintain). * We just remove the circuit breaker for now. * TODO: Put the Poincare circuit breaker back on epsilon's web emulator */ @@ -58,6 +59,11 @@ AppsContainer::AppsContainer() : Poincare::Expression::SetCircuitBreaker(AppsContainer::poincareCircuitBreaker); // #endif Ion::Storage::sharedStorage()->setDelegate(this); + + addTimer(&m_batteryTimer); + addTimer(&m_suspendTimer); + addTimer(&m_backlightDimmingTimer); + addTimer(&m_clockTimer); } bool AppsContainer::poincareCircuitBreaker() { @@ -146,8 +152,11 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { * We do it before switching to USB application to redraw the battery * pictogram. */ updateBatteryState(); - if (switchTo(usbConnectedAppSnapshot())) { - Ion::USB::DFU(); + if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { + // If we are in exam mode, we don't switch to usb connected app + didProcessEvent = true; + } else if (switchTo(usbConnectedAppSnapshot())) { + Ion::USB::DFU(true); // Update LED when exiting DFU mode Ion::LED::updateColorWithPlugAndCharge(); bool switched = switchTo(activeSnapshot); @@ -220,6 +229,7 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { return didProcessEvent || alphaLockWantsRedraw; } +// List of keys that are used to switch between apps, in order of app to go (eg. 0 : First App, 1 : Second App, 2 : Third App, ...) static constexpr Ion::Events::Event switch_events[] = { Ion::Events::ShiftSeven, Ion::Events::ShiftEight, Ion::Events::ShiftNine, Ion::Events::ShiftFour, Ion::Events::ShiftFive, Ion::Events::ShiftSix, @@ -231,27 +241,33 @@ 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 the exam mode is enabled, we ask to disable it, else, we enable USB if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { displayExamModePopUp(GlobalPreferences::ExamMode::Off); window()->redraw(); } else { Ion::USB::enable(); } + // Update brightness when USB is plugged Ion::Backlight::setBrightness(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()); } else { + // If the USB isn't plugged in USBPlug event, we disable USB Ion::USB::disable(); } return true; } + // If key home or key back is pressed, we switch to the home app. if (event == Ion::Events::Home || event == Ion::Events::Back) { switchTo(appSnapshotAtIndex(0)); return true; } + // If shift + Home are pressed, we switch to the first app. if (event == Ion::Events::ShiftHome) { switchTo(appSnapshotAtIndex(1)); return true; } + // Iterate through the switch events to find the one that matches the event, if one match, switch to the app at the index of the switch event. for(int i = 0; i < std::min((int) (sizeof(switch_events) / sizeof(Ion::Events::Event)), APPS_CONTAINER_SNAPSHOT_COUNT); i++) { if (event == switch_events[i]) { m_window.redraw(true); @@ -260,19 +276,44 @@ bool AppsContainer::processEvent(Ion::Events::Event event) { } } + // Add EE shortcut to go to the settings (app number 12) + if (event == Ion::Events::EE) { + switchTo(appSnapshotAtIndex(12)); + return true; + } + + // Add Shift + Ans shortcut to go to the previous app + if (event == Ion::Events::ShiftAns) { + switchTo(appSnapshotAtIndex(m_lastAppIndex)); + return true; + } + + // If the event is the OnOff key, we suspend the calculator. if (event == Ion::Events::OnOff) { suspend(true); return true; } + // If the event is a brightness event, we update the brightness according to the event. if (event == Ion::Events::BrightnessPlus || event == Ion::Events::BrightnessMinus) { int delta = Ion::Backlight::MaxBrightness/GlobalPreferences::NumberOfBrightnessStates; - int direction = (event == Ion::Events::BrightnessPlus) ? Ion::Backlight::NumberOfStepsPerShortcut*delta : -delta*Ion::Backlight::NumberOfStepsPerShortcut; + int NumberOfStepsPerShortcut = GlobalPreferences::sharedGlobalPreferences()->brightnessShortcut(); + int direction = (event == Ion::Events::BrightnessPlus) ? NumberOfStepsPerShortcut*delta : -delta*NumberOfStepsPerShortcut; GlobalPreferences::sharedGlobalPreferences()->setBrightnessLevel(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()+direction); } + // Else, the event was not processed. return false; } bool AppsContainer::switchTo(App::Snapshot * snapshot) { + // Get app index of the snapshot + int m_appIndexToSwitch = appIndexFromSnapshot(snapshot); + // If the app is home, skip app index saving + if (m_appIndexToSwitch != 0) { + // Save last app index + m_lastAppIndex = m_currentAppIndex; + // Save current app index + m_currentAppIndex = m_appIndexToSwitch; + } if (s_activeApp && snapshot != s_activeApp->snapshot()) { resetShiftAlphaStatus(); } @@ -309,7 +350,13 @@ 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 = switchTo(initialAppSnapshot()); + bool switched; + if (m_startAppSnapshot != nullptr) { + switched = switchTo(m_startAppSnapshot); + } else { + switched = switchTo(initialAppSnapshot()); + } + assert(switched); (void) switched; // Silence compilation warning about unused variable. } else { @@ -319,7 +366,7 @@ void AppsContainer::run() { * destroyed from the pool. To avoid using them before packing the app * (in App::willBecomeInactive for instance), we tidy them early on. */ s_activeApp->snapshot()->tidy(); - /* When an app encoutered an exception due to a full pool, the next time + /* When an app encountered 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 * app causing the issue, we reset its snapshot when leaving it due to @@ -353,6 +400,8 @@ bool AppsContainer::updateBatteryState() { } void AppsContainer::refreshPreferences() { + m_suspendTimer.reset(GlobalPreferences::sharedGlobalPreferences()->idleBeforeSuspendSeconds()*1000/Timer::TickDuration); + m_backlightDimmingTimer.reset(GlobalPreferences::sharedGlobalPreferences()->idleBeforeDimmingSeconds()*1000/Timer::TickDuration); m_window.refreshPreferences(); } @@ -446,15 +495,6 @@ Window * AppsContainer::window() { return &m_window; } -int AppsContainer::numberOfContainerTimers() { - return 4; -} - -Timer * AppsContainer::containerTimerAtIndex(int i) { - Timer * timers[4] = {&m_batteryTimer, &m_suspendTimer, &m_backlightDimmingTimer, &m_clockTimer}; - return timers[i]; -} - void AppsContainer::resetShiftAlphaStatus() { Ion::Events::setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default); updateAlphaLock(); diff --git a/apps/apps_container.h b/apps/apps_container.h index ab945b6efec..485c5ce3b2d 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -18,6 +18,7 @@ #include "shared/global_context.h" #include "clock_timer.h" #include "on_boarding/prompt_controller.h" +#include "xnt_loop.h" #include @@ -28,6 +29,7 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag static bool poincareCircuitBreaker(); virtual int numberOfApps() = 0; virtual App::Snapshot * appSnapshotAtIndex(int index) = 0; + virtual int appIndexFromSnapshot(App::Snapshot * snapshot) = 0; App::Snapshot * initialAppSnapshot(); App::Snapshot * hardwareTestAppSnapshot(); App::Snapshot * onBoardingAppSnapshot(); @@ -47,6 +49,8 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag void displayExamModePopUp(GlobalPreferences::ExamMode mode); void shutdownDueToLowBattery(); void setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus newStatus); + CodePoint XNT(CodePoint defaultXNT, bool * shouldRemoveLastCharacter) { return m_XNTLoop.XNT(defaultXNT, shouldRemoveLastCharacter); } + void resetXNT() { m_XNTLoop.reset(); } OnBoarding::PromptController * promptController(); void redrawWindow(bool force = false); void activateExamMode(GlobalPreferences::ExamMode examMode); @@ -55,19 +59,23 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag // Ion::StorageDelegate void storageDidChangeForRecord(const Ion::Storage::Record record) override; void storageIsFull() override; + #ifdef EPSILON_GETOPT + void setStartApp(App::Snapshot * snapshot) { m_startAppSnapshot = snapshot; } + #endif protected: Home::App::Snapshot * homeAppSnapshot() { return &m_homeSnapshot; } private: Window * window() override; - int numberOfContainerTimers() override; - Timer * containerTimerAtIndex(int i) override; bool processEvent(Ion::Events::Event event); void resetShiftAlphaStatus(); bool updateAlphaLock(); static I18n::Message k_promptMessages[]; - static KDColor k_promptColors[]; + static KDColor k_promptFGColors[]; + static KDColor k_promptBGColors[]; static int k_promptNumberOfMessages; + int m_currentAppIndex; // Home isn't included after the second app switching + int m_lastAppIndex; AppsWindow m_window; EmptyBatteryWindow m_emptyBatteryWindow; Shared::GlobalContext m_globalContext; @@ -83,6 +91,10 @@ class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::Storag OnBoarding::App::Snapshot m_onBoardingSnapshot; HardwareTest::App::Snapshot m_hardwareTestSnapshot; USB::App::Snapshot m_usbConnectedSnapshot; + XNTLoop m_XNTLoop; + #ifdef EPSILON_GETOPT + App::Snapshot * m_startAppSnapshot; + #endif }; #endif diff --git a/apps/apps_container_prompt_beta.cpp b/apps/apps_container_prompt_beta.cpp index 613acc99d9a..729062c6ac5 100644 --- a/apps/apps_container_prompt_beta.cpp +++ b/apps/apps_container_prompt_beta.cpp @@ -10,7 +10,7 @@ I18n::Message AppsContainer::k_promptMessages[] = { I18n::Message::BetaVersionMessage5, I18n::Message::BetaVersionMessage6}; -KDColor AppsContainer::k_promptColors[] = { +KDColor AppsContainer::k_promptFGColors[] = { KDColorBlack, KDColorBlack, KDColorBlack, @@ -20,4 +20,14 @@ KDColor AppsContainer::k_promptColors[] = { KDColorBlack, Palette::AccentText}; +KDColor AppsContainer::k_promptBGColors[] = { + KDColorWhite, + KDColorWhite, + KDColorWhite, + KDColorWhite, + KDColorWhite, + KDColorWhite, + KDColorWhite, + KDColorWhite}; + int AppsContainer::k_promptNumberOfMessages = 8; diff --git a/apps/apps_container_prompt_none.cpp b/apps/apps_container_prompt_none.cpp index 65556d670f8..e750df2b65d 100644 --- a/apps/apps_container_prompt_none.cpp +++ b/apps/apps_container_prompt_none.cpp @@ -2,7 +2,8 @@ I18n::Message AppsContainer::k_promptMessages[] = {}; -KDColor AppsContainer::k_promptColors[] = {}; +KDColor AppsContainer::k_promptFGColors[] = {}; +KDColor AppsContainer::k_promptBGColors[] = {}; int AppsContainer::k_promptNumberOfMessages = 0; diff --git a/apps/apps_container_prompt_update.cpp b/apps/apps_container_prompt_update.cpp index e837b592f27..59c93f0f478 100644 --- a/apps/apps_container_prompt_update.cpp +++ b/apps/apps_container_prompt_update.cpp @@ -8,7 +8,7 @@ I18n::Message AppsContainer::k_promptMessages[] = { I18n::Message::UpdateMessage3, I18n::Message::UpdateMessage4}; -KDColor AppsContainer::k_promptColors[] = { +KDColor AppsContainer::k_promptFGColors[] = { KDColorBlack, KDColorBlack, KDColorBlack, @@ -16,4 +16,12 @@ KDColor AppsContainer::k_promptColors[] = { KDColorBlack, Palette::AccentText}; +KDColor AppsContainer::k_promptBGColors[] = { + KDColorWhite, + KDColorWhite, + KDColorWhite, + KDColorBlack, + KDColorWhite, + KDColorBlack}; + int AppsContainer::k_promptNumberOfMessages = 6; diff --git a/apps/apps_container_storage.cpp b/apps/apps_container_storage.cpp index 2ef60aafa5c..6a945af111e 100644 --- a/apps/apps_container_storage.cpp +++ b/apps/apps_container_storage.cpp @@ -34,5 +34,25 @@ App::Snapshot * AppsContainerStorage::appSnapshotAtIndex(int index) { }; assert(sizeof(snapshots)/sizeof(snapshots[0]) == k_numberOfCommonApps); assert(index >= 0 && index < k_numberOfCommonApps); + // To avoid crashes, we return the home app snapshot if the index is out of + // bounds. (no crash in release mode, but an assert in debug mode) + if (index >= k_numberOfCommonApps) { + return snapshots[0]; + } return snapshots[index]; } + +// Get the index of the app from its snapshot +int AppsContainerStorage::appIndexFromSnapshot(App::Snapshot * snapshot) { + App::Snapshot * snapshots[] = { + homeAppSnapshot() + APPS_CONTAINER_SNAPSHOT_LIST + }; + assert(sizeof(snapshots)/sizeof(snapshots[0]) == k_numberOfCommonApps); + for (int i = 0; i < k_numberOfCommonApps; i++) { + if (snapshots[i] == snapshot) { + return i; + } + } + return NULL; +} diff --git a/apps/apps_container_storage.h b/apps/apps_container_storage.h index 9abd3c27ad4..c3191a751da 100644 --- a/apps/apps_container_storage.h +++ b/apps/apps_container_storage.h @@ -12,6 +12,7 @@ class AppsContainerStorage : public AppsContainer { AppsContainerStorage(); int numberOfApps() override; App::Snapshot * appSnapshotAtIndex(int index) override; + int appIndexFromSnapshot(App::Snapshot * snapshot) override; void * currentAppBuffer() override { return &m_apps; }; private: union Apps { diff --git a/apps/atomic b/apps/atomic index 64f2e38ed1e..5f7063d4474 160000 --- a/apps/atomic +++ b/apps/atomic @@ -1 +1 @@ -Subproject commit 64f2e38ed1e4b8a896b0488039fb866b7015ff3f +Subproject commit 5f7063d447414028a3c4b750cd6dbe83bd6296e6 diff --git a/apps/backlight_dimming_timer.cpp b/apps/backlight_dimming_timer.cpp index 2f60e809be5..a86c29c0080 100644 --- a/apps/backlight_dimming_timer.cpp +++ b/apps/backlight_dimming_timer.cpp @@ -1,11 +1,26 @@ #include "backlight_dimming_timer.h" +#include "global_preferences.h" +#include +#include +#include BacklightDimmingTimer::BacklightDimmingTimer() : - Timer(k_idleBeforeDimmingDuration/Timer::TickDuration) + Timer(GlobalPreferences::sharedGlobalPreferences()->idleBeforeDimmingSeconds()*1000/Timer::TickDuration) { } -bool BacklightDimmingTimer::fire() { - Ion::Backlight::setBrightness(k_dimBacklightBrightness); +bool BacklightDimmingTimer::fire(){ + int i = Ion::Backlight::brightness(); + while (i > 0){ + int t = 20; + Ion::Events::Event e = Ion::Events::getEvent(&t); + AppsContainer::sharedAppsContainer()->dispatchEvent(e); + if (e.isKeyboardEvent()){ + return false; + } + + Ion::Backlight::setBrightness(i); + i -= 15; + } return false; } diff --git a/apps/backlight_dimming_timer.h b/apps/backlight_dimming_timer.h index 17059e408de..1925913705d 100644 --- a/apps/backlight_dimming_timer.h +++ b/apps/backlight_dimming_timer.h @@ -7,8 +7,6 @@ class BacklightDimmingTimer : public Timer { public: BacklightDimmingTimer(); private: - constexpr static int k_idleBeforeDimmingDuration = 30*1000; // In miliseconds - constexpr static int k_dimBacklightBrightness = 0; bool fire() override; }; diff --git a/apps/battery_view.cpp b/apps/battery_view.cpp index 28ab8d9794e..c70413b9f98 100644 --- a/apps/battery_view.cpp +++ b/apps/battery_view.cpp @@ -60,32 +60,36 @@ void BatteryView::drawRect(KDContext * ctx, KDRect rect) const { *'content' depends on the charge */ // Draw the left part - ctx->fillRect(KDRect(0, 0, k_elementWidth, k_batteryHeight), Palette::Battery); + ctx->fillRect(KDRect(0, 1, k_elementWidth, k_batteryHeight - 2), Palette::Battery); + + // Draw top and bottom part + ctx->fillRect(KDRect(1, 0, k_batteryWidth-3, 1), Palette::Battery); + ctx->fillRect(KDRect(1, k_batteryHeight-1, k_batteryWidth-3, 1), Palette::Battery); // Draw the middle part constexpr KDCoordinate batteryInsideX = k_elementWidth+k_separatorThickness; constexpr KDCoordinate batteryInsideWidth = k_batteryWidth-3*k_elementWidth-2*k_separatorThickness; if (m_isCharging) { // Charging: Yellow background with flash - ctx->fillRect(KDRect(batteryInsideX, 0, batteryInsideWidth, k_batteryHeight), Palette::BatteryInCharge); + ctx->fillRect(KDRect(batteryInsideX, 2, batteryInsideWidth, k_batteryHeight-4), Palette::BatteryInCharge); KDRect frame((k_batteryWidth-k_flashWidth)/2, 0, k_flashWidth, k_flashHeight); KDColor flashWorkingBuffer[BatteryView::k_flashHeight*BatteryView::k_flashWidth]; ctx->blendRectWithMask(frame, Palette::Battery, (const uint8_t *)flashMask, flashWorkingBuffer); } else if (m_chargeState == Ion::Battery::Charge::LOW) { assert(!m_isPlugged); // Low: Quite empty battery - ctx->fillRect(KDRect(batteryInsideX, 0, 2*k_elementWidth, k_batteryHeight), Palette::BatteryLow); - ctx->fillRect(KDRect(3*k_elementWidth+k_separatorThickness, 0, k_batteryWidth-5*k_elementWidth-2*k_separatorThickness, k_batteryHeight), Palette::BatteryInCharge); + ctx->fillRect(KDRect(batteryInsideX, 2, 2*k_elementWidth, k_batteryHeight-4), Palette::BatteryLow); + ctx->fillRect(KDRect(3*k_elementWidth+k_separatorThickness, 2, k_batteryWidth-5*k_elementWidth-2*k_separatorThickness, k_batteryHeight-4), KDColor::blend(Palette::Toolbar, Palette::Battery, 128)); } else if (m_chargeState == Ion::Battery::Charge::SOMEWHERE_INBETWEEN) { assert(!m_isPlugged); // Middle: Half full battery constexpr KDCoordinate middleChargeWidth = batteryInsideWidth/2; - ctx->fillRect(KDRect(batteryInsideX, 0, middleChargeWidth, k_batteryHeight), Palette::Battery); - ctx->fillRect(KDRect(batteryInsideX+middleChargeWidth, 0, middleChargeWidth, k_batteryHeight), Palette::BatteryInCharge); + ctx->fillRect(KDRect(batteryInsideX, 2, middleChargeWidth, k_batteryHeight-4), Palette::Battery); + ctx->fillRect(KDRect(batteryInsideX+middleChargeWidth, 2, middleChargeWidth+1, k_batteryHeight-4), KDColor::blend(Palette::Toolbar, Palette::Battery, 128)); } else { assert(m_chargeState == Ion::Battery::Charge::FULL); // Full but not plugged: Full battery - ctx->fillRect(KDRect(batteryInsideX, 0, batteryInsideWidth, k_batteryHeight), Palette::Battery); + ctx->fillRect(KDRect(batteryInsideX, 2, batteryInsideWidth, k_batteryHeight-4), Palette::Battery); if (m_isPlugged) { // Plugged and full: Full battery with tick KDRect frame((k_batteryWidth-k_tickWidth)/2, (k_batteryHeight-k_tickHeight)/2, k_tickWidth, k_tickHeight); @@ -95,7 +99,7 @@ void BatteryView::drawRect(KDContext * ctx, KDRect rect) const { } // Draw the right part - ctx->fillRect(KDRect(k_batteryWidth-2*k_elementWidth, 0, k_elementWidth, k_batteryHeight), Palette::Battery); + ctx->fillRect(KDRect(k_batteryWidth-2*k_elementWidth, 1, k_elementWidth, k_batteryHeight-2), Palette::Battery); ctx->fillRect(KDRect(k_batteryWidth-k_elementWidth, (k_batteryHeight-k_capHeight)/2, k_elementWidth, k_capHeight), Palette::Battery); } diff --git a/apps/battery_view.h b/apps/battery_view.h index 5c1d373aa74..7d91fa929b4 100644 --- a/apps/battery_view.h +++ b/apps/battery_view.h @@ -20,10 +20,10 @@ class BatteryView : public TransparentView { constexpr static int k_tickHeight = 6; constexpr static int k_tickWidth = 8; private: - constexpr static KDCoordinate k_batteryHeight = 8; - constexpr static KDCoordinate k_batteryWidth = 15; + constexpr static KDCoordinate k_batteryHeight = 9; + constexpr static KDCoordinate k_batteryWidth = 16; constexpr static KDCoordinate k_elementWidth = 1; - constexpr static KDCoordinate k_capHeight = 4; + constexpr static KDCoordinate k_capHeight = 3; constexpr static KDCoordinate k_separatorThickness = Metric::CellSeparatorThickness; Ion::Battery::Charge m_chargeState; bool m_isCharging; diff --git a/apps/calculation/Makefile b/apps/calculation/Makefile index 43f8f92c3fd..a75d97dd85e 100644 --- a/apps/calculation/Makefile +++ b/apps/calculation/Makefile @@ -19,6 +19,7 @@ app_calculation_src = $(addprefix apps/calculation/,\ additional_outputs/list_controller.cpp \ additional_outputs/matrix_list_controller.cpp \ additional_outputs/rational_list_controller.cpp \ + additional_outputs/second_degree_list_controller.cpp \ additional_outputs/trigonometry_graph_cell.cpp \ additional_outputs/trigonometry_list_controller.cpp \ additional_outputs/trigonometry_model.cpp \ diff --git a/apps/calculation/additional_outputs/second_degree_list_controller.cpp b/apps/calculation/additional_outputs/second_degree_list_controller.cpp new file mode 100644 index 00000000000..afd9f1efa9d --- /dev/null +++ b/apps/calculation/additional_outputs/second_degree_list_controller.cpp @@ -0,0 +1,320 @@ +#include "../app.h" +#include +#include "../../shared/poincare_helpers.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "second_degree_list_controller.h" + +using namespace Poincare; +using namespace Shared; + +namespace Calculation { + +void SecondDegreeListController::setExpression(Poincare::Expression e) { + ExpressionsListController::setExpression(e); + assert(!m_expression.isUninitialized()); + + Expression polynomialCoefficients[Expression::k_maxNumberOfPolynomialCoefficients]; + + Context * context = App::app()->localContext(); + Preferences * preferences = Preferences::sharedPreferences(); + Poincare::ExpressionNode::ReductionContext reductionContext = Poincare::ExpressionNode::ReductionContext(context, + preferences->complexFormat(), preferences->angleUnit(), + GlobalPreferences::sharedGlobalPreferences()->unitFormat(), + ExpressionNode::ReductionTarget::SystemForApproximation, + ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, + Poincare::ExpressionNode::UnitConversion::Default); + + PoincareHelpers::Reduce(&m_expression, context, ExpressionNode::ReductionTarget::SystemForAnalysis); + + int degree = m_expression.getPolynomialReducedCoefficients( + "x", + polynomialCoefficients, + context, + Expression::UpdatedComplexFormatWithExpressionInput(preferences->complexFormat(), m_expression, context), + preferences->angleUnit(), + GlobalPreferences::sharedGlobalPreferences()->unitFormat(), + ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition); + + assert(degree == 2); + + Expression a = polynomialCoefficients[2]; + Expression b = polynomialCoefficients[1]; + Expression c = polynomialCoefficients[0]; + + Expression delta = Subtraction::Builder(Power::Builder(b.clone(), Rational::Builder(2)), Multiplication::Builder(Rational::Builder(4), a.clone(), c.clone())); + PoincareHelpers::Simplify(&delta, context, ExpressionNode::ReductionTarget::SystemForApproximation); + + // Alpha is -b/2a, but because after we use -α, we immediately store -α=-(-b/2a)=b/2a. + Expression minusAlpha = Division::Builder(b.clone(), Multiplication::Builder(Rational::Builder(2), a.clone())); + PoincareHelpers::Reduce(&minusAlpha, context, ExpressionNode::ReductionTarget::SystemForApproximation); + + // Same thing for β + Expression minusBeta = Division::Builder(delta.clone(), Multiplication::Builder(Rational::Builder(4), a.clone())); + PoincareHelpers::Reduce(&minusBeta, context, ExpressionNode::ReductionTarget::SystemForApproximation); + + enum MultiplicationTypeForA { + Nothing, + Minus, + Parenthesis, + Normal + }; + + MultiplicationTypeForA multiplicationTypeForA; + + if (a.type() == ExpressionNode::Type::Rational && static_cast(a).isOne()) { + multiplicationTypeForA = MultiplicationTypeForA::Nothing; + } else if(a.type() == ExpressionNode::Type::Rational && static_cast(a).isMinusOne()){ + multiplicationTypeForA = MultiplicationTypeForA::Minus; + } else if (a.type() == ExpressionNode::Type::Addition) { + multiplicationTypeForA = MultiplicationTypeForA::Parenthesis; + } else { + multiplicationTypeForA = MultiplicationTypeForA::Normal; + } + + PoincareHelpers::Simplify(&a, context, ExpressionNode::ReductionTarget::User); + + /* + * Because when can't apply reduce or simplify to keep the + * canonized form we must beautify the expression manually + */ + + Expression xMinusAlphaPowerTwo; + Expression alpha = getOppositeIfExists(minusAlpha, &reductionContext); + + if (alpha.isUninitialized()) { + PoincareHelpers::Simplify(&minusAlpha, context, ExpressionNode::ReductionTarget::User); + xMinusAlphaPowerTwo = Power::Builder(Parenthesis::Builder(Addition::Builder(Symbol::Builder("x", strlen("x")), minusAlpha)), Rational::Builder(2)); + } else { + PoincareHelpers::Simplify(&alpha, context, ExpressionNode::ReductionTarget::User); + xMinusAlphaPowerTwo = Power::Builder(Parenthesis::Builder(Subtraction::Builder(Symbol::Builder("x", strlen("x")), alpha)), Rational::Builder(2)); + } + + Expression xMinusAlphaPowerTwoWithFactor; + + switch (multiplicationTypeForA) + { + case MultiplicationTypeForA::Nothing: + xMinusAlphaPowerTwoWithFactor = xMinusAlphaPowerTwo; + break; + case MultiplicationTypeForA::Minus: + xMinusAlphaPowerTwoWithFactor = Opposite::Builder(xMinusAlphaPowerTwo); + break; + case MultiplicationTypeForA::Parenthesis: + xMinusAlphaPowerTwoWithFactor = Multiplication::Builder(Parenthesis::Builder(a.clone()), xMinusAlphaPowerTwo); + break; + case MultiplicationTypeForA::Normal: + xMinusAlphaPowerTwoWithFactor = Multiplication::Builder(a.clone(), xMinusAlphaPowerTwo); + break; + default: + assert(false); + break; + } + + Expression canonized; + PoincareHelpers::Simplify(&minusBeta, context, ExpressionNode::ReductionTarget::User); + Expression beta = getOppositeIfExists(minusBeta, &reductionContext); + if (beta.isUninitialized()) { + if (minusBeta.type() == ExpressionNode::Type::Addition || minusBeta.type() == ExpressionNode::Type::Subtraction) { + canonized = Subtraction::Builder(xMinusAlphaPowerTwoWithFactor, Parenthesis::Builder(minusBeta)); + } else { + canonized = Subtraction::Builder(xMinusAlphaPowerTwoWithFactor, minusBeta); + } + } else { + PoincareHelpers::Simplify(&beta, context, ExpressionNode::ReductionTarget::User); + canonized = Addition::Builder(xMinusAlphaPowerTwoWithFactor, beta); + } + + Expression x0; + Expression x1; + + if (delta.nullStatus(context) == ExpressionNode::NullStatus::Null) { + // x0 = x1 = -b/(2a) + x0 = Division::Builder(Opposite::Builder(b.clone()), Multiplication::Builder(Rational::Builder(2), a.clone())); + m_numberOfSolutions = 1; + PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::SystemForApproximation); + } else { + // x0 = (-b-sqrt(delta))/(2a) + x0 = Division::Builder(Subtraction::Builder(Opposite::Builder(b.clone()), SquareRoot::Builder(delta.clone())), Multiplication::Builder(Rational::Builder(2), a.clone())); + // x1 = (-b+sqrt(delta))/(2a) + x1 = Division::Builder(Addition::Builder(Opposite::Builder(b.clone()), SquareRoot::Builder(delta.clone())), Multiplication::Builder(Rational::Builder(2), a.clone())); + m_numberOfSolutions = 2; + PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::SystemForApproximation); + PoincareHelpers::Simplify(&x1, context, ExpressionNode::ReductionTarget::SystemForApproximation); + if (x0.type() == ExpressionNode::Type::Unreal) { + assert(x1.type() == ExpressionNode::Type::Unreal); + m_numberOfSolutions = 0; + } + } + + Expression factorized; + + if (m_numberOfSolutions == 2) { + Expression firstFactor; + Expression secondFactor; + + Expression x0Opposite = getOppositeIfExists(x0, &reductionContext); + if (x0Opposite.isUninitialized()) { + PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::User); + if (x0.type() == ExpressionNode::Type::Addition || x0.type() == ExpressionNode::Type::Subtraction) { + firstFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), Parenthesis::Builder(x0.clone())); + } else { + firstFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0.clone()); + } + } else { + if (x0Opposite.type() == ExpressionNode::Type::Addition || x0Opposite.type() == ExpressionNode::Type::Subtraction) { + x0Opposite = Parenthesis::Builder(x0Opposite.clone()); + } + firstFactor = Addition::Builder(Symbol::Builder("x", strlen("x")), x0Opposite.clone()); + } + + Expression x1Opposite = getOppositeIfExists(x1, &reductionContext); + if (x1Opposite.isUninitialized()) { + PoincareHelpers::Simplify(&x1, context, ExpressionNode::ReductionTarget::User); + if (x1.type() == ExpressionNode::Type::Addition || x1.type() == ExpressionNode::Type::Subtraction) { + secondFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), Parenthesis::Builder(x1.clone())); + } else { + secondFactor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x1.clone()); + } + } else { + PoincareHelpers::Simplify(&x1Opposite, context, ExpressionNode::ReductionTarget::User); + if (x1Opposite.type() == ExpressionNode::Type::Addition || x1Opposite.type() == ExpressionNode::Type::Subtraction) { + x1Opposite = Parenthesis::Builder(x1Opposite.clone()); + } + secondFactor = Addition::Builder(Symbol::Builder("x", strlen("x")), x1Opposite.clone()); + } + + Expression solutionProduct = Multiplication::Builder(Parenthesis::Builder(firstFactor), Parenthesis::Builder(secondFactor)); + switch (multiplicationTypeForA) + { + case MultiplicationTypeForA::Nothing: + factorized = solutionProduct; + break; + case MultiplicationTypeForA::Minus: + factorized = Opposite::Builder(solutionProduct); + break; + case MultiplicationTypeForA::Parenthesis: + factorized = Multiplication::Builder(Parenthesis::Builder(a.clone()), solutionProduct); + break; + case MultiplicationTypeForA::Normal: + factorized = Multiplication::Builder(a.clone(), solutionProduct); + break; + default: + assert(false); + break; + } + } else if (m_numberOfSolutions == 1) { + Expression x0Opposite = getOppositeIfExists(x0, &reductionContext); + Expression factor; + + if (x0Opposite.isUninitialized()) { + PoincareHelpers::Simplify(&x0, context, ExpressionNode::ReductionTarget::User); + if (x0.type() == ExpressionNode::Type::Addition || x0.type() == ExpressionNode::Type::Subtraction) { + factor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), Parenthesis::Builder(x0.clone())); + } else { + factor = Subtraction::Builder(Symbol::Builder("x", strlen("x")), x0.clone()); + } + } else { + PoincareHelpers::Simplify(&x0Opposite, context, ExpressionNode::ReductionTarget::User); + factor = Addition::Builder(Symbol::Builder("x", strlen("x")), x0Opposite.clone()); + } + + Expression solutionProduct = Power::Builder(Parenthesis::Builder(factor), Rational::Builder(2)); + switch (multiplicationTypeForA) + { + case MultiplicationTypeForA::Nothing: + factorized = solutionProduct; + break; + case MultiplicationTypeForA::Minus: + factorized = Opposite::Builder(solutionProduct); + break; + case MultiplicationTypeForA::Parenthesis: + factorized = Multiplication::Builder(Parenthesis::Builder(a.clone()), solutionProduct); + break; + case MultiplicationTypeForA::Normal: + factorized = Multiplication::Builder(a.clone(), solutionProduct); + break; + default: + assert(false); + break; + } + } + + PoincareHelpers::Simplify(&delta, context, ExpressionNode::ReductionTarget::User); + + m_layouts[0] = PoincareHelpers::CreateLayout(canonized); + if (m_numberOfSolutions > 0) { + m_layouts[1] = PoincareHelpers::CreateLayout(factorized); + m_layouts[2] = PoincareHelpers::CreateLayout(delta); + m_layouts[3] = PoincareHelpers::CreateLayout(x0); + if (m_numberOfSolutions > 1) { + m_layouts[4] = PoincareHelpers::CreateLayout(x1); + } + } else { + m_layouts[1] = PoincareHelpers::CreateLayout(delta); + } +} + +Expression SecondDegreeListController::getOppositeIfExists(Expression e, Poincare::ExpressionNode::ReductionContext * reductionContext) { + if (e.isNumber() && e.sign(reductionContext->context()) == ExpressionNode::Sign::Negative) { + Number n = static_cast(e); + return std::move(n.setSign(ExpressionNode::Sign::Positive)); + } else if(e.type() == ExpressionNode::Type::Opposite) { + return std::move(e.childAtIndex(0).clone()); + } else if (e.type() == ExpressionNode::Type::Multiplication && e.numberOfChildren() > 0 && e.childAtIndex(0).isNumber() && e.childAtIndex(0).sign(reductionContext->context()) == ExpressionNode::Sign::Negative) { + Multiplication m = static_cast(e); + if (m.childAtIndex(0).type() == ExpressionNode::Type::Rational && static_cast(e).isMinusOne()) { + // The negative numeral factor is -1, we just remove it + m.removeChildAtIndexInPlace(0); + } else { + Expression firstChild = m.childAtIndex(0); + Number n = static_cast(firstChild); + m.childAtIndex(0).setChildrenInPlace(n.setSign(ExpressionNode::Sign::Positive)); + } + PoincareHelpers::Simplify(&m, reductionContext->context(), ExpressionNode::ReductionTarget::User); + return std::move(m); + } + return Expression(); +} + +I18n::Message SecondDegreeListController::messageAtIndex(int index) { + if (m_numberOfSolutions > 0) { + if (index == 0) { + return I18n::Message::CanonicalForm; + } + if (index == 1) { + return I18n::Message::FactorizedForm; + } + if (index == 2) { + return I18n::Message::Discriminant; + } + if (index == 3) { + if (m_numberOfSolutions == 1) { + return I18n::Message::OnlyRoot; + } else { + return I18n::Message::FirstRoot; + } + } + return I18n::Message::SecondRoot; + } else { + switch (index) { + case 0: + return I18n::Message::CanonicalForm; + default: + return I18n::Message::Discriminant; + } + } +} + +} diff --git a/apps/calculation/additional_outputs/second_degree_list_controller.h b/apps/calculation/additional_outputs/second_degree_list_controller.h new file mode 100644 index 00000000000..89812b71043 --- /dev/null +++ b/apps/calculation/additional_outputs/second_degree_list_controller.h @@ -0,0 +1,26 @@ +#ifndef CALCULATION_ADDITIONAL_OUTPUTS_SECOND_DEGREE_CONTROLLER_H +#define CALCULATION_ADDITIONAL_OUTPUTS_SECOND_DEGREE_CONTROLLER_H + +#include "expressions_list_controller.h" + +namespace Calculation { + +class SecondDegreeListController : public ExpressionsListController { +public: + SecondDegreeListController(EditExpressionController * editExpressionController) : + ExpressionsListController(editExpressionController), + m_numberOfSolutions(0) {} + + void setExpression(Poincare::Expression e) override; + +private: + Poincare::Expression getOppositeIfExists(Poincare::Expression e, Poincare::ExpressionNode::ReductionContext * reductionContext); + I18n::Message messageAtIndex(int index) override; + int m_numberOfSolutions; +}; + +} + +#endif + + diff --git a/apps/calculation/additional_outputs/trigonometry_list_controller.cpp b/apps/calculation/additional_outputs/trigonometry_list_controller.cpp index b8bdcb0764e..354cbc7391f 100644 --- a/apps/calculation/additional_outputs/trigonometry_list_controller.cpp +++ b/apps/calculation/additional_outputs/trigonometry_list_controller.cpp @@ -1,16 +1,63 @@ #include "trigonometry_list_controller.h" #include "../app.h" +#include +#include "../../shared/poincare_helpers.h" using namespace Poincare; namespace Calculation { -void TrigonometryListController::setExpression(Poincare::Expression e) { +static constexpr int s_fullCircle[] = { + 360, + 2, + 400 +}; + +Poincare::Constant toConstant(Expression e) { + return static_cast(e); +} + +void TrigonometryListController::setExpression(Expression e) { assert(e.type() == ExpressionNode::Type::Cosine || e.type() == ExpressionNode::Type::Sine); - IllustratedListController::setExpression(e.childAtIndex(0)); - // Fill calculation store Poincare::Context * context = App::app()->localContext(); + Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); + Preferences::AngleUnit angleUnit = preferences->angleUnit(); + + Expression angleExpression = e.childAtIndex(0); + + Shared::PoincareHelpers::Reduce(&angleExpression, context, Poincare::ExpressionNode::ReductionTarget::SystemForAnalysis); + + if ((angleUnit == Preferences::AngleUnit::Radian + && angleExpression.type() == ExpressionNode::Type::Multiplication + && angleExpression.numberOfChildren() == 2 + && angleExpression.childAtIndex(1).type() == ExpressionNode::Type::Constant + && toConstant(angleExpression.childAtIndex(1)).isPi() + && angleExpression.childAtIndex(0).type() == ExpressionNode::Type::Rational) + || ((angleUnit == Preferences::AngleUnit::Degree || angleUnit == Preferences::AngleUnit::Gradian) + && angleExpression.type() == ExpressionNode::Type::Rational)) { + + Expression extracted = angleUnit == Preferences::AngleUnit::Radian ? angleExpression.childAtIndex(0) : angleExpression; + Rational r = static_cast(extracted); + + Integer denominator = Integer::Multiplication(r.integerDenominator(), Integer(s_fullCircle[(int) angleUnit])); + IntegerDivision division = Integer::Division(r.signedIntegerNumerator(), denominator); + + Integer remainder = division.remainder; + + Expression newAngle; + Integer rDenominator = r.integerDenominator(); + Rational newCoefficient = Rational::Builder(remainder, rDenominator); + if (angleUnit == Preferences::AngleUnit::Radian) { + angleExpression = Multiplication::Builder(newCoefficient, angleExpression.childAtIndex(1)); + } else { + angleExpression = newCoefficient; + } + } + + IllustratedListController::setExpression(angleExpression); + + // Fill calculation store m_calculationStore.push("sin(θ)", context, CalculationHeight); m_calculationStore.push("cos(θ)", context, CalculationHeight); m_calculationStore.push("θ", context, CalculationHeight); diff --git a/apps/calculation/additional_outputs/unit_list_controller.cpp b/apps/calculation/additional_outputs/unit_list_controller.cpp index 869826d5e4f..37fdcf7763e 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.cpp +++ b/apps/calculation/additional_outputs/unit_list_controller.cpp @@ -12,6 +12,26 @@ using namespace Shared; namespace Calculation { + +UnitListController::UnitListController(EditExpressionController * editExpressionController) : + ExpressionsListController(editExpressionController), + m_dimensionMessage(I18n::Message::Default) +{ + m_dimensionCell.setMessageFont(KDFont::LargeFont); +} + +bool UnitListController::handleEvent(Ion::Events::Event event) { + if (selectedRow() == 0 && (event == Ion::Events::OK || event == Ion::Events::EXE)) { + return true; + } + + // HACK: Change the selected row (prevent some bugs when OK is pressed) + selectRow(selectedRow() - 1); + bool value = ListController::handleEvent(event); + selectRow(selectedRow() + 1); + return value; +} + void UnitListController::setExpression(Poincare::Expression e) { ExpressionsListController::setExpression(e); assert(!m_expression.isUninitialized()); @@ -22,6 +42,7 @@ void UnitListController::setExpression(Poincare::Expression e) { for (size_t i = 0; i < k_maxNumberOfRows; i++) { expressions[i] = Expression(); } + m_dimensionMessage = I18n::Message::Default; /* 1. First rows: miscellaneous classic units for some dimensions, in both * metric and imperial units. */ @@ -37,7 +58,7 @@ void UnitListController::setExpression(Poincare::Expression e) { GlobalPreferences::sharedGlobalPreferences()->unitFormat(), ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); - int numberOfExpressions = Unit::SetAdditionalExpressions(units, value, expressions, k_maxNumberOfRows, reductionContext); + int numberOfExpressions = Unit::SetAdditionalExpressionsAndMessage(units, value, expressions, k_maxNumberOfRows, reductionContext, &m_dimensionMessage); // 2. SI units only assert(numberOfExpressions < k_maxNumberOfRows - 1); @@ -89,6 +110,44 @@ void UnitListController::setExpression(Poincare::Expression e) { } } +int UnitListController::numberOfRows() const { + int messageRow = m_dimensionMessage != I18n::Message::Default ? 1 : 0; + return ExpressionsListController::numberOfRows() + messageRow; +} + +void UnitListController::willDisplayCellForIndex(HighlightCell * cell, int index) { + if (index == 0) { + MessageTableCell<> * messageTableCell = (MessageTableCell<> *)cell; + messageTableCell->setMessage(m_dimensionMessage); + } else { + ExpressionsListController::willDisplayCellForIndex(cell, index - 1); + } +} + +KDCoordinate UnitListController::rowHeight(int index) { + if (index == 0) { + return 35; + } else { + return ExpressionsListController::rowHeight(index - 1); + } +} + +HighlightCell * UnitListController::reusableCell(int index, int type) { + if (type == 0) { + return ExpressionsListController::reusableCell(index, type); + } else { + return &m_dimensionCell; + } +} + +int UnitListController::typeAtLocation(int i, int j) { + if (j == 0) { + return 1; + } else { + return ExpressionsListController::typeAtLocation(i, j - 1); + } +} + I18n::Message UnitListController::messageAtIndex(int index) { return (I18n::Message)0; } diff --git a/apps/calculation/additional_outputs/unit_list_controller.h b/apps/calculation/additional_outputs/unit_list_controller.h index 58f6d1e0d90..fb351ec320c 100644 --- a/apps/calculation/additional_outputs/unit_list_controller.h +++ b/apps/calculation/additional_outputs/unit_list_controller.h @@ -7,13 +7,23 @@ namespace Calculation { class UnitListController : public ExpressionsListController { public: - UnitListController(EditExpressionController * editExpressionController) : - ExpressionsListController(editExpressionController) {} + UnitListController(EditExpressionController * editExpressionController); + + /* Responder */ + bool handleEvent(Ion::Events::Event event) override; void setExpression(Poincare::Expression e) override; + int reusableCellCount(int type) override { return type == 0 ? ExpressionsListController::reusableCellCount(type) : 1; } + HighlightCell * reusableCell(int index, int type) override; + KDCoordinate rowHeight(int j) override; + int typeAtLocation(int i, int j) override; + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + int numberOfRows() const override; private: I18n::Message messageAtIndex(int index) override; + I18n::Message m_dimensionMessage; + MessageTableCell<> m_dimensionCell; }; } diff --git a/apps/calculation/app.cpp b/apps/calculation/app.cpp index 2c5c97d3c87..ad344c31687 100644 --- a/apps/calculation/app.cpp +++ b/apps/calculation/app.cpp @@ -72,7 +72,7 @@ bool App::isAcceptableExpression(const Poincare::Expression expression) { return false; } } - return !(expression.isUninitialized() || expression.type() == ExpressionNode::Type::Equal); + return !expression.isUninitialized(); } void App::didBecomeActive(Window * window) { diff --git a/apps/calculation/base.de.i18n b/apps/calculation/base.de.i18n index 406f27e8499..419d3b96574 100644 --- a/apps/calculation/base.de.i18n +++ b/apps/calculation/base.de.i18n @@ -11,4 +11,10 @@ AdditionalDeterminant = "Determinante" AdditionalInverse = "Inverse" AdditionalRowEchelonForm = "Stufenform" AdditionalReducedRowEchelonForm = "Reduzierte Stufenform" -AdditionalTrace = "Spur" \ No newline at end of file +AdditionalTrace = "Spur" +CanonicalForm = "Kanonische Form" +FactorizedForm = "Factorisierte Form" +Discriminant = "Diskriminante" +OnlyRoot = "Wurzel" +FirstRoot = "Erste Wurzel" +SecondRoot = "Zweite Wurzel" diff --git a/apps/calculation/base.en.i18n b/apps/calculation/base.en.i18n index 967c0905063..23df23e3a55 100644 --- a/apps/calculation/base.en.i18n +++ b/apps/calculation/base.en.i18n @@ -12,3 +12,9 @@ AdditionalInverse = "Inverse" AdditionalRowEchelonForm = "Row echelon form" AdditionalReducedRowEchelonForm = "Reduced row echelon form" AdditionalTrace = "Trace" +CanonicalForm = "Canonical form" +FactorizedForm = "Factorized form" +Discriminant = "Discriminant" +OnlyRoot = "Root" +FirstRoot = "First root" +SecondRoot = "Second root" diff --git a/apps/calculation/base.es.i18n b/apps/calculation/base.es.i18n index 057481a0dbd..1df949c380c 100644 --- a/apps/calculation/base.es.i18n +++ b/apps/calculation/base.es.i18n @@ -11,4 +11,10 @@ AdditionalDeterminant = "Determinante" AdditionalInverse = "Inversa" AdditionalRowEchelonForm = "Matriz escalonada" AdditionalReducedRowEchelonForm = "Matriz escalonada reducida" -AdditionalTrace = "Traza" \ No newline at end of file +AdditionalTrace = "Traza" +CanonicalForm = "Forma canónica" +FactorizedForm = "Forma factorizada" +Discriminant = "Discriminante" +OnlyRoot = "Raíz" +FirstRoot = "Primera raíz" +SecondRoot = "Segunda raíz" diff --git a/apps/calculation/base.fr.i18n b/apps/calculation/base.fr.i18n index 0e155e29474..2b9e7bdac8b 100644 --- a/apps/calculation/base.fr.i18n +++ b/apps/calculation/base.fr.i18n @@ -11,4 +11,10 @@ AdditionalDeterminant = "Déterminant" AdditionalInverse = "Inverse" AdditionalRowEchelonForm = "Forme échelonnée" AdditionalReducedRowEchelonForm = "Forme échelonnée réduite" -AdditionalTrace = "Trace" \ No newline at end of file +AdditionalTrace = "Trace" +CanonicalForm = "Forme canonique" +FactorizedForm = "Forme factorisée" +Discriminant = "Discriminant" +OnlyRoot = "Racine" +FirstRoot = "Première racine" +SecondRoot = "Seconde racine" diff --git a/apps/calculation/base.hu.i18n b/apps/calculation/base.hu.i18n index 39397adee2f..4c6cfc4a020 100644 --- a/apps/calculation/base.hu.i18n +++ b/apps/calculation/base.hu.i18n @@ -1,14 +1,20 @@ -CalculApp = "Számolás" -CalculAppCapital = "SZÁMOLÁS" -AdditionalResults = "További eredmények" -DecimalBase = "Decimális" -HexadecimalBase = "Hexadecimális" -BinaryBase = "Bináris" -PrimeFactors = "Alapvetö tényezök" -MixedFraction = "Vegyes frakció" -EuclideanDivision = "Euklideszi osztás" -AdditionalDeterminant = "Meghatározó" -AdditionalInverse = "inverz" -AdditionalRowEchelonForm = "Sor echelon forma" -AdditionalReducedRowEchelonForm = "Csökkentett sorú Echelon forma" -AdditionalTrace = "Nyomkövetés" +CalculApp = "Számolás" +CalculAppCapital = "SZÁMOLÁS" +AdditionalResults = "További eredmények" +DecimalBase = "Decimális" +HexadecimalBase = "Hexadecimális" +BinaryBase = "Bináris" +PrimeFactors = "Alapvetö tényezök" +MixedFraction = "Vegyes frakció" +EuclideanDivision = "Euklideszi osztás" +AdditionalDeterminant = "Meghatározó" +AdditionalInverse = "inverz" +AdditionalRowEchelonForm = "Sor echelon forma" +AdditionalReducedRowEchelonForm = "Csökkentett sorú Echelon forma" +AdditionalTrace = "Nyomkövetés" +CanonicalForm = "Kanonikus forma" +FactorizedForm = "Factorizált forma" +Discriminant = "Discriminant" +OnlyRoot = "Gyökér" +FirstRoot = "Első gyökér" +SecondRoot = "Második gyökér" diff --git a/apps/calculation/base.it.i18n b/apps/calculation/base.it.i18n index ca41028937e..62620d05c9c 100644 --- a/apps/calculation/base.it.i18n +++ b/apps/calculation/base.it.i18n @@ -11,4 +11,10 @@ AdditionalDeterminant = "Determinante" AdditionalInverse = "Inversa" AdditionalRowEchelonForm = "Matrice a scalini" AdditionalReducedRowEchelonForm = "Matrice ridotta a scalini" -AdditionalTrace = "Traccia" \ No newline at end of file +AdditionalTrace = "Traccia" +CanonicalForm = "Forma canonica" +FactorizedForm = "Forma fattorizzata" +Discriminant = "Discriminante" +OnlyRoot = "Radice" +FirstRoot = "Prima radice" +SecondRoot = "Seconda radice" diff --git a/apps/calculation/base.nl.i18n b/apps/calculation/base.nl.i18n index 51e412cb40c..1b286d49b9b 100644 --- a/apps/calculation/base.nl.i18n +++ b/apps/calculation/base.nl.i18n @@ -11,4 +11,10 @@ AdditionalDeterminant = "Determinant" AdditionalInverse = "Inverse" AdditionalRowEchelonForm = "Echelonvorm" AdditionalReducedRowEchelonForm = "Gereduceerde echelonvorm" -AdditionalTrace = "Spoor" \ No newline at end of file +AdditionalTrace = "Spoor" +CanonicalForm = "Canonische vorm" +FactorizedForm = "Factorized vorm" +Discriminant = "Discriminant" +OnlyRoot = "Wortel" +FirstRoot = "Eerste wortel" +SecondRoot = "Tweede wortel" diff --git a/apps/calculation/base.pt.i18n b/apps/calculation/base.pt.i18n index 941363a04b7..15abb51a7e1 100644 --- a/apps/calculation/base.pt.i18n +++ b/apps/calculation/base.pt.i18n @@ -11,4 +11,10 @@ AdditionalDeterminant = "Determinante" AdditionalInverse = "Matriz inversa" AdditionalRowEchelonForm = "Matriz escalonada" AdditionalReducedRowEchelonForm = "Matriz escalonada reduzida" -AdditionalTrace = "Traço" \ No newline at end of file +AdditionalTrace = "Traço" +CanonicalForm = "Forma canónica" +FactorizedForm = "Factorized form" +Discriminant = "Discriminante" +OnlyRoot = "Raiz" +FirstRoot = "Primeira raiz" +SecondRoot = "Segunda raiz" diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index c293cbbe841..b67efc2b89b 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -256,8 +257,9 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co if (o.hasUnit()) { Expression unit; PoincareHelpers::ReduceAndRemoveUnit(&o, App::app()->localContext(), ExpressionNode::ReductionTarget::User, &unit, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined, ExpressionNode::UnitConversion::None); - double value = PoincareHelpers::ApproximateToScalar(o, App::app()->localContext()); - return (Unit::ShouldDisplayAdditionalOutputs(value, unit, GlobalPreferences::sharedGlobalPreferences()->unitFormat())) ? AdditionalInformationType::Unit : AdditionalInformationType::None; + UnitNode::Vector vector = UnitNode::Vector::FromBaseUnits(unit); + const Unit::Representative * representative = Unit::Representative::RepresentativeForDimension(vector); + return representative != nullptr ? AdditionalInformationType::Unit : AdditionalInformationType::None; } if (o.isBasedIntegerCappedBy(k_maximalIntegerWithAdditionalInformation)) { return AdditionalInformationType::Integer; @@ -272,6 +274,9 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co if (o.type() == ExpressionNode::Type::Matrix) { return AdditionalInformationType::Matrix; } + if (o.polynomialDegree(context, "x") == 2) { + return AdditionalInformationType::SecondDegree; + } return AdditionalInformationType::None; } diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index be7f87c9bcc..4c026cbc42b 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -39,6 +39,7 @@ friend CalculationStore; None = 0, Integer, Rational, + SecondDegree, Trigonometry, Unit, Matrix, diff --git a/apps/calculation/calculation_store.cpp b/apps/calculation/calculation_store.cpp index e0dcc12f161..eee89ac89ed 100644 --- a/apps/calculation/calculation_store.cpp +++ b/apps/calculation/calculation_store.cpp @@ -7,6 +7,39 @@ #include "../exam_mode_configuration.h" #include +#if defined _FXCG || defined NSPIRE_NEWLIB +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static KDCoordinate dummyHeight(::Calculation::Calculation * c, bool expanded) { + bool b; + Poincare::Layout l = c->createExactOutputLayout(&b); + if (!b) { + l=c->createInputLayout(); + } + KDSize s = l.layoutSize(); + const int bordersize = 10; + int h = s.height() + bordersize; + const int maxheight = 64; + if (h > maxheight) { + return maxheight; + } + return h; +} + +extern void * last_calculation_history; +void * last_calculation_history = 0; +const char * retrieve_calc_history(); + +#endif + using namespace Poincare; using namespace Shared; @@ -16,16 +49,55 @@ CalculationStore::CalculationStore(char * buffer, int size) : m_buffer(buffer), m_bufferSize(size), m_calculationAreaEnd(m_buffer), - m_numberOfCalculations(0) + m_numberOfCalculations(0), + m_trashIndex(-1) { assert(m_buffer != nullptr); assert(m_bufferSize > 0); +#if defined _FXCG || defined NSPIRE_NEWLIB + if (last_calculation_history == 0){ + // Restore from scriptstore + const char * buf=retrieve_calc_history(); + if (buf) { + Shared::GlobalContext globalContext; + char * ptr=(char *)buf; + for (;*ptr;) { + for (;*ptr;++ptr) { + if (*ptr=='\n') { + break; + } + } + char c = *ptr; + *ptr=0; + if (ptr > buf) { + push(buf,&globalContext, dummyHeight); + } + *ptr = c; + ++ptr; + buf = ptr; + } + } + last_calculation_history = (void *) this; + } +#endif } -// Returns an expiring pointer to the calculation of index i +// Returns an expiring pointer to the calculation of index i, and ignore the trash ExpiringPointer CalculationStore::calculationAtIndex(int i) { +#if defined _FXCG || defined NSPIRE_NEWLIB + last_calculation_history = (void *) this; +#endif + if (m_trashIndex == -1 || i < m_trashIndex) { + return realCalculationAtIndex(i); + } else { + return realCalculationAtIndex(i + 1); + } +} + +// Returns an expiring pointer to the real calculation of index i +ExpiringPointer CalculationStore::realCalculationAtIndex(int i) { assert(i >= 0 && i < m_numberOfCalculations); - // m_buffer is the adress of the oldest calculation in calculation store + // m_buffer is the address of the oldest calculation in calculation store Calculation * c = (Calculation *) m_buffer; if (i != m_numberOfCalculations-1) { // The calculation we want is not the oldest one so we get its pointer @@ -36,12 +108,13 @@ ExpiringPointer CalculationStore::calculationAtIndex(int i) { // Pushes an expression in the store ExpiringPointer CalculationStore::push(const char * text, Context * context, HeightComputer heightComputer) { + emptyTrash(); /* Compute ans now, before the buffer is updated and before the calculation * might be deleted */ Expression ans = ansExpression(context); /* Prepare the buffer for the new calculation - *The minimal size to store the new calculation is the minimal size of a calculation plus the pointer to its end */ + * The minimal size to store the new calculation is the minimal size of a calculation plus the pointer to its end */ int minSize = Calculation::MinimalSize() + sizeof(Calculation *); assert(m_bufferSize > minSize); while (remainingBufferSize() < minSize) { @@ -100,9 +173,9 @@ ExpiringPointer CalculationStore::push(const char * text, Context * numberOfSignificantDigits = Poincare::Preferences::sharedPreferences()->numberOfSignificantDigits(); } if (!pushSerializeExpression(outputs[i], beginingOfFreeSpace, &endOfFreeSpace, numberOfSignificantDigits)) { - /* If the exat/approximate output does not fit in the store (event if the + /* If the exact/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. */ + * undef if it fits, else replace the whole calculation with undef. */ Expression undef = Undefined::Builder(); if (!pushSerializeExpression(undef, beginingOfFreeSpace, &endOfFreeSpace)) { return emptyStoreAndPushUndef(context, heightComputer); @@ -132,15 +205,23 @@ ExpiringPointer CalculationStore::push(const char * text, Context * // Delete the calculation of index i void CalculationStore::deleteCalculationAtIndex(int i) { + if (m_trashIndex != -1) { + emptyTrash(); + } + m_trashIndex = i; +} + +// Delete the calculation of index i, internal algorithm +void CalculationStore::realDeleteCalculationAtIndex(int i) { assert(i >= 0 && i < m_numberOfCalculations); if (i == 0) { - ExpiringPointer lastCalculationPointer = calculationAtIndex(0); + ExpiringPointer lastCalculationPointer = realCalculationAtIndex(0); m_calculationAreaEnd = (char *)(lastCalculationPointer.pointer()); m_numberOfCalculations--; return; } - char * calcI = (char *)calculationAtIndex(i).pointer(); - char * nextCalc = (char *) calculationAtIndex(i-1).pointer(); + char * calcI = (char *)realCalculationAtIndex(i).pointer(); + char * nextCalc = (char *) realCalculationAtIndex(i-1).pointer(); assert(m_calculationAreaEnd >= nextCalc); size_t slidingSize = m_calculationAreaEnd - nextCalc; // Slide the i-1 most recent calculations right after the i+1'th @@ -154,13 +235,14 @@ void CalculationStore::deleteCalculationAtIndex(int i) { // Delete the oldest calculation in the store and returns the amount of space freed by the operation size_t CalculationStore::deleteOldestCalculation() { char * oldBufferEnd = (char *) m_calculationAreaEnd; - deleteCalculationAtIndex(numberOfCalculations()-1); + realDeleteCalculationAtIndex(numberOfCalculations()-1); char * newBufferEnd = (char *) m_calculationAreaEnd; return oldBufferEnd - newBufferEnd; } // Delete all calculations void CalculationStore::deleteAll() { + m_trashIndex = -1; m_calculationAreaEnd = m_buffer; m_numberOfCalculations = 0; } @@ -177,8 +259,8 @@ Expression CalculationStore::ansExpression(Context * context) { * parsed), ans is replaced by the approximation output when any Store or * Equal expression appears. */ Expression e = mostRecentCalculation->exactOutput(); - bool exactOuptutInvolvesStoreEqual = e.type() == ExpressionNode::Type::Store || e.type() == ExpressionNode::Type::Equal; - if (mostRecentCalculation->input().recursivelyMatches(Expression::IsApproximate, context) || exactOuptutInvolvesStoreEqual) { + bool exactOutputInvolvesStoreEqual = e.type() == ExpressionNode::Type::Store || e.type() == ExpressionNode::Type::Equal; + if (mostRecentCalculation->input().recursivelyMatches(Expression::IsApproximate, context) || exactOutputInvolvesStoreEqual) { return mostRecentCalculation->approximateOutput(context, Calculation::NumberOfSignificantDigits::Maximal); } return mostRecentCalculation->exactOutput(); @@ -200,6 +282,12 @@ bool CalculationStore::pushSerializeExpression(Expression e, char * location, ch return expressionIsPushed; } +void CalculationStore::emptyTrash() { + if (m_trashIndex != -1) { + realDeleteCalculationAtIndex(m_trashIndex); + m_trashIndex = -1; + } +} Shared::ExpiringPointer CalculationStore::emptyStoreAndPushUndef(Context * context, HeightComputer heightComputer) { @@ -213,7 +301,7 @@ Shared::ExpiringPointer CalculationStore::emptyStoreAndPushUndef(Co void CalculationStore::recomputeMemoizedPointersAfterCalculationIndex(int index) { assert(index < m_numberOfCalculations); // Clear pointer and recompute new ones - Calculation * c = calculationAtIndex(index).pointer(); + Calculation * c = realCalculationAtIndex(index).pointer(); Calculation * nextCalc; while (index != 0) { nextCalc = c->next(); diff --git a/apps/calculation/calculation_store.h b/apps/calculation/calculation_store.h index 53bc3fdf4f6..134a9fbb853 100644 --- a/apps/calculation/calculation_store.h +++ b/apps/calculation/calculation_store.h @@ -41,11 +41,15 @@ class CalculationStore { void deleteCalculationAtIndex(int i); void deleteAll(); int remainingBufferSize() const { assert(m_calculationAreaEnd >= m_buffer); return m_bufferSize - (m_calculationAreaEnd - m_buffer) - m_numberOfCalculations*sizeof(Calculation*); } - int numberOfCalculations() const { return m_numberOfCalculations; } + int numberOfCalculations() const { return m_numberOfCalculations - (m_trashIndex != -1); } Poincare::Expression ansExpression(Poincare::Context * context); int bufferSize() { return m_bufferSize; } + void reinsertTrash() { m_trashIndex = -1; } private: + void emptyTrash(); + Shared::ExpiringPointer realCalculationAtIndex(int i); + void realDeleteCalculationAtIndex(int i); class CalculationIterator { public: @@ -70,6 +74,7 @@ class CalculationStore { int m_bufferSize; const char * m_calculationAreaEnd; int m_numberOfCalculations; + int m_trashIndex; size_t deleteOldestCalculation(); char * addressOfPointerToCalculationOfIndex(int i) {return m_buffer + m_bufferSize - (m_numberOfCalculations - i)*sizeof(Calculation *);} diff --git a/apps/calculation/edit_expression_controller.cpp b/apps/calculation/edit_expression_controller.cpp index 7f747d3598f..9d999bf196e 100644 --- a/apps/calculation/edit_expression_controller.cpp +++ b/apps/calculation/edit_expression_controller.cpp @@ -68,6 +68,15 @@ void EditExpressionController::memoizeInput() { *m_cacheBufferInformation = m_contentView.expressionField()->moveCursorAndDumpContent(m_cacheBuffer, k_cacheBufferSize); } +bool EditExpressionController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::ShiftBack) { + m_historyController->reinsertTrash(); + m_historyController->reload(); + return true; + } + return false; +} + void EditExpressionController::viewWillAppear() { m_historyController->viewWillAppear(); } diff --git a/apps/calculation/edit_expression_controller.h b/apps/calculation/edit_expression_controller.h index 32a6ec84cb2..9e788faa361 100644 --- a/apps/calculation/edit_expression_controller.h +++ b/apps/calculation/edit_expression_controller.h @@ -31,6 +31,7 @@ class EditExpressionController : public ViewController, public Shared::TextField void insertTextBody(const char * text); void restoreInput(); void memoizeInput(); + bool handleEvent(Ion::Events::Event event) override; /* TextFieldDelegate */ bool textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) override; diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index 6d51e7862f9..9a868730973 100644 --- a/apps/calculation/history_controller.cpp +++ b/apps/calculation/history_controller.cpp @@ -16,6 +16,7 @@ HistoryController::HistoryController(EditExpressionController * editExpressionCo m_complexController(editExpressionController), m_integerController(editExpressionController), m_rationalController(editExpressionController), + m_secondDegreeController(editExpressionController), m_trigonometryController(editExpressionController), m_unitController(editExpressionController), m_matrixController(editExpressionController) @@ -100,6 +101,8 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { Expression e = calculationAtIndex(focusRow)->exactOutput(); if (additionalInfoType == Calculation::AdditionalInformationType::Complex) { vc = &m_complexController; + } else if (additionalInfoType == Calculation::AdditionalInformationType::SecondDegree) { + vc = &m_secondDegreeController; } else if (additionalInfoType == Calculation::AdditionalInformationType::Trigonometry) { vc = &m_trigonometryController; // Find which of the input or output is the cosine/sine diff --git a/apps/calculation/history_controller.h b/apps/calculation/history_controller.h index e289eb5fc43..8b7c3bf53ef 100644 --- a/apps/calculation/history_controller.h +++ b/apps/calculation/history_controller.h @@ -8,6 +8,7 @@ #include "additional_outputs/complex_list_controller.h" #include "additional_outputs/integer_list_controller.h" #include "additional_outputs/rational_list_controller.h" +#include "additional_outputs/second_degree_list_controller.h" #include "additional_outputs/trigonometry_list_controller.h" #include "additional_outputs/unit_list_controller.h" #include "additional_outputs/matrix_list_controller.h" @@ -34,6 +35,7 @@ class HistoryController : public ViewController, public ListViewDataSource, publ int typeAtLocation(int i, int j) override; void setSelectedSubviewType(SubviewType subviewType, bool sameCell, int previousSelectedX = -1, int previousSelectedY = -1) override; void tableViewDidChangeSelectionAndDidScroll(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false) override; + void reinsertTrash() { m_calculationStore->reinsertTrash(); } private: int storeIndex(int i) { return numberOfRows() - i - 1; } Shared::ExpiringPointer calculationAtIndex(int i); @@ -47,6 +49,7 @@ class HistoryController : public ViewController, public ListViewDataSource, publ ComplexListController m_complexController; IntegerListController m_integerController; RationalListController m_rationalController; + SecondDegreeListController m_secondDegreeController; TrigonometryListController m_trigonometryController; UnitListController m_unitController; MatrixListController m_matrixController; diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index 71799403c26..e30995750fb 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -81,7 +81,7 @@ void HistoryViewCell::reloadSubviewHighlight() { m_ellipsis.setHighlighted(false); if (isHighlighted()) { if (m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Input) { - m_inputView.setExpressionBackgroundColor(Palette::ListCellBackgroundSelected); + m_inputView.setExpressionBackgroundColor(Palette::Select); } else if (m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Output) { m_scrollableOutputView.evenOddCell()->setHighlighted(true); } else { diff --git a/apps/code/Makefile b/apps/code/Makefile index 23b6a320780..af50725cd59 100644 --- a/apps/code/Makefile +++ b/apps/code/Makefile @@ -1,6 +1,8 @@ apps += Code::App app_headers += apps/code/app.h +SFLAGS += -DHAS_CODE + app_code_src = $(addprefix apps/code/,\ app.cpp \ console_controller.cpp \ diff --git a/apps/code/app.cpp b/apps/code/app.cpp index 4db851b222b..8739030871c 100644 --- a/apps/code/app.cpp +++ b/apps/code/app.cpp @@ -3,6 +3,13 @@ #include #include "helpers.h" #include +#include + +#if defined _FXCG || defined NSPIRE_NEWLIB +extern "C" int calculator; +extern "C" const int prizm_heap_size; +extern "C" char prizm_heap[]; +#endif namespace Code { @@ -129,6 +136,16 @@ VariableBoxController * App::variableBoxForInputEventHandler(InputEventHandler * } bool App::textInputDidReceiveEvent(InputEventHandler * textInput, Ion::Events::Event event) { + if (event == Ion::Events::XNT) { + int bufferSize = CodePoint::MaxCodePointCharLength + 1; + char buffer[bufferSize]; + bool shouldRemoveLastCharacter = false; + CodePoint codePoint = AppsContainer::sharedAppsContainer()->XNT('x', &shouldRemoveLastCharacter); + UTF8Decoder::CodePointToChars(codePoint, buffer, bufferSize); + buffer[UTF8Decoder::CharSizeOfCodePoint(codePoint)] = 0; + textInput->handleEventWithText(const_cast(buffer), false, false, shouldRemoveLastCharacter); + return true; + } const char * pythonText = Helpers::PythonTextForEvent(event); if (pythonText != nullptr) { textInput->handleEventWithText(pythonText); @@ -139,7 +156,17 @@ bool App::textInputDidReceiveEvent(InputEventHandler * textInput, Ion::Events::E void App::initPythonWithUser(const void * pythonUser) { if (!m_pythonUser) { - MicroPython::init(m_pythonHeap, m_pythonHeap + k_pythonHeapSize); +#if defined _FXCG || defined NSPIRE_NEWLIB + if (calculator == 1) { // fxcg50 + MicroPython::init( (void *) 0x8c200000, (void *)(0x8c200000+ 0x2e0000)); + } else if (calculator >= 1 && calculator <=4 ) { + MicroPython::init( prizm_heap, prizm_heap+prizm_heap_size); + } else { +#endif + MicroPython::init(m_pythonHeap, m_pythonHeap + k_pythonHeapSize); +#if defined _FXCG || defined NSPIRE_NEWLIB + } +#endif } m_pythonUser = pythonUser; } diff --git a/apps/code/app.h b/apps/code/app.h index 41f542eb4d4..67494d3f363 100644 --- a/apps/code/app.h +++ b/apps/code/app.h @@ -75,7 +75,7 @@ class App : public Shared::InputEventHandlerDelegateApp { VariableBoxController * variableBoxController() { return &m_variableBoxController; } - static constexpr int k_pythonHeapSize = 99000; + static constexpr int k_pythonHeapSize = 69500; private: /* Python delegate: diff --git a/apps/code/base.universal.i18n b/apps/code/base.universal.i18n index dbbf69f5a3b..57acbc2ec34 100644 --- a/apps/code/base.universal.i18n +++ b/apps/code/base.universal.i18n @@ -1,3 +1,2 @@ CodeAppCapital = "PYTHON" ConsolePrompt = ">>> " -ScriptParameters = "..." diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index 3ec3691d972..b5a22857c9e 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -1,5 +1,11 @@ PythonPound = "Kommentar" PythonPercent = "Modulo" +PythonColon = "Doppelpunkt" +PythonSemicon = "Semikolon" +PythonExclamationMark = "Ausrufezeichen" +PythonLessThan = "Kleiner als" +PythonGreaterThan = "Größer als" +PythonQuestionMark = "Fragezeichen" Python1J = "Imaginäres i" PythonLF = "Zeilenvorschub" PythonTab = "Tabulator" @@ -45,6 +51,7 @@ PythonCosh = "Hyperbolischer Kosinus" PythonCount = "Zählt die Vorkommen von x" PythonDegrees = "x von Bogenmaß in Grad umrechnen" PythonDivMod = "Quotient und Rest" +PythonDrawCircle = "Zeichne einen Kreis" PythonDrawLine = "Eine Linie zeichnen" PythonDrawString = "Text bei Pixel (x,y) darstellen" PythonErf = "Fehlerfunktion" @@ -52,13 +59,18 @@ PythonErfc = "Komplementäre Fehlerfunktion" PythonEval = "Rückgabe ausgewerteter Ausdruck" PythonExp = "Exponentialfunktion" PythonExpm1 = "Berechne exp(x)-1" +PythonFactorial = "Fakultät von x" PythonFabs = "Absoluter Wert" PythonFillRect = "Gefülltes Rechteck bei Pixel (x,y)" +PythonFillCircle = "Füllt einen Kreis" +PythonFillPolygon = "Füllt ein Polygon" PythonFloat = "x in einen Fließkommawert umwandeln" PythonFloor = "Abrunden" PythonFmod = "a modulo b" PythonFrExp = "Mantisse und Exponent von x: (m,e)" PythonGamma = "Gamma-Funktion" +PythonGetKeys = "Gedrückte Tasten erhalten" +PythonGetPalette = "Themenpalette erhalten" PythonGetPixel = "Farbe von Pixel (x,y) zurückgeben" PythonGetrandbits = "Ganzzahl mit k Zufallsbits" PythonGrid = "Sichtbarkeit des Gitters umschalten" @@ -70,12 +82,22 @@ PythonImportKandinsky = "Kandinsky-Modul importieren" PythonImportRandom = "Random-Modul importieren" PythonImportMath = "Math-Modul importieren" PythonImportMatplotlibPyplot = "Matplotlib.pyplot-Modul importieren" +PythonImportNumpy = "Ulab.numpy-Modul importieren" +PythonImportScipy = "Ulab.scipy-Modul importieren" PythonImportOs = "OS-Modul importieren" +PythonImportSys = "SYS-Modul importieren" PythonOsUname = "Informationen über das System holen" PythonOsGetlogin = "Benutzernamen holen" PythonOsRemove = "Datei namens Dateiname entfernen" PythonOsRename = "Datei umbenennen von Alt nach Neu" PythonOsListdir = "Dateien im Speicher auflisten" +PythonSysExit = "Terminate current program" +PythonSysPrintexception = "Print exception" +PythonSysByteorder = "The byte order of the system" +PythonSysImplementation = "Information about Python" +PythonSysModules = "Dictionary of loaded modules" +PythonSysVersion = "Python language version (string)" +PythonSysVersioninfo = "Python language version (tuple)" PythonImportTime = "Time-Modul importieren" PythonImportTurtle = "Turtle-Modul importieren" PythonIndex = "Index des ersten x-Vorkommens" @@ -87,7 +109,58 @@ PythonIsFinite = "Prüfen, ob x endlich ist" PythonIsInfinite = "Prüfen, ob x unendlich ist" PythonIsNaN = "Prüfen, ob x keine Zahl ist" PythonIsKeyDown = "Wahr, wenn die Taste k gedrückt ist" +PythonBattery = "Rückgabe der Batteriespannung" +PythonBatteryLevel = "Gibt den Batteriestand zurück" +PythonBatteryIscharging = "Gibt zurück, ob die Batterie geladen wird" +PythonSetBrightness = "Helligkeitsstufe festlegen" +PythonGetBrightness = "Helligkeitsstufe abrufen" PythonKandinskyFunction = "Kandinsky-Modul Funktionspräfix" +PythonKeyLeft = "LEFT ARROW key" +PythonKeyUp = "UP ARROW key" +PythonKeyDown = "DOWN ARROW key" +PythonKeyRight = "RIGHT ARROW key" +PythonKeyOk = "OK key" +PythonKeyBack = "BACK key" +PythonKeyHome = "HOME key" +PythonKeyOnOff = "ON/OFF key" +PythonKeyShift = "SHIFT key" +PythonKeyAlpha = "ALPHA key" +PythonKeyXnt = "X,N,T key" +PythonKeyVar = "VAR key" +PythonKeyToolbox = "TOOLBOX key" +PythonKeyBackspace = "BACKSPACE key" +PythonKeyExp = "EXPONENTIAL key" +PythonKeyLn = "NATURAL LOGARITHM key" +PythonKeyLog = "DECIMAL LOGARITHM key" +PythonKeyImaginary = "IMAGINARY I key" +PythonKeyComma = "COMMA key" +PythonKeyPower = "POWER key" +PythonKeySine = "SINE key" +PythonKeyCosine = "COSINE key" +PythonKeyTangent = "TANGENT key" +PythonKeyPi = "PI key" +PythonKeySqrt = "SQUARE ROOT key" +PythonKeySquare = "SQUARE key" +PythonKeySeven = "7 key" +PythonKeyEight = "8 key" +PythonKeyNine = "9 key" +PythonKeyLeftParenthesis = "LEFT PARENTHESIS key" +PythonKeyRightParenthesis = "RIGHT PARENTHESIS key" +PythonKeyFour = "4 key" +PythonKeyFive = "5 key" +PythonKeySix = "6 key" +PythonKeyMultiplication = "MULTIPLICATION key" +PythonKeyDivision = "DIVISION key" +PythonKeyOne = "1 key" +PythonKeyTwo = "2 key" +PythonKeyThree = "3 key" +PythonKeyPlus = "PLUS key" +PythonKeyMinus = "MINUS key" +PythonKeyZero = "0 key" +PythonKeyDot = "DOT key" +PythonKeyEe = "10 POWER X key" +PythonKeyAns = "ANS key" +PythonKeyExe = "EXE key" PythonLdexp = "Liefert x*(2**i), Inverse von frexp" PythonLength = "Länge eines Objekts" PythonLgamma = "Log-Gamma-Funktion" @@ -100,6 +173,14 @@ PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "Bruch- und Ganzzahl-Anteile von x" PythonMonotonic = "Wert einer monotonen Uhr" +PythonNumpyFunction = "numpy Modul-Präfix" +PythonNumpyFftFunction = "numpy.fft Modul-Präfix" +PythonNumpyLinalgFunction = "numpy.linalg Modul-Präfix" +PythonScipyFunction = "scipy Modul-Präfix" +PythonScipyLinalgFunction = "scipy.linalg Modul-Präfix" +PythonScipyOptimizeFunction = "scipy.optimize Modul-Präfix" +PythonScipySignalFunction = "scipy.signal Modul-Präfix" +PythonScipySpecialFunction = "scipy.special Modul-Präfix" PythonOct = "Ganzzahl in Oktal umwandeln" PythonPhase = "Phase von z" PythonPlot = "Plotten von y gegen x als Linien" @@ -125,6 +206,12 @@ PythonShow = "Figur anzeigen" PythonSin = "Sinus" PythonSinh = "Hyperbolischer Sinus" PythonSleep = "Ausführung aussetzen für t Sekunden" +PythonLocalTime = "Zeit in Tupel umwandeln" +PythonMktime = "Tupel in Zeit umwandeln" +PythonTime = "Abrufen des aktuellen Zeitstempels" +PythonSetLocaltime = "Zeit aus einem Tupel einstellen" +PythonRTCmode = "Aktuellen RTC-Modus abrufen" +PythonSetRTCmode = "RTC-Modus festlegen" PythonSort = "Die Liste sortieren" PythonSqrt = "Quadratwurzel" PythonSum = "Summe der Elemente einer Liste" @@ -151,14 +238,11 @@ PythonTurtlePosition = "Aktuelle (x,y) Position zurückgeben" PythonTurtleReset = "Die Zeichnung zurücksetzen" PythonTurtleRight = "Um ein Grad nach rechts drehen" PythonTurtleSetheading = "Ausrichtung auf einen Grad setzen" -PythonTurtleSetposition = "Den Igel auf Position setzen" PythonTurtleShowturtle = "Den Igel anzeigen" PythonTurtleSpeed = "Zeichengeschwindigkeit von 0 bis 10" PythonTurtleWrite = "Einen Text anzeigen" PythonUniform = "Fließkommazahl in [a,b]" PythonImportTime = "Time-Modul importieren" -PythonTimePrefix = "Zeitmodul-Funktionspräfix" -PythonTimeSleep = "Warte für n Sekunden" PythonMonotonic = "Monotone Zeit zurückgeben" PythonFileOpen = "Öffnet eine Datei" PythonFileSeekable = "Kann Datei durchsucht werden?" @@ -176,3 +260,5 @@ PythonFileName = "Enthält den Namen der Datei" PythonFileMode = "Enthält den Öffnungsmodus der Datei" PythonFileReadable = "Kann Datei gelesen werden?" PythonFileWritable = "Kann Datei geschrieben werden?" +PythonImportUtils = "Importieren von ulab.utils" +PythonUtilsFunction = "Funktionspräfix des utils-Moduls" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index e3b2d47787d..dd3936409d8 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -1,5 +1,11 @@ PythonPound = "Comment" PythonPercent = "Modulo" +PythonColon = "Colon" +PythonSemicon = "Semicolon" +PythonExclamationMark = "Exclamation mark" +PythonLessThan = "Less than" +PythonGreaterThan = "Greater than" +PythonQuestionMark = "Question mark" Python1J = "Imaginary i" PythonLF = "Line feed" PythonTab = "Tabulation" @@ -45,6 +51,7 @@ PythonCosh = "Hyperbolic cosine" PythonCount = "Count the occurrences of x" PythonDegrees = "Convert x from radians to degrees" PythonDivMod = "Quotient and remainder" +PythonDrawCircle = "Draw a circle" PythonDrawLine = "Draw a line" PythonDrawString = "Display a text from pixel (x,y)" PythonErf = "Error function" @@ -52,13 +59,18 @@ PythonErfc = "Complementary error function" PythonEval = "Return the evaluated expression" PythonExp = "Exponential function" PythonExpm1 = "Compute exp(x)-1" +PythonFactorial = "Factorial of x" PythonFabs = "Absolute value" +PythonFillCircle = "Fill a circle" +PythonFillPolygon = "Fill a polygon" PythonFillRect = "Fill a rectangle at pixel (x,y)" PythonFloat = "Convert x to a float" PythonFloor = "Floor" PythonFmod = "a modulo b" PythonFrExp = "Mantissa and exponent of x: (m,e)" PythonGamma = "Gamma function" +PythonGetKeys = "Get keys pressed" +PythonGetPalette = "Get theme palette" PythonGetPixel = "Return pixel (x,y) color" PythonGetrandbits = "Integer with k random bits" PythonGrid = "Toggle the visibility of the grid" @@ -70,6 +82,8 @@ PythonImportKandinsky = "Import kandinsky module" PythonImportRandom = "Import random module" PythonImportMath = "Import math module" PythonImportMatplotlibPyplot = "Import matplotlib.pyplot module" +PythonImportNumpy = "Import ulab.numpy module" +PythonImportScipy = "Import ulab.scipy module" PythonImportTime = "Import time module" PythonImportTurtle = "Import turtle module" PythonIndex = "Index of the first x occurrence" @@ -80,8 +94,59 @@ PythonIonFunction = "ion module function prefix" PythonIsFinite = "Check if x is finite" PythonIsInfinite = "Check if x is infinity" PythonIsKeyDown = "Return True if the k key is down" +PythonBattery = "Return battery voltage" +PythonBatteryLevel = "Return battery level" +PythonBatteryIscharging = "Return if battery is charging" +PythonSetBrightness = "Set brightness level" +PythonGetBrightness = "Get brightness level" PythonIsNaN = "Check if x is a NaN" PythonKandinskyFunction = "kandinsky module function prefix" +PythonKeyLeft = "LEFT ARROW key" +PythonKeyUp = "UP ARROW key" +PythonKeyDown = "DOWN ARROW key" +PythonKeyRight = "RIGHT ARROW key" +PythonKeyOk = "OK key" +PythonKeyBack = "BACK key" +PythonKeyHome = "HOME key" +PythonKeyOnOff = "ON/OFF key" +PythonKeyShift = "SHIFT key" +PythonKeyAlpha = "ALPHA key" +PythonKeyXnt = "X,N,T key" +PythonKeyVar = "VAR key" +PythonKeyToolbox = "TOOLBOX key" +PythonKeyBackspace = "BACKSPACE key" +PythonKeyExp = "EXPONENTIAL key" +PythonKeyLn = "NATURAL LOGARITHM key" +PythonKeyLog = "DECIMAL LOGARITHM key" +PythonKeyImaginary = "IMAGINARY I key" +PythonKeyComma = "COMMA key" +PythonKeyPower = "POWER key" +PythonKeySine = "SINE key" +PythonKeyCosine = "COSINE key" +PythonKeyTangent = "TANGENT key" +PythonKeyPi = "PI key" +PythonKeySqrt = "SQUARE ROOT key" +PythonKeySquare = "SQUARE key" +PythonKeySeven = "7 key" +PythonKeyEight = "8 key" +PythonKeyNine = "9 key" +PythonKeyLeftParenthesis = "LEFT PARENTHESIS key" +PythonKeyRightParenthesis = "RIGHT PARENTHESIS key" +PythonKeyFour = "4 key" +PythonKeyFive = "5 key" +PythonKeySix = "6 key" +PythonKeyMultiplication = "MULTIPLICATION key" +PythonKeyDivision = "DIVISION key" +PythonKeyOne = "1 key" +PythonKeyTwo = "2 key" +PythonKeyThree = "3 key" +PythonKeyPlus = "PLUS key" +PythonKeyMinus = "MINUS key" +PythonKeyZero = "0 key" +PythonKeyDot = "DOT key" +PythonKeyEe = "10 POWER X key" +PythonKeyAns = "ANS key" +PythonKeyExe = "EXE key" PythonLdexp = "Return x*(2**i), inverse of frexp" PythonLength = "Length of an object" PythonLgamma = "Log-gamma function" @@ -94,6 +159,14 @@ PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "Fractional and integer parts of x" PythonMonotonic = "Value of a monotonic clock" +PythonNumpyFunction = "numpy module prefix" +PythonNumpyFftFunction = "numpy.fft module prefix" +PythonNumpyLinalgFunction = "numpy.linalg module prefix" +PythonScipyFunction = "scipy module prefix" +PythonScipyLinalgFunction = "scipy.linalg module prefix" +PythonScipyOptimizeFunction = "scipy.optimize module prefix" +PythonScipySignalFunction = "scipy.signal module prefix" +PythonScipySpecialFunction = "scipy.special module prefix" PythonOct = "Convert integer to octal" PythonPhase = "Phase of z" PythonPlot = "Plot y versus x as lines" @@ -119,6 +192,12 @@ PythonShow = "Display the figure" PythonSin = "Sine" PythonSinh = "Hyperbolic sine" PythonSleep = "Suspend the execution for t seconds" +PythonLocalTime = "Convert time into tuple" +PythonMktime = "Convert tuple into time" +PythonTime = "Get the current timestamp" +PythonSetLocaltime = "Set time from a tuple" +PythonRTCmode = "Get current RTC mode" +PythonSetRTCmode = "Set RTC mode" PythonSort = "Sort the list" PythonSqrt = "Square root" PythonSum = "Sum the items of a list" @@ -145,20 +224,25 @@ PythonTurtlePosition = "Return the current (x,y) location" PythonTurtleReset = "Reset the drawing" PythonTurtleRight = "Turn right by a degrees" PythonTurtleSetheading = "Set the orientation to a degrees" -PythonTurtleSetposition = "Positionne la tortue" PythonTurtleShowturtle = "Show the turtle" PythonTurtleSpeed = "Drawing speed between 0 and 10" PythonTurtleWrite = "Display a text" PythonUniform = "Floating point number in [a,b]" PythonImportTime = "Import time module" PythonImportOs = "Import os module" +PythonImportSys = "Import sys module" PythonOsUname = "Get infos about the system" PythonOsGetlogin = "Get username" PythonOsRemove = "Remove file named filename" PythonOsRename = "Rename file oldname to newname" PythonOsListdir = "List files in memory" -PythonTimePrefix = "time module function prefix" -PythonTimeSleep = "Wait for n second" +PythonSysExit = "Terminate current program" +PythonSysPrintexception = "Print exception" +PythonSysByteorder = "The byte order of the system" +PythonSysImplementation = "Information about Python" +PythonSysModules = "Dictionary of loaded modules" +PythonSysVersion = "Python language version (string)" +PythonSysVersioninfo = "Python language version (tuple)" PythonMonotonic = "Return monotonic time" PythonFileOpen = "Opens a file" PythonFileSeekable = "Tells if seek can be used on a file" @@ -176,3 +260,5 @@ PythonFileName = "Contains file's name" PythonFileMode = "Contains file's open mode" PythonFileReadable = "Tells if read can be used on a file" PythonFileWritable = "Tells if write can be used on a file" +PythonImportUtils = "Importing ulab.utils" +PythonUtilsFunction = "utils module function prefix" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index 2dee2b928af..62d5bf2c991 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -1,5 +1,11 @@ PythonPound = "Comment" PythonPercent = "Modulo" +PythonColon = "Colon" +PythonSemicon = "Semicolon" +PythonExclamationMark = "Exclamation mark" +PythonLessThan = "Less than" +PythonGreaterThan = "Greater than" +PythonQuestionMark = "Question mark" Python1J = "Imaginary i" PythonLF = "Line feed" PythonTab = "Tabulation" @@ -45,6 +51,7 @@ PythonCosh = "Hyperbolic cosine" PythonCount = "Count the occurrences of x" PythonDegrees = "Convert x from radians to degrees" PythonDivMod = "Quotient and remainder" +PythonDrawCircle = "Draw a circle" PythonDrawLine = "Draw a line" PythonDrawString = "Display a text from pixel (x,y)" PythonErf = "Error function" @@ -52,13 +59,18 @@ PythonErfc = "Complementary error function" PythonEval = "Return the evaluated expression" PythonExp = "Exponential function" PythonExpm1 = "Compute exp(x)-1" +PythonFactorial = "factorial de x" PythonFabs = "Absolute value" +PythonFillCircle = "Fill a circle" +PythonFillPolygon = "Fill a polygon" PythonFillRect = "Fill a rectangle at pixel (x,y)" PythonFloat = "Convert x to a float" PythonFloor = "Floor" PythonFmod = "a modulo b" PythonFrExp = "Mantissa and exponent of x: (m,e)" PythonGamma = "Gamma function" +PythonGetKeys = "Obtener teclas presionadas" +PythonGetPalette = "Get theme palette" PythonGetPixel = "Return pixel (x,y) color" PythonGetrandbits = "Integer with k random bits" PythonGrid = "Toggle the visibility of the grid" @@ -70,6 +82,8 @@ PythonImportKandinsky = "Import kandinsky module" PythonImportRandom = "Import random module" PythonImportMath = "Import math module" PythonImportMatplotlibPyplot = "Import matplotlib.pyplot module" +PythonImportNumpy = "Import ulab.numpy module" +PythonImportScipy = "Import ulab.scipy module" PythonImportTime = "Import time module" PythonImportTurtle = "Import turtle module" PythonIndex = "Index of the first x occurrence" @@ -80,8 +94,59 @@ PythonIonFunction = "ion module function prefix" PythonIsFinite = "Check if x is finite" PythonIsInfinite = "Check if x is infinity" PythonIsKeyDown = "Return True if the k key is down" +PythonBattery = "Rückgabe der Batteriespannung" +PythonBatteryLevel = "Gibt den Batteriestand zurück" +PythonBatteryIscharging = "Gibt zurück, ob die Batterie geladen wird" +PythonSetBrightness = "Establecer nivel de brillo" +PythonGetBrightness = "Obtener nivel de brillo" PythonIsNaN = "Check if x is a NaN" PythonKandinskyFunction = "kandinsky module function prefix" +PythonKeyLeft = "LEFT ARROW key" +PythonKeyUp = "UP ARROW key" +PythonKeyDown = "DOWN ARROW key" +PythonKeyRight = "RIGHT ARROW key" +PythonKeyOk = "OK key" +PythonKeyBack = "BACK key" +PythonKeyHome = "HOME key" +PythonKeyOnOff = "ON/OFF key" +PythonKeyShift = "SHIFT key" +PythonKeyAlpha = "ALPHA key" +PythonKeyXnt = "X,N,T key" +PythonKeyVar = "VAR key" +PythonKeyToolbox = "TOOLBOX key" +PythonKeyBackspace = "BACKSPACE key" +PythonKeyExp = "EXPONENTIAL key" +PythonKeyLn = "NATURAL LOGARITHM key" +PythonKeyLog = "DECIMAL LOGARITHM key" +PythonKeyImaginary = "IMAGINARY I key" +PythonKeyComma = "COMMA key" +PythonKeyPower = "POWER key" +PythonKeySine = "SINE key" +PythonKeyCosine = "COSINE key" +PythonKeyTangent = "TANGENT key" +PythonKeyPi = "PI key" +PythonKeySqrt = "SQUARE ROOT key" +PythonKeySquare = "SQUARE key" +PythonKeySeven = "7 key" +PythonKeyEight = "8 key" +PythonKeyNine = "9 key" +PythonKeyLeftParenthesis = "LEFT PARENTHESIS key" +PythonKeyRightParenthesis = "RIGHT PARENTHESIS key" +PythonKeyFour = "4 key" +PythonKeyFive = "5 key" +PythonKeySix = "6 key" +PythonKeyMultiplication = "MULTIPLICATION key" +PythonKeyDivision = "DIVISION key" +PythonKeyOne = "1 key" +PythonKeyTwo = "2 key" +PythonKeyThree = "3 key" +PythonKeyPlus = "PLUS key" +PythonKeyMinus = "MINUS key" +PythonKeyZero = "0 key" +PythonKeyDot = "DOT key" +PythonKeyEe = "10 POWER X key" +PythonKeyAns = "ANS key" +PythonKeyExe = "EXE key" PythonLdexp = "Return x*(2**i), inverse of frexp" PythonLength = "Length of an object" PythonLgamma = "Log-gamma function" @@ -94,6 +159,14 @@ PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "Fractional and integer parts of x" PythonMonotonic = "Value of a monotonic clock" +PythonNumpyFunction = "numpy module prefix" +PythonNumpyFftFunction = "numpy.fft module prefix" +PythonNumpyLinalgFunction = "numpy.linalg module prefix" +PythonScipyFunction = "scipy module prefix" +PythonScipyLinalgFunction = "scipy.linalg module prefix" +PythonScipyOptimizeFunction = "scipy.optimize module prefix" +PythonScipySignalFunction = "scipy.signal module prefix" +PythonScipySpecialFunction = "scipy.special module prefix" PythonOct = "Convert integer to octal" PythonPhase = "Phase of z" PythonPlot = "Plot y versus x as lines" @@ -119,6 +192,12 @@ PythonShow = "Display the figure" PythonSin = "Sine" PythonSinh = "Hyperbolic sine" PythonSleep = "Suspend the execution for t seconds" +PythonLocalTime = "Convertir el tiempo en tupla" +PythonMktime = "Convertir tupla en tiempo" +PythonTime = "Obtener la marca de tiempo actual" +PythonSetLocaltime = "Establecer tiempo desde una tupla" +PythonRTCmode = "Obtener el modo RTC actual" +PythonSetRTCmode = "Establecer modo RTC" PythonSort = "Sort the list" PythonSqrt = "Square root" PythonSum = "Sum the items of a list" @@ -145,20 +224,25 @@ PythonTurtlePosition = "Return the current (x,y) location" PythonTurtleReset = "Reset the drawing" PythonTurtleRight = "Turn right by a degrees" PythonTurtleSetheading = "Set the orientation to a degrees" -PythonTurtleSetposition = "Positionne la tortue" PythonTurtleShowturtle = "Show the turtle" PythonTurtleSpeed = "Drawing speed between 0 and 10" PythonTurtleWrite = "Display a text" PythonUniform = "Floating point number in [a,b]" PythonImportTime = "Import time module" PythonImportOs = "Import os module" +PythonImportSys = "Import sys module" PythonOsUname = " Información del sistema " PythonOsGetlogin = "Get username" PythonOsRemove = "Eliminar un archivo" PythonOsRename = "Renombrar archivo" PythonOsListdir = "Archivos de la lista" -PythonTimePrefix = "time module function prefix" -PythonTimeSleep = "Esperar n segundos" +PythonSysExit = "Terminate current program" +PythonSysPrintexception = "Print exception" +PythonSysByteorder = "The byte order of the system" +PythonSysImplementation = "Information about Python" +PythonSysModules = "Dictionary of loaded modules" +PythonSysVersion = "Python language version (string)" +PythonSysVersioninfo = "Python language version (tuple)" PythonMonotonic = "Tiempo monótono de retorno" PythonFileOpen = "Opens a file" PythonFileSeekable = "Tells if seek can be used on a file" @@ -176,3 +260,5 @@ PythonFileName = "Contains file's name" PythonFileMode = "Contains file's open mode" PythonFileReadable = "Tells if read can be used on a file" PythonFileWritable = "Tells if write can be used on a file" +PythonImportUtils = "Importando ulab.utils" +PythonUtilsFunction = "prefijo de función del módulo utils" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index 4ac2560c962..3185c5ce5a8 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -1,5 +1,11 @@ PythonPound = "Commentaire" PythonPercent = "Modulo" +PythonColon = "Deux-points" +PythonSemicon = "Point-virgule" +PythonExclamationMark = "Point d'exclamation" +PythonLessThan = "Inférieur à" +PythonGreaterThan = "Supérieur à" +PythonQuestionMark = "Point d'interrogation" Python1J = "i complexe" PythonLF = "Saut à la ligne" PythonTab = "Tabulation" @@ -45,6 +51,7 @@ PythonCosh = "Cosinus hyperbolique" PythonCount = "Compte les occurrences de x" PythonDegrees = "Conversion de radians en degrés" PythonDivMod = "Quotient et reste" +PythonDrawCircle = "Trace un cercle" PythonDrawLine = "Trace une ligne" PythonDrawString = "Affiche un texte au pixel (x,y)" PythonErf = "Fonction d'erreur" @@ -52,13 +59,18 @@ PythonErfc = "Fonction d'erreur complémentaire" PythonEval = "Evalue l'expression en argument " PythonExp = "Fonction exponentielle" PythonExpm1 = "Calcul de exp(x)-1" +PythonFactorial = "Factorielle de x" PythonFabs = "Valeur absolue" +PythonFillCircle = "Remplit un cercle" +PythonFillPolygon = "Remplit un polygone" PythonFillRect = "Remplit un rectangle" PythonFloat = "Conversion en flottant" PythonFloor = "Partie entière" PythonFmod = "a modulo b" PythonFrExp = "Mantisse et exposant de x : (m,e)" PythonGamma = "Fonction gamma" +PythonGetKeys = "Obtenir les touches pressées" +PythonGetPalette = "Obtient la palette du thème" PythonGetPixel = "Renvoie la couleur du pixel (x,y)" PythonGetrandbits = "Nombre aléatoire sur k bits" PythonGrid = "Affiche ou masque la grille" @@ -70,6 +82,9 @@ PythonImportKandinsky = "Importation du module kandinsky" PythonImportRandom = "Importation du module random" PythonImportMath = "Importation du module math" PythonImportMatplotlibPyplot = "Importation de matplotlib.pyplot" +PythonImportNumpy = "Importation de ulab.numpy" +PythonImportScipy = "Importation de ulab.scipy" +PythonImportUtils = "Importation de ulab.utils" PythonImportTurtle = "Importation du module turtle" PythonImportTime = "Importation du module time" PythonIndex = "Indice première occurrence de x" @@ -80,8 +95,59 @@ PythonIonFunction = "Préfixe fonction module ion" PythonIsFinite = "Teste si x est fini" PythonIsInfinite = "Teste si x est infini" PythonIsKeyDown = "Renvoie True si touche k enfoncée" +PythonBattery = "Renvoie le voltage de la batterie" +PythonBatteryLevel = "Renvoie le niveau de la batterie" +PythonBatteryIscharging = "Chargement en cours" +PythonSetBrightness = "Définir le niveau de luminosité" +PythonGetBrightness = "Obtenir le niveau de luminosité" PythonIsNaN = "Teste si x est NaN" PythonKandinskyFunction = "Préfixe fonction module kandinsky" +PythonKeyLeft = "Touche FLECHE GAUCHE" +PythonKeyUp = "Touche FLECHE HAUT" +PythonKeyDown = "Touche FLECHE BAS" +PythonKeyRight = "Touche FLECHE DROITE" +PythonKeyOk = "Touche OK" +PythonKeyBack = "Touche RETOUR" +PythonKeyHome = "Touche HOME" +PythonKeyOnOff = "Touche ON/OFF" +PythonKeyShift = "Touche SHIFT" +PythonKeyAlpha = "Touche ALPHA" +PythonKeyXnt = "Touche X,N,T" +PythonKeyVar = "Touche VAR" +PythonKeyToolbox = "Touche BOITE A OUTILS" +PythonKeyBackspace = "Touche EFFACER" +PythonKeyExp = "Touche EXPONENTIELLE" +PythonKeyLn = "Touche LOGARITHME NEPERIEN" +PythonKeyLog = "Touche LOGARITHME DECIMAL" +PythonKeyImaginary = "Touche I IMAGINAIRE" +PythonKeyComma = "Touche VIRGULE" +PythonKeyPower = "Touche PUISSANCE" +PythonKeySine = "Touche SINUS" +PythonKeyCosine = "Touche COSINUS" +PythonKeyTangent = "Touche TANGENTE" +PythonKeyPi = "Touche PI" +PythonKeySqrt = "Touche RACINE CARREE" +PythonKeySquare = "Touche CARRE" +PythonKeySeven = "Touche 7" +PythonKeyEight = "Touche 8" +PythonKeyNine = "Touche 9" +PythonKeyLeftParenthesis = "Touche PARENTHESE GAUCHE" +PythonKeyRightParenthesis = "Touche PARENTHESE DROITE" +PythonKeyFour = "Touche 4" +PythonKeyFive = "Touche 5" +PythonKeySix = "Touche 6" +PythonKeyMultiplication = "Touche MULTIPLICATION" +PythonKeyDivision = "Touche DIVISION" +PythonKeyOne = "Touche 1" +PythonKeyTwo = "Touche 2" +PythonKeyThree = "Touche 3" +PythonKeyPlus = "Touche PLUS" +PythonKeyMinus = "Touche MOINS" +PythonKeyZero = "Touche 0" +PythonKeyDot = "Touche POINT" +PythonKeyEe = "Touche 10 PUISSANCE X" +PythonKeyAns = "Touche ANS" +PythonKeyExe = "Touche EXE" PythonLdexp = "Inverse de frexp : x*(2**i)" PythonLength = "Longueur d'un objet" PythonLgamma = "Logarithme de la fonction gamma" @@ -94,6 +160,15 @@ PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "Parties fractionnaire et entière" PythonMonotonic = "Renvoie la valeur de l'horloge" +PythonNumpyFunction = "Préfixe fonction du module numpy" +PythonNumpyFftFunction = "Préfixe fonction du module numpy.fft" +PythonNumpyLinalgFunction = "Préfixe fonction du module numpy.linalg" +PythonScipyFunction = "Préfixe fonction du module scipy" +PythonScipyLinalgFunction = "Préfixe fonction du module scipy.linalg" +PythonScipyOptimizeFunction = "Préfixe fonction du module scipy.optimize" +PythonScipySignalFunction = "Préfixe fonction du module scipy.signal" +PythonScipySpecialFunction = "Préfixe fonction du module scipy.special" +PythonUtilsFunction = "Préfixe fonction du module utils" PythonOct = "Conversion en octal" PythonPhase = "Argument de z" PythonPlot = "Trace y en fonction de x" @@ -109,7 +184,7 @@ PythonRandrange = "Nombre dans range(start,stop)" PythonRangeStartStop = "Liste de start à stop-1" PythonRangeStop = "Liste de 0 à stop-1" PythonRect = "Conversion en algébrique" -PythonRemove = "Supprime le premier x de la liste" +PythonRemove = "Supprime le premier x de la liste" PythonReverse = "Inverse les éléments de la liste" PythonRound = "Arrondi à n décimales" PythonScatter = "Nuage des points (x,y)" @@ -119,6 +194,12 @@ PythonShow = "Affiche la figure" PythonSin = "Sinus" PythonSinh = "Sinus hyperbolique" PythonSleep = "Suspend l'exécution t secondes" +PythonLocalTime = "Convertir le temps en tuple" +PythonMktime = "Convertir le tuple en temps" +PythonTime = "Obtenir l'horodatage actuel" +PythonSetLocaltime = "Définir l'heure à partir d'un tuple" +PythonRTCmode = "Obtenir le mode RTC actuel" +PythonSetRTCmode = "Définir le mode RTC" PythonSort = "Trie la liste" PythonSqrt = "Racine carrée" PythonSum = "Somme des éléments de la liste" @@ -145,20 +226,25 @@ PythonTurtlePosition = "Renvoie la position (x,y)" PythonTurtleReset = "Réinitialise le dessin" PythonTurtleRight = "Pivote de a degrés vers la droite" PythonTurtleSetheading = "Met un cap de a degrés" -PythonTurtleSetposition = "Positionne la tortue" PythonTurtleShowturtle = "Affiche la tortue" PythonTurtleSpeed = "Vitesse du tracé entre 0 et 10" PythonTurtleWrite = "Affiche un texte" PythonUniform = "Nombre décimal dans [a,b]" PythonImportTime = "Importation du module temps" PythonImportOs = "Importation du module os" +PythonImportSys = "Importation du module sys" PythonOsUname = "Donne des infos sur le système" PythonOsGetlogin = "Donne le nom d'utilisateur" PythonOsRemove = "Supprime le fichier nommé filename" PythonOsRename = "Renomme oldname en newname" PythonOsListdir = "Liste les fichiers" -PythonTimePrefix = "Préfixe fonction du module temps" -PythonTimeSleep = "Attendre n secondes" +PythonSysExit = "Termine le programme" +PythonSysPrintexception = "Imprime une exception" +PythonSysByteorder = "L'ordre des octets du système" +PythonSysImplementation = "Information sur Python" +PythonSysModules = "Dictionnaire des modules chargés" +PythonSysVersion = "Version du langage Python (string)" +PythonSysVersioninfo = "Version du langage Python (tuple)" PythonMonotonic = "Retourne le temps monotone" PythonFileOpen = "Ouvre un fichier" PythonFileSeekable = "Indique si seek peut être utilisé" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index 6959839aa06..7dd162659e9 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -1,178 +1,264 @@ -PythonPound = "Megjegyzés" -PythonPercent = "Modulo" -Python1J = "Képzeletbeli i" -PythonLF = "Enter" -PythonTab = "Táblázat" -PythonAmpersand = "Logikus és" -PythonSymbolExp = "logikus exkluzív vagy pedig" -PythonVerticalBar = "logikus vagy pedig" -PythonImag = "z képzeletbeli része" -PythonReal = "z valódi része" -PythonSingleQuote = "apostróf" -PythonAbs = "Abszolút érték/nagyság" -PythonAcos = "Ív (arc) koszinusz" -PythonAcosh = "Hiperbolikus arc koszinusz" -PythonAppend = "Lista végére hozzáadni x-et" -PythonArrow = "(x,y) nyíla (x+dx,y+dy) nyílához" -PythonAsin = "Ív (arc) szinusz" -PythonAsinh = "Hiperbolikus ív (arc) szinusz" -PythonAtan = "Ív (arc) érintö (tan)" -PythonAtan2 = "atan(y/x) sámolása" -PythonAtanh = "Hiperbolikus ív (arc) érintö (atan)" -PythonAxis = "Tengelyeket (xmin,xmax,ymin,ymax)-ra állitani" -PythonBar = "Az x lista oszlopdiagramja" -PythonBin = "Egész szám konvertálása binárisra" -PythonCeil = "Mennyezet" -PythonChoice = "Véletlenszerü szám a listában" -PythonClear = "A lista ürítése" -PythonCmathFunction = "cmath modul funkció elötag" -PythonColor = "Rgb (pzk) szín allítása" -PythonColorBlack = "Fekete szín" -PythonColorBlue = "Kék szín" -PythonColorBrown = "Barna szín" -PythonColorGreen = "Zöld szín" -PythonColorGray = "Szürke szín" -PythonColorOrange = "Narancssárga szín" -PythonColorPink = "Rózsaszín szín" -PythonColorPurple = "Lila szín" -PythonColorRed = "Piros szín" -PythonColorWhite = "Fehér szín" -PythonColorYellow = "Sárga szín" -PythonComplex = "A + ib visszaadása" -PythonCopySign = "X visszaadása y jelével" -PythonCos = "Koszinusz" -PythonCosh = "Hiperbolikus koszinusz" -PythonCount = "Számolja az x elöfordulását" -PythonDegrees = "x konvertálása radiánokrol fokokra" -PythonDivMod = "Hányados és maradék" -PythonDrawLine = "Húzzon egy vonalat " -PythonDrawString = "Szöveg megjelenítése (x, y)-en" -PythonErf = "Hiba funkció" -PythonErfc = "Kiegészítö hiba funkció" -PythonEval = "Visszaadja az értékelt kifejezést" -PythonExp = "Exponenciális függvény" -PythonExpm1 = "exp(x)-1 sámitása" -PythonFabs = "Abszolút érték" -PythonFillRect = "Téglalap töltése" -PythonFloat = "Konvertálása tizedes számra" -PythonFloor = "Egész része" -PythonFmod = "a modulo b" -PythonFrExp = "X mantissája és kiállítója" -PythonGamma = "Gamma funkció" -PythonGetPixel = "Visszatéríti (x,y) színét" -PythonGetrandbits = "Váletlenszám visszatérítése k biten" -PythonGrid = "Rács megjelenítése/elrejtése" -PythonHex = "Decimális szám konvertálása hexadecimális számra" -PythonHist = "x hisztográmiája" -PythonImportCmath = "cmath modul importálása" -PythonImportIon = "Ion modul importálása" -PythonImportKandinsky = "Kandinsky modul importálása" -PythonImportRandom = "Véletlenszerü modul importálása" -PythonImportMath = "math modul importálása" -PythonImportMatplotlibPyplot = "matplotlib.pyplot modul importálása" -PythonImportTurtle = "turtle modul importálása" -PythonImportTime = "time modul importálása" -PythonIndex = "Az elsö x esemény indexe" -PythonInput = "Irjon egy értéket (számot)" -PythonInsert = "x-et i. pozícióra helyezze a listában" -PythonInt = "egész számra konvertálás" -PythonIonFunction = "ion modul funkció elötag" -PythonIsFinite = "x véges-e" -PythonIsInfinite = "x végtelen-e" -PythonIsKeyDown = "True-t válaszol ha a k gomb le van nyomva" -PythonIsNaN = "Ellenörizze hogy x nem NaN" -PythonKandinskyFunction = "kandinsky modul funkció elötag" -PythonLdexp = "frexp ellentéte : x*(2**i)" -PythonLength = "Egy targy hossza" -PythonLgamma = "Gamma funkció logaritmusa" -PythonLog = "a alapú logaritmus" -PythonLog10 = "Decimális logaritmus" -PythonLog2 = "Bináris logaritmus" -PythonMathFunction = "math modul funkció elötag" -PythonMatplotlibPyplotFunction = "matplotlib.pyplot elötag" -PythonMax = "Maximum" -PythonMin = "Minimum" -PythonModf = "x-nek tört és egész részei" -PythonMonotonic = "Az óra értékét adja vissza" -PythonOct = "Decimális szám konvertálása octális számra" -PythonPhase = "z fázisa" -PythonPlot = "y-t jelöli x függvényében" -PythonPolar = "Verctorizálni" -PythonPop = "Az utolsó elemet el törölni" -PythonPower = "x y. kitevö" -PythonPrint = "Ki irni a elemeket" -PythonRadians = "Fokról radiánra konvertálni" -PythonRandint = "Véletlen egész szám [a;b] -ban" -PythonRandom = "Decimális szám [0;1] -ban" -PythonRandomFunction = "random modul funkció elötag" -PythonRandrange = "Véletlen szám range(start,stop)-ban" -PythonRangeStartStop = "start-tol stop-ig listája" -PythonRangeStop = "0 tol stop-ig lista" -PythonRect = "Algebrai számra konvertálni" -PythonRemove = "Elsö x elöfordulását törolni" -PythonReverse = "A lista elemeit megfordítani (másik irány)" -PythonRound = "N számjegyre kerekítni" -PythonScatter = "(x,y) halmaza" -PythonSeed = "Inicializálni a véletlenszám-választót" -PythonSetPixel = "Az (x,y) pixel-t ki szinezni" -PythonShow = "Mutassa az ábrát" -PythonSin = "Szinusz" -PythonSinh = "Hiperbolikus szinusz" -PythonSleep = "t másodpercre meg állitani a programmot" -PythonSort = "A listát rendezni" -PythonSqrt = "Négyzetgyök" -PythonSum = "Összeadni a lista elemeit" -PythonTan = "Érintö (tan)" -PythonTanh = "Hiperbolikus érintö (tan)" -PythonText = "(x,y) nél egy szöveget irni" -PythonTimeFunction = "time funkció elötag" -PythonTrunc = "Egész csonka (?)" -PythonTurtleBackward = "x pixelt hátra" -PythonTurtleCircle = "r pixel sugarú kört rajzolni" -PythonTurtleColor = "Toll szinét beállitani" -PythonTurtleColorMode = "Szin módot 1.0-ra vagy 255-ra állitani" -PythonTurtleForward = "x pixelt elölre" -PythonTurtleFunction = "turtle modul funkció elötag" -PythonTurtleGoto = "Menjen a (x,y) koordinátákra" -PythonTurtleHeading = "Visszaadja az aktuális irányt" -PythonTurtleHideturtle = "A teknös elrejtése" -PythonTurtleIsdown = "True-t válaszol ha a toll irás pozícióban van" -PythonTurtleLeft = "a fokkot forduljon balra" -PythonTurtlePendown = "Húzza le a tollat" -PythonTurtlePensize = "Állítsa a vonalvastagságot x pixelre" -PythonTurtlePenup = "Húzza fel a tollat" -PythonTurtlePosition = "Az aktuális (x,y) pozíciót visszaadása" -PythonTurtleReset = "Visszaállitani a rajzot (torléssel)" -PythonTurtleRight = "a fokkot forduljon jobbra" -PythonTurtleSetheading = "a fokokra állítja be az irányt" -PythonTurtleSetposition = "A teknös pozicioját allitja" -PythonTurtleShowturtle = "A teknöst meg mutatni" -PythonTurtleSpeed = "Rajzolási sebesség 0 és 10 között" -PythonTurtleWrite = "Szöveg irás" -PythonUniform = "Lebegöpontos szám [a,b] -ban" -PythonImportTime = "time modul importálása" -PythonTimePrefix = "time funkció elötag" -PythonTimeSleep = "n másodpercet várni" -PythonMonotonic = "Meg fordítani a monoton idö" -PythonFileOpen = "Fájl megnyitása" -PythonFileSeekable = "Seek-et lehete használni" -PythonFileSeek = "A kurzort áthelyezni" -PythonFileTell = "Visszaadja a kurzor helye" -PythonFileClose = "Bezárni egy fájlt" -PythonFileClosed = "True ha a fájl bezárva" -PythonFileRead = "Olvas 16 bájtig" -PythonFileWrite = "b-t irjon a fájlba" -PythonFileReadline = "Olvas egy sort vagy 16 bájtig" -PythonFileReadlines = "Olvas több sort" -PythonFileTruncate = "A fájl átméretezése" -PythonFileWritelines = "Irjon több sort" -PythonFileName = "A fájl neve" -PythonFileMode = "A fájl nyitott módja" -PythonFileReadable = "read-et lehete használni" -PythonFileWritable = "write-ot lehete használni" -PythonImportOs = "os modul importálása" -PythonOsUname = "Rendszer informaciók" -PythonOsGetlogin = "Get username" -PythonOsRemove = "Fájl törlése" -PythonOsRename = "Fájl átnevezése" -PythonOsListdir = "Fájlok listája" +PythonPound = "Megjegyzés" +PythonPercent = "Modulo" +PythonColon = "Kettőspont" +PythonSemicon = "Pontosvessző" +PythonExclamationMark = "Felkiáltójel" +PythonLessThan = "Kisebb mint" +PythonGreaterThan = "Nagyobb mint" +PythonQuestionMark = "Kérdőjel" +Python1J = "Képzeletbeli i" +PythonLF = "Enter" +PythonTab = "Táblázat" +PythonAmpersand = "Logikus és" +PythonSymbolExp = "logikus exkluzív vagy pedig" +PythonVerticalBar = "logikus vagy pedig" +PythonImag = "z képzeletbeli része" +PythonReal = "z valódi része" +PythonSingleQuote = "apostróf" +PythonAbs = "Abszolút érték/nagyság" +PythonAcos = "Ív (arc) koszinusz" +PythonAcosh = "Hiperbolikus arc koszinusz" +PythonAppend = "Lista végére hozzáadni x-et" +PythonArrow = "(x,y) nyíla (x+dx,y+dy) nyílához" +PythonAsin = "Ív (arc) szinusz" +PythonAsinh = "Hiperbolikus ív (arc) szinusz" +PythonAtan = "Ív (arc) érintö (tan)" +PythonAtan2 = "atan(y/x) sámolása" +PythonAtanh = "Hiperbolikus ív (arc) érintö (atan)" +PythonAxis = "Tengelyeket (xmin,xmax,ymin,ymax)-ra állitani" +PythonBar = "Az x lista oszlopdiagramja" +PythonBin = "Egész szám konvertálása binárisra" +PythonCeil = "Mennyezet" +PythonChoice = "Véletlenszerü szám a listában" +PythonClear = "A lista ürítése" +PythonCmathFunction = "cmath modul funkció elötag" +PythonColor = "Rgb (pzk) szín allítása" +PythonColorBlack = "Fekete szín" +PythonColorBlue = "Kék szín" +PythonColorBrown = "Barna szín" +PythonColorGreen = "Zöld szín" +PythonColorGray = "Szürke szín" +PythonColorOrange = "Narancssárga szín" +PythonColorPink = "Rózsaszín szín" +PythonColorPurple = "Lila szín" +PythonColorRed = "Piros szín" +PythonColorWhite = "Fehér szín" +PythonColorYellow = "Sárga szín" +PythonComplex = "A + ib visszaadása" +PythonCopySign = "X visszaadása y jelével" +PythonCos = "Koszinusz" +PythonCosh = "Hiperbolikus koszinusz" +PythonCount = "Számolja az x elöfordulását" +PythonDegrees = "x konvertálása radiánokrol fokokra" +PythonDivMod = "Hányados és maradék" +PythonDrawCircle = "Rajzolj egy kört" +PythonDrawLine = "Húzzon egy vonalat " +PythonDrawString = "Szöveg megjelenítése (x, y)-en" +PythonErf = "Hiba funkció" +PythonErfc = "Kiegészítö hiba funkció" +PythonEval = "Visszaadja az értékelt kifejezést" +PythonExp = "Exponenciális függvény" +PythonExpm1 = "exp(x)-1 sámitása" +PythonFactorial = "x faktorál" +PythonFabs = "Abszolút érték" +PythonFillRect = "Téglalap töltése" +PythonFillCircle = "Kitölti a kört" +PythonFillPolygon = "Kitölti a poligont" +PythonFloat = "Konvertálása tizedes számra" +PythonFloor = "Egész része" +PythonFmod = "a modulo b" +PythonFrExp = "X mantissája és kiállítója" +PythonGamma = "Gamma funkció" +PythonGetKeys = "Billentyűk lenyomva" +PythonGetPalette = "Téma paletta beszerzése" +PythonGetPixel = "Visszatéríti (x,y) színét" +PythonGetrandbits = "Váletlenszám visszatérítése k biten" +PythonGrid = "Rács megjelenítése/elrejtése" +PythonHex = "Decimális szám konvertálása hexadecimális számra" +PythonHist = "x hisztográmiája" +PythonImportCmath = "cmath modul importálása" +PythonImportIon = "Ion modul importálása" +PythonImportKandinsky = "Kandinsky modul importálása" +PythonImportRandom = "Véletlenszerü modul importálása" +PythonImportMath = "math modul importálása" +PythonImportMatplotlibPyplot = "matplotlib.pyplot modul importálása" +PythonImportNumpy = "ulab.numpy modul importálása" +PythonImportScipy = "ulab.scipy modul importálása" +PythonImportTurtle = "turtle modul importálása" +PythonImportTime = "time modul importálása" +PythonIndex = "Az elsö x esemény indexe" +PythonInput = "Irjon egy értéket (számot)" +PythonInsert = "x-et i. pozícióra helyezze a listában" +PythonInt = "egész számra konvertálás" +PythonIonFunction = "ion modul funkció elötag" +PythonIsFinite = "x véges-e" +PythonIsInfinite = "x végtelen-e" +PythonIsKeyDown = "True-t válaszol ha a k gomb le van nyomva" +PythonBattery = "Az akkumulátor feszültségének visszaadása" +PythonBatteryLevel = "Az akkumulátor töltöttségi szintjének visszaadása" +PythonBatteryIscharging = "Visszaadja, ha az akkumulátor töltődik" +PythonSetBrightness = "Fényerőszint beállítása" +PythonGetBrightness = "Get brightness level" +PythonIsNaN = "Ellenörizze hogy x nem NaN" +PythonKandinskyFunction = "kandinsky modul funkció elötag" +PythonLdexp = "frexp ellentéte : x*(2**i)" +PythonLength = "Egy targy hossza" +PythonLgamma = "Gamma funkció logaritmusa" +PythonLog = "a alapú logaritmus" +PythonLog10 = "Decimális logaritmus" +PythonLog2 = "Bináris logaritmus" +PythonMathFunction = "math modul funkció elötag" +PythonMatplotlibPyplotFunction = "matplotlib.pyplot elötag" +PythonMax = "Maximum" +PythonMin = "Minimum" +PythonModf = "x-nek tört és egész részei" +PythonMonotonic = "Az óra értékét adja vissza" +PythonNumpyFunction = "numpy elötag" +PythonNumpyFftFunction = "numpy.fft elötag" +PythonNumpyLinalgFunction = "numpy.linalg elötag" +PythonScipyFunction = "scipy elötag" +PythonScipyLinalgFunction = "scipy.linalg elötag" +PythonScipyOptimizeFunction = "scipy.optimize elötag" +PythonScipySignalFunction = "scipy.signal elötag" +PythonScipySpecialFunction = "scipy.special elötag" +PythonOct = "Decimális szám konvertálása octális számra" +PythonPhase = "z fázisa" +PythonPlot = "y-t jelöli x függvényében" +PythonPolar = "Verctorizálni" +PythonPop = "Az utolsó elemet el törölni" +PythonPower = "x y. kitevö" +PythonPrint = "Ki irni a elemeket" +PythonRadians = "Fokról radiánra konvertálni" +PythonRandint = "Véletlen egész szám [a;b] -ban" +PythonRandom = "Decimális szám [0;1] -ban" +PythonRandomFunction = "random modul funkció elötag" +PythonRandrange = "Véletlen szám range(start,stop)-ban" +PythonRangeStartStop = "start-tol stop-ig listája" +PythonRangeStop = "0 tol stop-ig lista" +PythonRect = "Algebrai számra konvertálni" +PythonRemove = "Elsö x elöfordulását törolni" +PythonReverse = "A lista elemeit megfordítani (másik irány)" +PythonRound = "N számjegyre kerekítni" +PythonScatter = "(x,y) halmaza" +PythonSeed = "Inicializálni a véletlenszám-választót" +PythonSetPixel = "Az (x,y) pixel-t ki szinezni" +PythonShow = "Mutassa az ábrát" +PythonSin = "Szinusz" +PythonSinh = "Hiperbolikus szinusz" +PythonSleep = "t másodpercre meg állitani a programmot" +PythonLocalTime = "Idő konvertálása csomóvá" +PythonMktime = "A tuple konvertálása az időben" +PythonTime = "Az aktuális időbélyeg letöltése" +PythonSetLocaltime = "Idő beállítása egy csomóból" +PythonRTCmode = "Aktuális RTC mód" +PythonSetRTCmode = "RTC mód beállítása" +PythonSort = "A listát rendezni" +PythonSqrt = "Négyzetgyök" +PythonSum = "Összeadni a lista elemeit" +PythonTan = "Érintö (tan)" +PythonTanh = "Hiperbolikus érintö (tan)" +PythonText = "(x,y) nél egy szöveget irni" +PythonTimeFunction = "time funkció elötag" +PythonTrunc = "Egész csonka (?)" +PythonTurtleBackward = "x pixelt hátra" +PythonTurtleCircle = "r pixel sugarú kört rajzolni" +PythonTurtleColor = "Toll szinét beállitani" +PythonTurtleColorMode = "Szin módot 1.0-ra vagy 255-ra állitani" +PythonTurtleForward = "x pixelt elölre" +PythonTurtleFunction = "turtle modul funkció elötag" +PythonTurtleGoto = "Menjen a (x,y) koordinátákra" +PythonTurtleHeading = "Visszaadja az aktuális irányt" +PythonTurtleHideturtle = "A teknös elrejtése" +PythonTurtleIsdown = "True-t válaszol ha a toll irás pozícióban van" +PythonTurtleLeft = "a fokkot forduljon balra" +PythonTurtlePendown = "Húzza le a tollat" +PythonTurtlePensize = "Állítsa a vonalvastagságot x pixelre" +PythonTurtlePenup = "Húzza fel a tollat" +PythonTurtlePosition = "Az aktuális (x,y) pozíciót visszaadása" +PythonTurtleReset = "Visszaállitani a rajzot (torléssel)" +PythonTurtleRight = "a fokkot forduljon jobbra" +PythonTurtleSetheading = "a fokokra állítja be az irányt" +PythonTurtleShowturtle = "A teknöst meg mutatni" +PythonTurtleSpeed = "Rajzolási sebesség 0 és 10 között" +PythonTurtleWrite = "Szöveg irás" +PythonUniform = "Lebegöpontos szám [a,b] -ban" +PythonImportTime = "time modul importálása" +PythonMonotonic = "Meg fordítani a monoton idö" +PythonFileOpen = "Fájl megnyitása" +PythonFileSeekable = "Seek-et lehete használni" +PythonFileSeek = "A kurzort áthelyezni" +PythonFileTell = "Visszaadja a kurzor helye" +PythonFileClose = "Bezárni egy fájlt" +PythonFileClosed = "True ha a fájl bezárva" +PythonFileRead = "Olvas 16 bájtig" +PythonFileWrite = "b-t irjon a fájlba" +PythonFileReadline = "Olvas egy sort vagy 16 bájtig" +PythonFileReadlines = "Olvas több sort" +PythonFileTruncate = "A fájl átméretezése" +PythonFileWritelines = "Irjon több sort" +PythonFileName = "A fájl neve" +PythonFileMode = "A fájl nyitott módja" +PythonFileReadable = "read-et lehete használni" +PythonFileWritable = "write-ot lehete használni" +PythonImportOs = "os modul importálása" +PythonOsUname = "Rendszer informaciók" +PythonOsGetlogin = "Get username" +PythonOsRemove = "Fájl törlése" +PythonOsRename = "Fájl átnevezése" +PythonOsListdir = "Fájlok listája" +PythonImportSys = "sys modul importálása" +PythonSysExit = "Terminate current program" +PythonSysPrintexception = "Print exception" +PythonSysByteorder = "The byte order of the system" +PythonSysImplementation = "Information about Python" +PythonSysModules = "Dictionary of loaded modules" +PythonSysVersion = "Python language version (string)" +PythonSysVersioninfo = "Python language version (tuple)" +PythonKeyLeft = "BALRA NYÍL billentyű" +PythonKeyUp = "FEL NYÍL billentyű" +PythonKeyDown = "LE NYÍL billentyű" +PythonKeyRight = "JOBBRA NYÍL billentyű" +PythonKeyOk = "OK gomb" +PythonKeyBack = "VISSZA gomb" +PythonKeyHome = "Lakáskulcs" +PythonKeyOnOff = "BE/KI gomb" +PythonKeyShift = "SHIFT billentyű" +PythonKeyAlpha = "ALPHA kulcs" +PythonKeyXnt = "X,N,T gomb" +PythonKeyVar = "VAR gomb" +PythonKeyToolbox = "TOOLBOX gomb" +PythonKeyBackspace = "BACKSPACE billentyű" +PythonKeyExp = "EXPONENTIÁLIS kulcs" +PythonKeyLn = "TERMÉSZETES LOGARITMUS kulcs" +PythonKeyLog = "DECIMÁLIS LOGARITMUS billentyű" +PythonKeyImaginary = "KÉPZELETES I kulcs" +PythonKeyComma = "VESZSŰ gomb" +PythonKeyPower = "POWER gomb" +PythonKeySine = "SINE gomb" +PythonKeyCosine = "KOSINUS kulcs" +PythonKeyTangent = "ÉRINTŐ gomb" +PythonKeyPi = "PI kulcs" +PythonKeySqrt = "NÉGYGYÖK kulcs" +PythonKeySquare = "SZÖGZET billentyű" +PythonKeySeven = "7 kulcs" +PythonKeyEight = "8 kulcs" +PythonKeyNine = "9 kulcs" +PythonKeyLeftParenthesis = "BAL ZÁRÓZELŐ gomb" +PythonKeyRightParenthesis = "JOBB ZÁRÓZELŐ billentyű" +PythonKeyFour = "4 kulcs" +PythonKeyFive = "5 kulcs" +PythonKeySix = "6 kulcs" +PythonKeyMultiplication = "SZORZAT gomb" +PythonKeyDivision = "OSZTÁS kulcs" +PythonKeyOne = "1 kulcs" +PythonKeyTwo = "2 kulcs" +PythonKeyThree = "3 kulcs" +PythonKeyPlus = "PLUSZ kulcs" +PythonKeyMinus = "MÍNUS gomb" +PythonKeyZero = "0 kulcs" +PythonKeyDot = "DOT gomb" +PythonKeyEe = "10 POWER X gomb" +PythonKeyAns = "ANS kulcs" +PythonKeyExe = "EXE kulcs" +PythonImportUtils = "Az ulab.utils importálása" +PythonUtilsFunction = "utils modul függvény előtagja" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n index 6c174b53c9e..d839de5418a 100644 --- a/apps/code/catalog.it.i18n +++ b/apps/code/catalog.it.i18n @@ -1,5 +1,11 @@ PythonPound = "Commento" PythonPercent = "Modulo" +PythonColon = "Due punti" +PythonSemicon = "Punto e virgola" +PythonExclamationMark = "Punto esclamativo" +PythonLessThan = "Minore di" +PythonGreaterThan = "Maggiore di" +PythonQuestionMark = "Punto interrogativo" Python1J = "Unità immaginaria" PythonLF = "Nuova riga" PythonTab = "Tabulazione" @@ -25,7 +31,7 @@ PythonBin = "Converte un intero in binario" PythonCeil = "Parte intera superiore" PythonChoice = "Numero aleatorio nella lista" PythonClear = "Svuota la lista" -PythonCmathFunction = "Funz. prefissata modulo cmath" +PythonCmathFunction = "Prefisso funzione del modulo cmath" PythonColor = "Definisci un colore rvb" PythonColorBlack = "Colore nero" PythonColorBlue = "Colore blu" @@ -45,6 +51,7 @@ PythonCosh = "Coseno iperbolico" PythonCount = "Conta le ricorrenze di x" PythonDegrees = "Conversione di radianti in gradi" PythonDivMod = "Quoziente e resto" +PythonDrawCircle = "Disegnare un cerchio" PythonDrawLine = "Disegna una linea" PythonDrawString = "Visualizza il testo dal pixel x,y" PythonErf = "Funzione d'errore" @@ -52,13 +59,18 @@ PythonErfc = "Funzione d'errore complementare" PythonEval = "Valuta l'espressione nell'argomento " PythonExp = "Funzione esponenziale" PythonExpm1 = "Calcola exp(x)-1" +PythonFactorial = "Fattoriale di x" PythonFabs = "Valore assoluto" +PythonFillCircle = "Riempire un cerchio" +PythonFillPolygon = "Riempire un poligono" PythonFillRect = "Riempie un rettangolo" PythonFloat = "Conversione in flottanti" PythonFloor = "Parte intera" PythonFmod = "a modulo b" PythonFrExp = "Mantissa ed esponente di x : (m,e)" PythonGamma = "Funzione gamma" +PythonGetKeys = "Ottieni i tasti premuti" +PythonGetPalette = "Ottieni la tavolozza del tema" PythonGetPixel = "Restituisce colore del pixel(x,y)" PythonGetrandbits = "Numero aleatorio con k bit" PythonGrid = "Attiva la visibilità della griglia" @@ -70,24 +82,85 @@ PythonImportKandinsky = "Importa modulo kandinsky" PythonImportRandom = "Importa modulo random" PythonImportMath = "Importa modulo math" PythonImportMatplotlibPyplot = "Importa modulo matplotlib.pyplot" +PythonImportNumpy = "Importa modulo ulab.numpy" +PythonImportScipy = "Importa modulo ulab.scipy" PythonImportTurtle = "Importa del modulo turtle" PythonImportTime = "Importa del modulo time" PythonImportOs = "Importa modulo os" PythonOsUname = "Ottieni informazioni sul sistema" -PythonOsGetlogin = "Get username" +PythonOsGetlogin = "Ottieni username" PythonOsRemove = "Rimuovere un file" PythonOsRename = "Rinomina file" PythonOsListdir = "Elenca file" +PythonImportSys = "Importa modulo sys" +PythonSysExit = "Termina programma corrente" +PythonSysPrintexception = "Stampa eccezione" +PythonSysByteorder = "Ordine di byte del sistema" +PythonSysImplementation = "Informazioni su Python" +PythonSysModules = "Dizionario dei moduli caricati" +PythonSysVersion = "Versione linguaggio Python (stringa)" +PythonSysVersioninfo = "Versione linguaggio Python (tupla)" PythonIndex = "Indice prima occorrenza di x" PythonInput = "Inserire un valore" PythonInsert = "Inserire x in posizione i-esima" PythonInt = "Conversione in intero" PythonIonFunction = "Prefisso di funzione modulo ion" PythonIsFinite = "Testa se x è finito" -PythonIsInfinite = "Testa se x est infinito" +PythonIsInfinite = "Testa se x è infinito" PythonIsKeyDown = "Restituisce True premendo tasto k" +PythonBattery = "Restituisce la tensione della batteria" +PythonBatteryLevel = "Restituisce il livello della batteria" +PythonBatteryIscharging = "Restituisce se la batteria è in carica" +PythonSetBrightness = "Imposta livello di luminosità" +PythonGetBrightness = "Ottieni livello di luminosità" PythonIsNaN = "Testa se x è NaN" PythonKandinskyFunction = "Prefisso funzione modulo kandinsky" +PythonKeyLeft = "Tasto FRECCIA SINISTRA" +PythonKeyUp = "Tasto FRECCIA ALTO" +PythonKeyDown = "Tasto FRECCIA BASSO" +PythonKeyRight = "Tasto FRECCIA DESTRA" +PythonKeyOk = "Tasto OK" +PythonKeyBack = "Tasto INDIETRO" +PythonKeyHome = "Tasto CASA" +PythonKeyOnOff = "Tasto ON/OFF" +PythonKeyShift = "Tasto SHIFT" +PythonKeyAlpha = "Tasto ALPHA" +PythonKeyXnt = "Tasto X,N,T" +PythonKeyVar = "Tasto VAR" +PythonKeyToolbox = "Tasto TOOLBOX" +PythonKeyBackspace = "Tasto CANCELLA" +PythonKeyExp = "Tasto ESPONENZIALE" +PythonKeyLn = "Tasto LOGARITMO NEPERIANO" +PythonKeyLog = "Tasto LOGARITMO DECIMALE" +PythonKeyImaginary = "Tasto I IMMAGINE" +PythonKeyComma = "Tasto VIRGOLA" +PythonKeyPower = "Tasto POTENZA" +PythonKeySine = "Tasto SENO" +PythonKeyCosine = "Tasto COSENO" +PythonKeyTangent = "Tasto TANGENTE" +PythonKeyPi = "Tasto PI" +PythonKeySqrt = "Tasto RADICE QUADRATA" +PythonKeySquare = "Tasto QUADRATO" +PythonKeySeven = "Tasto 7" +PythonKeyEight = "Tasto 8" +PythonKeyNine = "Tasto 9" +PythonKeyLeftParenthesis = "Tasto PARENTESI SINISTRA" +PythonKeyRightParenthesis = "Tasto PARENTESI DESTRA" +PythonKeyFour = "Tasto 4" +PythonKeyFive = "Tasto 5" +PythonKeySix = "Tasto 6" +PythonKeyMultiplication = "Tasto MOLTIPLICAZIONE" +PythonKeyDivision = "Tasto DIVISIONE" +PythonKeyOne = "Tasto 1" +PythonKeyTwo = "Tasto 2" +PythonKeyThree = "Tasto 3" +PythonKeyPlus = "Tasto PIÙ" +PythonKeyMinus = "Tasto MENO" +PythonKeyZero = "Tasto 0" +PythonKeyDot = "Tasto PUNTO" +PythonKeyEe = "Tasto 10 POTENZA X" +PythonKeyAns = "Tasto ANS" +PythonKeyExe = "Tasto EXE" PythonLdexp = "Inversa di frexp : x*(2**i)" PythonLength = "Longhezza di un oggetto" PythonLgamma = "Logaritmo della funzione gamma" @@ -100,6 +173,14 @@ PythonMax = "Massimo" PythonMin = "Minimo" PythonModf = "Parti frazionarie e intere" PythonMonotonic = "Restituisce il valore dell'orologio" +PythonNumpyFunction = "Prefisso modulo numpy" +PythonNumpyFftFunction = "Prefisso modulo numpy.fft" +PythonNumpyLinalgFunction = "Prefisso modulo numpy.linalg" +PythonScipyFunction = "Prefisso modulo scipy" +PythonScipyLinalgFunction = "Prefisso modulo scipy.linalg" +PythonScipyOptimizeFunction = "Prefisso modulo scipy.optimize" +PythonScipySignalFunction = "Prefisso modulo scipy.signal" +PythonScipySpecialFunction = "Prefisso modulo scipy.special" PythonOct = "Conversione in ottale" PythonPhase = "Argomento di z" PythonPlot = "Disegna y in f. di x come linee" @@ -115,7 +196,7 @@ PythonRandrange = "Numero dentro il range(start, stop)" PythonRangeStartStop = "Lista da start a stop-1" PythonRangeStop = "Lista da 0 a stop-1" PythonRect = "Converte in coordinate algebriche" -PythonRemove = "Cancella la prima x dalla lista" +PythonRemove = "Cancella la prima x dalla lista" PythonReverse = "Inverte gli elementi della lista" PythonRound = "Arrotondato a n cifre decimali" PythonScatter = "Diagramma dispersione y in f. di x" @@ -125,6 +206,12 @@ PythonShow = "Mostra la figura" PythonSin = "Seno" PythonSinh = "Seno iperbolico" PythonSleep = "Sospende l'esecuzione t secondi" +PythonLocalTime = "Converti il tempo in tuple" +PythonMktime = "Converti tuple in tempo" +PythonTime = "Ottieni il timestamp corrente" +PythonSetLocaltime = "Imposta il tempo da una tupla" +PythonRTCmode = "Ottieni la modalità RTC corrente" +PythonSetRTCmode = "Imposta modalità RTC" PythonSort = "Ordina l'elenco" PythonSqrt = "Radice quadrata" PythonSum = "Somma degli elementi della lista" @@ -151,28 +238,27 @@ PythonTurtlePosition = "Fornisce posizione corrente (x,y)" PythonTurtleReset = "Azzera il disegno" PythonTurtleRight = "Ruota di a gradi a destra" PythonTurtleSetheading = "Imposta l'orientamento per a gradi" -PythonTurtleSetposition = "Posiziona la tartaruga" PythonTurtleShowturtle = "Mostra la tartaruga" PythonTurtleSpeed = "Velocità di disegno (x tra 0 e 10)" PythonTurtleWrite = "Mostra un testo" PythonUniform = "Numero decimale tra [a,b]" -PythonImportTime = "Import time module" -PythonTimePrefix = "time module function prefix" -PythonTimeSleep = "Wait for n second" -PythonMonotonic = "Return monotonic time" -PythonFileOpen = "Opens a file" -PythonFileSeekable = "Tells if seek can be used on a file" -PythonFileSeek = "Move file's cursor" -PythonFileTell = "Get file's cursor location" -PythonFileClose = "Closes a file" -PythonFileClosed = "True if file was closed" -PythonFileRead = "Read up to size bytes" -PythonFileWrite = "Write b into file" -PythonFileReadline = "Reads a line or up to size bytes" -PythonFileReadlines = "Reads a list of lines" -PythonFileTruncate = "Resize the file to size" -PythonFileWritelines = "Writes a list of lines" -PythonFileName = "Contains file's name" -PythonFileMode = "Contains file's open mode" -PythonFileReadable = "Tells if read can be used on a file" -PythonFileWritable = "Tells if write can be used on a file" +PythonImportTime = "Importa modulo time" +PythonMonotonic = "Restituisce tempo monotonico" +PythonFileOpen = "Apre un file" +PythonFileSeekable = "Dice se si può usare il cursore su un file" +PythonFileSeek = "Sposta cursore di un file" +PythonFileTell = "Ottieni posizione del cursore del file" +PythonFileClose = "Chiude un file" +PythonFileClosed = "True se un file è stato chiuso" +PythonFileRead = "Legge fino ai byte di un file" +PythonFileWrite = "Scrive b in un file" +PythonFileReadline = "Legge una riga o fino ai byte di un file" +PythonFileReadlines = "Legge una lista di linee di un file" +PythonFileTruncate = "Ridimensiona il file" +PythonFileWritelines = "Scrive una lista di linee su file" +PythonFileName = "Contiene il nome del file" +PythonFileMode = "Contiene la modalità di apertura del file" +PythonFileReadable = "Dice se si può leggere sul file" +PythonFileWritable = "Dice se si può scrivere sul file" +PythonImportUtils = "Importazione di ulab.utils" +PythonUtilsFunction = "Prefisso funzione del modulo utils" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n index cc788ee135e..4ba8d9906c5 100644 --- a/apps/code/catalog.nl.i18n +++ b/apps/code/catalog.nl.i18n @@ -1,5 +1,11 @@ PythonPound = "Opmerkingen" PythonPercent = "Modulo" +PythonColon = "Dubbele punt" +PythonSemicon = "Puntkomma" +PythonExclamationMark = "Uitroepteken" +PythonLessThan = "Kleiner dan" +PythonGreaterThan = "Groter dan" +PythonQuestionMark = "Vraagteken" Python1J = "Imaginaire i" PythonLF = "Nieuwe regel" PythonTab = "Tabulatie" @@ -45,6 +51,7 @@ PythonCosh = "Cosinus hyperbolicus" PythonCount = "Tel voorkomen van x" PythonDegrees = "Zet x om van radialen naar graden" PythonDivMod = "Quotiënt en rest" +PythonDrawCircle = "Teken een cirkel" PythonDrawLine = "Teken een lijn" PythonDrawString = "Geef een tekst weer van pixel (x,y)" PythonErf = "Error functie" @@ -52,13 +59,18 @@ PythonErfc = "Complementaire error functie" PythonEval = "Geef de geëvalueerde uitdrukking" PythonExp = "Exponentiële functie" PythonExpm1 = "Bereken exp(x)-1" +PythonFactorial = "faculteit van x" PythonFabs = "Absolute waarde" +PythonFillCircle = "Vul een cirkel" +PythonFillPolygon = "Vul een veelhoek" PythonFillRect = "Vul een rechthoek bij pixel (x,y)" PythonFloat = "Zet x om in een float" PythonFloor = "Vloer" PythonFmod = "a modulo b" PythonFrExp = "Mantisse en exponent van x: (m,e)" PythonGamma = "Gammafunctie" +PythonGetKeys = "Get toetsen ingedrukt" +PythonGetPalette = "Thema palet krijgen" PythonGetPixel = "Geef pixel (x,y) kleur (rgb)" PythonGetrandbits = "Integer met k willekeurige bits" PythonGrid = "Verander zichtbaarheid raster" @@ -70,6 +82,8 @@ PythonImportKandinsky = "Importeer kandinsky module" PythonImportRandom = "Importeer random module" PythonImportMath = "Importeer math module" PythonImportMatplotlibPyplot = "Importeer matplotlib.pyplot module" +PythonImportNumpy = "Importeer ulab.numpy module" +PythonImportScipy = "Importeer ulab.scipy module" PythonImportTime = "Importeer time module" PythonImportOs = "Importeer os module" PythonOsUname = " Krijg systeeminfo" @@ -77,6 +91,15 @@ PythonOsGetlogin = "Get username" PythonOsRemove = "Een bestand verwijderen" PythonOsRename = "Hernoem bestand" PythonOsListdir = "Lijstbestanden" +PythonImportSys = "Importeer sys module" +PythonImportSys = "Importeer sys module" +PythonSysExit = "Terminate current program" +PythonSysPrintexception = "Print exception" +PythonSysByteorder = "The byte order of the system" +PythonSysImplementation = "Information about Python" +PythonSysModules = "Dictionary of loaded modules" +PythonSysVersion = "Python language version (string)" +PythonSysVersioninfo = "Python language version (tuple)" PythonImportTurtle = "Importeer turtle module" PythonIndex = "Index van de eerste x aanwezigheden" PythonInput = "Wijs een waarde toe" @@ -86,8 +109,59 @@ PythonIonFunction = "ion module voorvoegsel" PythonIsFinite = "Controleer of x eindig is" PythonIsInfinite = "Controleer of x oneindig is" PythonIsKeyDown = "Geef True als k toets omlaag is" +PythonBattery = "Return batterijspanning" +PythonBatteryLevel = "Batterijniveau teruggeven" +PythonBatteryIscharging = "Keer terug als de batterij wordt opgeladen" +PythonSetBrightness = "Set brightness level" +PythonGetBrightness = "Get brightness level" PythonIsNaN = "Controleer of x geen getal is" PythonKandinskyFunction = "kandinsky module voorvoegsel" +PythonKeyLeft = "PIJL NAAR LINKS toets" +PythonKeyUp = "PIJL OMHOOG toets" +PythonKeyDown = "PIJL OMLAAG toets" +PythonKeyRight = "PIJL NAAR RECHTS toets" +PythonKeyOk = "OK toets" +PythonKeyBack = "TERUG toets" +PythonKeyHome = "HOME toets" +PythonKeyOnOff = "AAN/UIT toets" +PythonKeyShift = "SHIFT toets" +PythonKeyAlpha = "ALPHA toets" +PythonKeyXnt = "X,N,T toets" +PythonKeyVar = "VAR toets" +PythonKeyToolbox = "TOOLBOX toets" +PythonKeyBackspace = "BACKSPACE toets" +PythonKeyExp = "EXPONENTIEEL toets" +PythonKeyLn = "NATUURLIJKE LOGARITME toets" +PythonKeyLog = "BRIGGSE LOGARITME toets" +PythonKeyImaginary = "IMAGINAIRE I toets" +PythonKeyComma = "KOMMA toets" +PythonKeyPower = "MACHT toets" +PythonKeySine = "SINUS toets" +PythonKeyCosine = "COSINUS toets" +PythonKeyTangent = "TANGENS toets" +PythonKeyPi = "PI toets" +PythonKeySqrt = "VIERKANTSWORTEL toets" +PythonKeySquare = "KWADRAAT toets" +PythonKeySeven = "7 toets" +PythonKeyEight = "8 toets" +PythonKeyNine = "9 toets" +PythonKeyLeftParenthesis = "HAAKJE OPENEN toets" +PythonKeyRightParenthesis = "HAAKJE SLUITEN toets" +PythonKeyFour = "4 toets" +PythonKeyFive = "5 toets" +PythonKeySix = "6 toets" +PythonKeyMultiplication = "VERMENIGVULDIGEN toets" +PythonKeyDivision = "DELEN toets" +PythonKeyOne = "1 toets" +PythonKeyTwo = "2 toets" +PythonKeyThree = "3 toets" +PythonKeyPlus = "PLUS toets" +PythonKeyMinus = "MIN toets" +PythonKeyZero = "0 toets" +PythonKeyDot = "PUNT toets" +PythonKeyEe = "10 TOT DE MACHT X toets" +PythonKeyAns = "ANS toets" +PythonKeyExe = "EXE toets" PythonLdexp = "Geeft x*(2**i), inversie van frexp" PythonLength = "Lengte van een object" PythonLgamma = "Log-gammafunctie" @@ -100,6 +174,14 @@ PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "Fractionele en gehele delen van x" PythonMonotonic = "Waarde van een monotone klok" +PythonNumpyFunction = "numpy module prefix" +PythonNumpyFftFunction = "numpy.fft module prefix" +PythonNumpyLinalgFunction = "numpy.linalg module prefix" +PythonScipyFunction = "scipy module prefix" +PythonScipyLinalgFunction = "scipy.linalg module prefix" +PythonScipyOptimizeFunction = "scipy.optimize module prefix" +PythonScipySignalFunction = "scipy.signal module prefix" +PythonScipySpecialFunction = "scipy.special module prefix" PythonOct = "Integer omzetten naar octaal" PythonPhase = "Fase van z in radialen" PythonPlot = "Plot y versus x als lijnen" @@ -122,9 +204,15 @@ PythonScatter = "Teken scatterplot van y versus x" PythonSeed = "Start willek. getallengenerator" PythonSetPixel = "Kleur pixel (x,y)" PythonShow = "Figuur weergeven" -PythonSin= "Sinus" +PythonSin = "Sinus" PythonSinh = "Sinus hyperbolicus" PythonSleep = "Stel executie voor t seconden uit" +PythonLocalTime = "Zet tijd om in tuple" +PythonMktime = "Tuple omzetten in tijd" +PythonTime = "Haal de huidige tijdstempel" +PythonSetLocaltime = "Stel de tijd in vanaf een tuple" +PythonRTCmode = "Huidige RTC-modus kregen" +PythonSetRTCmode = "RTC-modus instellen" PythonSort = "Sorteer de lijst" PythonSqrt = "Vierkantswortel" PythonSum = "Sommeer de items van een lijst" @@ -151,14 +239,11 @@ PythonTurtlePosition = "Zet huidige (x,y) locatie terug" PythonTurtleReset = "Reset de tekening" PythonTurtleRight = "Ga rechtsaf met a graden" PythonTurtleSetheading = "Zet de oriëntatie op a graden" -PythonTurtleSetposition = "Plaats de schildpad" PythonTurtleShowturtle = "Laat de schildpad zien" PythonTurtleSpeed = "Tekensnelheid tussen 0 and 10" PythonTurtleWrite = "Display a text" PythonUniform = "Decimaal getal in [a,b]" PythonImportTime = "Import time module" -PythonTimePrefix = "time module function prefix" -PythonTimeSleep = "Wait for n second" PythonMonotonic = "Return monotonic time" PythonFileOpen = "Opens a file" PythonFileSeekable = "Tells if seek can be used on a file" @@ -176,3 +261,5 @@ PythonFileName = "Contains file's name" PythonFileMode = "Contains file's open mode" PythonFileReadable = "Tells if read can be used on a file" PythonFileWritable = "Tells if write can be used on a file" +PythonImportUtils = "Ulab.utils importeren" +PythonUtilsFunction = "utils module functie prefix" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index d50630dda74..bc740acdf7f 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -1,5 +1,11 @@ PythonPound = "Comentário" PythonPercent = "Módulo" +PythonColon = "Dois pontos" +PythonSemicon = "Ponto e vírgula" +PythonExclamationMark = "Ponto de exclamação" +PythonLessThan = "Menor que" +PythonGreaterThan = "Maior que" +PythonQuestionMark = "Ponto de interrogação" Python1J = "i Complexo" PythonLF = "Nova linha" PythonTab = "Tabulação" @@ -45,6 +51,7 @@ PythonCosh = "Cosseno hiperbólico" PythonCount = "Contar as ocorrências de x" PythonDegrees = "Converter x de radianos para graus" PythonDivMod = "Quociente e resto" +PythonDrawCircle = "Desenha um círculo" PythonDrawLine = "Desenhe uma linha" PythonDrawString = "Mostrar o texto do pixel (x,y)" PythonErf = "Função erro" @@ -52,13 +59,18 @@ PythonErfc = "Função erro complementar" PythonEval = "Devolve a expressão avaliada" PythonExp = "Função exponencial" PythonExpm1 = "Calcular exp(x)-1" +PythonFactorial = "Fatorial de x" PythonFabs = "Valor absoluto" +PythonFillCircle = "Preencher um círculo" +PythonFillPolygon = "Preencher um polígono" PythonFillRect = "Preencher um retângulo em (x,y)" PythonFloat = "Converter x num flutuante" PythonFloor = "Parte inteira" PythonFmod = "a módulo b" PythonFrExp = "Coeficiente e expoente de x: (m, e)" PythonGamma = "Função gama" +PythonGetKeys = "Obter teclas pressionadas" +PythonGetPalette = "Obter paleta temática" PythonGetPixel = "Devolve a cor do pixel (x,y)" PythonGetrandbits = "Número inteiro aleatório com k bits" PythonGrid = "Alterar visibilidade da grelha" @@ -70,6 +82,8 @@ PythonImportKandinsky = "Importar módulo kandinsky" PythonImportRandom = "Importar módulo random" PythonImportMath = "Importar módulo math" PythonImportMatplotlibPyplot = "Importar módulo matplotlib.pyplot" +PythonImportNumpy = "Importar módulo ulab.numpy" +PythonImportScipy = "Importar módulo ulab.scipy" PythonImportTime = "Importar módulo time" PythonImportTurtle = "Importar módulo turtle" PythonIndex = "Índice da primeira ocorrência de x" @@ -80,8 +94,59 @@ PythonIonFunction = "Prefixo da função do módulo ion" PythonIsFinite = "Verificar se x é finito" PythonIsInfinite = "Verificar se x é infinito" PythonIsKeyDown = "Devolve True se tecla k pressionada" +PythonBattery = "Retornar a voltagem da bateria" +PythonBatteryLevel = "Retornar nível de bateria" +PythonBatteryIscharging = "Retorne se a bateria estiver carregando" +PythonSetBrightness = "Definir nível de brilho" +PythonGetBrightness = "Obter nível de brilho" PythonIsNaN = "Verificar se x é um NaN" PythonKandinskyFunction = "Prefixo da função do módulo kandinsky" +PythonKeyLeft = "tecla SETA ESQUERDA" +PythonKeyUp = "tecla SETA CIMA " +PythonKeyDown = "tecla SETA BAIXO" +PythonKeyRight = "tecla SETA DIREITA" +PythonKeyOk = "tecla OK" +PythonKeyBack = "tecla VOLTAR" +PythonKeyHome = "tecla HOME" +PythonKeyOnOff = "tecla ON/OFF" +PythonKeyShift = "tecla SHIFT" +PythonKeyAlpha = "tecla ALPHA" +PythonKeyXnt = "tecla X,N,T" +PythonKeyVar = "tecla VAR" +PythonKeyToolbox = "tecla CAIXA DE FERRAMENTAS" +PythonKeyBackspace = "tecla APAGAR" +PythonKeyExp = "tecla EXPONENCIAL" +PythonKeyLn = "tecla LOGARITMO NATURAL" +PythonKeyLog = "tecla LOGARITMO DECIMAL" +PythonKeyImaginary = "tecla I IMAGINÁRIO" +PythonKeyComma = "tecla VÍRGULA" +PythonKeyPower = "tecla EXPOENTE" +PythonKeySine = "tecla SENO" +PythonKeyCosine = "tecla COSSENO" +PythonKeyTangent = "tecla TANGENTE" +PythonKeyPi = "tecla PI" +PythonKeySqrt = "tecla RAIZ QUADRADA" +PythonKeySquare = "tecla AO QUADRADO" +PythonKeySeven = "tecla 7" +PythonKeyEight = "tecla 8" +PythonKeyNine = "tecla 9" +PythonKeyLeftParenthesis = "tecla PARÊNTESE ESQUERDO" +PythonKeyRightParenthesis = "tecla PARÊNTESE DIREITO" +PythonKeyFour = "tecla 4" +PythonKeyFive = "tecla 5" +PythonKeySix = "tecla 6" +PythonKeyMultiplication = "tecla MULTIPLICAÇÃO" +PythonKeyDivision = "tecla DIVISÃO" +PythonKeyOne = "tecla 1" +PythonKeyTwo = "tecla 2" +PythonKeyThree = "tecla 3" +PythonKeyPlus = "tecla MAIS" +PythonKeyMinus = "tecla MENOS" +PythonKeyZero = "tecla 0" +PythonKeyDot = "tecla PONTO" +PythonKeyEe = "tecla 10 expoente X" +PythonKeyAns = "tecla ANS" +PythonKeyExe = "tecla EXE" PythonLdexp = "Devolve x*(2**i), inverso de frexp" PythonLength = "Comprimento de um objeto" PythonLgamma = "Logaritmo da função gama" @@ -94,6 +159,14 @@ PythonMax = "Máximo" PythonMin = "Mínimo" PythonModf = "Partes inteira e frácionária de x" PythonMonotonic = "Devolve o valor do relógio" +PythonNumpyFunction = "Prefixo do módulo numpy" +PythonNumpyFftFunction = "Prefixo do módulo numpy.fft" +PythonNumpyLinalgFunction = "Prefixo do módulo numpy.linalg" +PythonScipyFunction = "Prefixo do módulo scipy" +PythonScipyLinalgFunction = "Prefixo do módulo scipy.linalg" +PythonScipyOptimizeFunction = "Prefixo do módulo scipy.optimize" +PythonScipySignalFunction = "Prefixo do módulo scipy.signal" +PythonScipySpecialFunction = "Prefixo do módulo scipy.special" PythonOct = "Converter número inteiro em octal" PythonPhase = "Argumento de z" PythonPlot = "Desenhar y em função de x" @@ -119,6 +192,12 @@ PythonShow = "Mostrar a figura" PythonSin = "Seno" PythonSinh = "Seno hiperbólico" PythonSleep = "Suspender a execução por t segundos" +PythonLocalTime = "Convert o tempo em tupla" +PythonMktime = "Convert tuple em tempo" +PythonTime = "Obter o estamp de tempo atual" +PythonSetLocaltime = "Definir tempo a partir de uma tupla" +PythonRTCmode = "Obter o modo RTC atual" +PythonSetRTCmode = "Definir o modo RTC" PythonSort = "Ordenar a lista" PythonSqrt = "Raiz quadrada" PythonSum = "Soma dos itens da lista" @@ -145,7 +224,6 @@ PythonTurtlePosition = "Devolve a posição atual (x,y)" PythonTurtleReset = "Reiniciar o desenho" PythonTurtleRight = "Virar à esquerda por a graus" PythonTurtleSetheading = "Definir a orientação por a graus" -PythonTurtleSetposition = "Positionne la tortue" PythonTurtleShowturtle = "Mostrar o turtle" PythonTurtleSpeed = "Velocidade do desenho entre 0 e 10" PythonTurtleWrite = "Mostrar um texto" @@ -157,8 +235,14 @@ PythonOsGetlogin = "Get username" PythonOsRemove = "Remover um ficheiro" PythonOsRename = "Renomear ficheiro" PythonOsListdir = "Listar ficheiros" -PythonTimePrefix = "time module function prefix" -PythonTimeSleep = "Wait for n second" +PythonImportSys = "Import sys module" +PythonSysExit = "Terminate current program" +PythonSysPrintexception = "Print exception" +PythonSysByteorder = "The byte order of the system" +PythonSysImplementation = "Information about Python" +PythonSysModules = "Dictionary of loaded modules" +PythonSysVersion = "Python language version (string)" +PythonSysVersioninfo = "Python language version (tuple)" PythonMonotonic = "Return monotonic time" PythonFileOpen = "Opens a file" PythonFileSeekable = "Tells if seek can be used on a file" @@ -176,3 +260,5 @@ PythonFileName = "Contains file's name" PythonFileMode = "Contains file's open mode" PythonFileReadable = "Tells if read can be used on a file" PythonFileWritable = "Tells if write can be used on a file" +PythonImportUtils = "Importando ulab.utils" +PythonUtilsFunction = "prefixo de função do módulo utils" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index 0c05ad6f460..09d292f7c25 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -1,6 +1,12 @@ PythonCommandAmpersand = "&" PythonCommandLF = "\\n" PythonCommandPercent = "%" +PythonCommandColon = ":" +PythonCommandSemicon = ";" +PythonCommandExclamationMark = "!" +PythonCommandLessThan = "<" +PythonCommandGreaterThan = ">" +PythonCommandQuestionMark = "?" PythonCommandPound = "#" PythonCommandSingleQuote = "'x'" PythonCommandSymbolExp = "^" @@ -50,6 +56,7 @@ PythonCommandCount = "list.count(x)" PythonCommandCountWithoutArg = ".count(\x11)" PythonCommandDegrees = "degrees(x)" PythonCommandDivMod = "divmod(a,b)" +PythonCommandDrawCircle = "draw_circle(x,y,r,color)" PythonCommandDrawLine = "draw_line(x1,y1,x2,y2,color)" PythonCommandDrawString = "draw_string(\"text\",x,y)" PythonCommandConstantE = "e" @@ -59,13 +66,18 @@ PythonCommandEval = "eval(\"expression\")" PythonCommandExp = "exp(x)" PythonCommandExpComplex = "exp(z)" PythonCommandExpm1 = "expm1(x)" +PythonCommandFactorial = "factorial(x)" PythonCommandFabs = "fabs(x)" +PythonCommandFillCircle = "fill_circle(x,y,r,color)" +PythonCommandFillPolygon = "fill_polygon([(x1,y1),...],color)" PythonCommandFillRect = "fill_rect(x,y,width,height,color)" PythonCommandFloat = "float(x)" PythonCommandFloor = "floor(x)" PythonCommandFmod = "fmod(a,b)" PythonCommandFrExp = "frexp(x)" PythonCommandGamma = "gamma(x)" +PythonCommandGetKeys = "get_keys()" +PythonCommandGetPalette = "get_palette()" PythonCommandGetPixel = "get_pixel(x,y)" PythonCommandGetrandbits = "getrandbits(k)" PythonCommandGrid = "grid()" @@ -86,9 +98,13 @@ PythonCommandImportIon = "import ion" PythonCommandImportKandinsky = "import kandinsky" PythonCommandImportMath = "import math" PythonCommandImportMatplotlibPyplot = "import matplotlib.pyplot" +PythonCommandImportFromNumpy = "from ulab import numpy as np" +PythonCommandImportFromScipy = "from ulab import scipy as spy" PythonCommandImportRandom = "import random" PythonCommandImportOs = "import os" PythonCommandImportFromOs = "from os import *" +PythonCommandImportSys = "import sys" +PythonCommandImportFromSys = "from sys import *" PythonCommandImportTime = "import time" PythonCommandImportTurtle = "import turtle" PythonCommandIndex = "list.index(x)" @@ -151,6 +167,11 @@ PythonCommandKeyEe = "KEY_EE" PythonCommandKeyAns = "KEY_ANS" PythonCommandKeyExe = "KEY_EXE" PythonCommandIsKeyDown = "keydown(k)" +PythonCommandBattery = "battery()" +PythonCommandBatteryLevel = "battery_level()" +PythonCommandBatteryIscharging = "battery_ischarging()" +PythonCommandSetBrightness = "set_brightness()" +PythonCommandGetBrightness = "get_brightness()" PythonCommandLdexp = "ldexp(x,i)" PythonCommandLength = "len(object)" PythonCommandLgamma = "lgamma(x)" @@ -166,6 +187,128 @@ PythonCommandMax = "max(list)" PythonCommandMin = "min(list)" PythonCommandModf = "modf(x)" PythonCommandMonotonic = "monotonic()" +PythonCommandNumpyArray = "np.array(a)" +PythonCommandNumpyArange = "np.arange(i)" +PythonCommandNumpyConcatenate = "np.concatenate(a,b)" +PythonCommandNumpyDiag = "np.diag(a)" +PythonCommandNumpyZeros = "np.zeros(s)" +PythonCommandNumpyOnes = "np.ones(s)" +PythonCommandNumpyEmpty = "np.empty(s)" +PythonCommandNumpyEye = "np.eye(n, m)" +PythonCommandNumpyFull = "np.full(s, v)" +PythonCommandNumpyLinspace = "np.linspace(s, e)" +PythonCommandNumpyLogspace = "np.logspace(s, e)" +PythonCommandNumpyCopy = "ndarray.copy()" +PythonCommandNumpyCopyWithoutArg = ".copy()" +PythonCommandNumpyDtype = "ndarray.dtype" +PythonCommandNumpyDtypeWithoutArg = ".dtype" +PythonCommandNumpyFlat = "ndarray.flat" +PythonCommandNumpyFlatWithoutArg = ".flat" +PythonCommandNumpyFlatten = "ndarray.flatten()" +PythonCommandNumpyFlattenWithoutArg = ".flatten()" +PythonCommandNumpyShape = "ndarray.shape" +PythonCommandNumpyShapeWithoutArg = ".shape" +PythonCommandNumpyReshape = "ndarray.reshape(s)" +PythonCommandNumpyReshapeWithoutArg = ".reshape(\x11)" +PythonCommandNumpySize = "ndarray.size" +PythonCommandNumpySizeWithoutArg = ".size" +PythonCommandNumpyTranspose = "ndarray.transpose()" +PythonCommandNumpyTransposeWithoutArg = ".transpose()" +PythonCommandNumpySort = "ndarray.sort()" +PythonCommandNumpySortWithoutArg = ".sort()" +PythonCommandNumpyFromBuffer = "ndarray.frombuffer(b)" +PythonCommandNumpyToBytes = "ndarray.tobytes()" +PythonCommandNumpyToBytesWithoutArg = ".tobytes()" +PythonCommandNumpySetPrintOptions = "np.set_printoptions()" +PythonCommandNumpyGetPrintOptions = "np.get_printoptions()" +PythonCommandNumpyNdinfo = "np.ndinfo(a)" +PythonCommandNumpyAll = "np.all(a)" +PythonCommandNumpyAny = "np.any(a)" +PythonCommandNumpyArgmax = "np.argmax(a)" +PythonCommandNumpyArgmin = "np.argmin(a)" +PythonCommandNumpyArgsort = "np.argsort(a)" +PythonCommandNumpyClip = "np.clip(a, min, max)" +PythonCommandNumpyConvolve = "np.convolve(a, b)" +PythonCommandNumpyDiff = "np.diff(a)" +PythonCommandNumpyInterp = "np.interp(a)" +PythonCommandNumpyDot = "np.dot(a, b)" +PythonCommandNumpyCross = "np.cross(a, b)" +PythonCommandNumpyEqual = "np.equal(a, b)" +PythonCommandNumpyNot_equal = "np.not_equal(a, b)" +PythonCommandNumpyFlip = "np.flip(a)" +PythonCommandNumpyIsfinite = "np.isfinite(a)" +PythonCommandNumpyIsinf = "np.isinf(a)" +PythonCommandNumpyMean = "np.mean(a)" +PythonCommandNumpyMin = "np.min(a)" +PythonCommandNumpyMax = "np.max(a)" +PythonCommandNumpyMedian = "np.median(a)" +PythonCommandNumpyMinimum = "np.minimum(a, b)" +PythonCommandNumpyMaximum = "np.maximum(a, b)" +PythonCommandNumpyPolyfit = "np.polyfit(a, b, y)" +PythonCommandNumpyPolyval = "np.polyval(p, x)" +PythonCommandNumpyRoll = "np.roll(a, n)" +PythonCommandNumpySortWithArguments = "np.sort(a)" +PythonCommandNumpyStd = "np.std(a)" +PythonCommandNumpySum = "np.sum(a)" +PythonCommandNumpyTrace = "np.trace(a)" +PythonCommandNumpyTrapz = "np.trapz(y)" +PythonCommandNumpyWhere = "np.where(c, x, y)" +PythonCommandNumpyVectorize = "np.vectorize(f)" +PythonCommandNumpyAcos = "np.acos(a)" +PythonCommandNumpyAcosh = "np.acosh(a)" +PythonCommandNumpyArctan2 = "np.arctan2(a)" +PythonCommandNumpyAround = "np.around(a)" +PythonCommandNumpyAsin = "np.asin(a)" +PythonCommandNumpyAsinh = "np.asinh(a)" +PythonCommandNumpyAtan = "np.atan(a)" +PythonCommandNumpyAtanh = "np.atanh(a)" +PythonCommandNumpyCeil = "np.ceil(a)" +PythonCommandNumpyCos = "np.cos(a)" +PythonCommandNumpyCosh = "np.cosh(a)" +PythonCommandNumpyDegrees = "np.degrees(a)" +PythonCommandNumpyExp = "np.exp(a)" +PythonCommandNumpyExpm1 = "np.expm1(a)" +PythonCommandNumpyFloor = "np.floor(a)" +PythonCommandNumpyLog = "np.log(a)" +PythonCommandNumpyLog10 = "np.log10(a)" +PythonCommandNumpyLog2 = "np.log2(a)" +PythonCommandNumpyRadians = "np.radians(a)" +PythonCommandNumpySin = "np.sin(a)" +PythonCommandNumpySinh = "np.sinh(a)" +PythonCommandNumpySqrt = "np.sqrt(a)" +PythonCommandNumpyTan = "np.tan(a)" +PythonCommandNumpyTanh = "np.tanh(a)" +PythonCommandNumpyBool = "np.bool" +PythonCommandNumpyFloat = "np.float" +PythonCommandNumpyUint8 = "np.uint8" +PythonCommandNumpyInt8 = "np.int8" +PythonCommandNumpyUint16 = "np.uint16" +PythonCommandNumpyInt16 = "np.int16" +PythonCommandNumpyNan = "np.nan" +PythonCommandNumpyInf = "np.inf" +PythonCommandNumpyE = "np.e" +PythonCommandNumpyPi = "np.pi" +PythonCommandNumpyFft = "np.fft.fft(a)" +PythonCommandNumpyIfft = "np.fft.ifft(a)" +PythonCommandNumpyDet = "np.linalg.det(a)" +PythonCommandNumpyEig = "np.linalg.eig(a)" +PythonCommandNumpyCholesky = "np.linalg.cholesky(a)" +PythonCommandNumpyInv = "np.linalg.inv(a)" +PythonCommandNumpyNorm = "np.linalg.norm(a)" +PythonCommandNumpyFunction = "np.function" +PythonCommandNumpyFunctionWithoutArg = "np.\x11" +PythonCommandNumpyFftFunction = "np.fft.function" +PythonCommandNumpyFftFunctionWithoutArg = "np.fft.\x11" +PythonCommandNumpyLinalgFunction = "np.linalg.function" +PythonCommandNumpyLinalgFunctionWithoutArg = "np.linalg.\x11" +PythonCommandImportFromUtils = "from ulab import utils" +PythonCommandUtilsFunction = "utils.function" +PythonCommandUtilsFunctionWithoutArg = "utils.\x11" +PythonCommandUtilsSpectrogram = "spectrogram(a)" +PythonCommandUtilsFromInt16Buffer = "from_int16_buffer(b)" +PythonCommandUtilsFromUint16Buffer = "from_uint16_buffer(b)" +PythonCommandUtilsFromInt32Buffer = "from_int32_buffer(b)" +PythonCommandUtilsFromUint32Buffer = "from_uint32_buffer(b)" PythonCommandOct = "oct(x)" PythonCommandPhase = "phase(z)" PythonCommandPlot = "plot(x,y,color)" @@ -192,12 +335,39 @@ PythonCommandReverseWithoutArg = ".reverse()" PythonCommandRound = "round(x,n)" PythonCommandScatter = "scatter(x,y)" PythonCommandSeed = "seed(x)" +PythonCommandScipyFunction = "spy.function" +PythonCommandScipyFunctionWithoutArg = "spy.\x11" +PythonCommandScipyLinalgFunction = "spy.linalg.function" +PythonCommandScipyLinalgFunctionWithoutArg = "spy.linalg.\x11" +PythonCommandScipyOptimizeFunction = "spy.optimize.function" +PythonCommandScipyOptimizeFunctionWithoutArg = "spy.optimize.\x11" +PythonCommandScipySignalFunction = "spy.signal.function" +PythonCommandScipySignalFunctionWithoutArg = "spy.signal.\x11" +PythonCommandScipySpecialFunction = "spy.special.function" +PythonCommandScipySpecialFunctionWithoutArg = "spy.special.\x11" +PythonCommandScipyLinalgChoSolve = "spy.linalg.cho_solve(a, b)" +PythonCommandScipyLinalgSolveTriangular = "spy.linalg.solve_triangular(a, b)" +PythonCommandScipyOptimizeBisect = "spy.optimize.bisect(f, a, b)" +PythonCommandScipyOptimizeFmin = "spy.optimize.fmin(f, x0)" +PythonCommandScipyOptimizeNewton = "spy.optimize.newton(f, x0)" +PythonCommandScipySignalSosfilt = "spy.signal.sosfilt(sos, x)" +PythonCommandScipySignalSpectrogram = "spy.signal.spectrogram(y)" +PythonCommandScipySpecialErf = "spy.erf(a)" +PythonCommandScipySpecialErfc = "spy.erfc(a)" +PythonCommandScipySpecialGamma = "spy.gamma(a)" +PythonCommandScipySpecialGammaln = "spy.gammaln(a)" PythonCommandSetPixel = "set_pixel(x,y,color)" PythonCommandShow = "show()" PythonCommandSin = "sin(x)" PythonCommandSinComplex = "sin(z)" PythonCommandSinh = "sinh(x)" PythonCommandSleep = "sleep(t)" +PythonCommandLocalTime = "localtime([timestamp])" +PythonCommandMktime = "mktime(tm)" +PythonCommandTime = "time()" +PythonCommandSetLocaltime = "setlocaltime(tm)" +PythonCommandRTCmode = "rtcmode()" +PythonCommandSetRTCmode = "setrtcmode(mode)" PythonCommandSort = "list.sort()" PythonCommandSortWithoutArg = ".sort()" PythonCommandSorted = "sorted(list)" @@ -222,6 +392,14 @@ PythonOsCommandRename = "rename(oldname, newname)" PythonOsCommandRemoveWithoutArg = "remove(\x11)" PythonOsCommandRenameWithoutArg = "rename(\x11,)" PythonOsCommandListdir = "listdir()" +PythonSysCommandExit = "exit()" +PythonSysCommandPrintexception = "print_exception(exc)" +PythonSysCommandPrintexceptionWithoutArg = "print_exception(\x11)" +PythonSysCommandByteorder = "byteorder" +PythonSysCommandImplementation = "implementation" +PythonSysCommandModules = "modules" +PythonSysCommandVersion = "version" +PythonSysCommandVersioninfo = "version_info" PythonTurtleCommandBackward = "backward(x)" PythonTurtleCommandCircle = "circle(r)" PythonTurtleCommandColor = "color('c')" @@ -239,16 +417,8 @@ PythonTurtleCommandPosition = "position()" PythonTurtleCommandReset = "reset()" PythonTurtleCommandRight = "right(a)" PythonTurtleCommandSetheading = "setheading(a)" -PythonTurtleCommandSetposition = "setposition(x,[y])" PythonTurtleCommandShowturtle = "showturtle()" PythonTurtleCommandSpeed = "speed(x)" -PythonTurtleCommandWhite = "'white'" -PythonTurtleCommandYellow = "'yellow'" -PythonTimeModule = "time" -PythonTimeCommandImportFrom = "from time import *" -PythonTimeCommandSleep = "sleep()" -PythonTimeCommandSleepDemo = "sleep(n)" -PythonTimeCommandMonotonic = "monotonic()" PythonCommandFileOpen = "open(name, [mode])" PythonCommandFileOpenWithoutArg = "open(\x11)" PythonCommandFileSeek = "file.seek(offset, [whence])" diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index ffc2a77685b..257398ae580 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -4,6 +4,8 @@ #include "app.h" #include #include +#include "../global_preferences.h" +#include using namespace Shared; @@ -71,6 +73,10 @@ void EditorController::viewDidDisappear() { m_menuController->scriptContentEditionDidFinish(); } +void EditorController::textAreaDidReceiveNoneXNTEvent() { + AppsContainer::sharedAppsContainer()->resetXNT(); +} + bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) { if (App::app()->textInputDidReceiveEvent(textArea, event)) { return true; @@ -80,7 +86,6 @@ bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events: return true; } - if (event == Ion::Events::Backspace && textArea->selectionIsEmpty()) { /* If the cursor is on the left of the text of a line, backspace one * indentation space at a time. */ diff --git a/apps/code/editor_controller.h b/apps/code/editor_controller.h index 4cd32c1edc8..42b35bfa7af 100644 --- a/apps/code/editor_controller.h +++ b/apps/code/editor_controller.h @@ -30,6 +30,7 @@ class EditorController : public ViewController, public TextAreaDelegate, public TELEMETRY_ID("Editor"); /* TextAreaDelegate */ + void textAreaDidReceiveNoneXNTEvent() override; bool textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) override; /* InputEventHandlerDelegate */ diff --git a/apps/code/editor_view.cpp b/apps/code/editor_view.cpp index 2541612d5be..1e3c34af9a0 100644 --- a/apps/code/editor_view.cpp +++ b/apps/code/editor_view.cpp @@ -26,7 +26,9 @@ void EditorView::resetSelection() { } void EditorView::scrollViewDidChangeOffset(ScrollViewDataSource * scrollViewDataSource) { - m_gutterView.setOffset(scrollViewDataSource->offset().y()); + if (m_gutterView.setOffsetAndNeedResize(scrollViewDataSource->offset().y())) { + internalLayoutSubviews(true); + } } View * EditorView::subviewAtIndex(int index) { @@ -42,8 +44,12 @@ void EditorView::didBecomeFirstResponder() { } void EditorView::layoutSubviews(bool force) { - m_gutterView.setOffset(0); - KDCoordinate gutterWidth = m_gutterView.minimalSizeForOptimalDisplay().width(); + m_gutterView.setOffsetAndNeedResize(0); // Whatever the return is, we layout the editor view + internalLayoutSubviews(force); +} + +void EditorView::internalLayoutSubviews(bool force) { + KDCoordinate gutterWidth = m_gutterView.computeWidth(); m_gutterView.setFrame(KDRect(0, 0, gutterWidth, bounds().height()), force); m_textArea.setFrame(KDRect( @@ -67,23 +73,23 @@ void EditorView::GutterView::drawRect(KDContext * ctx, KDRect rect) const { KDCoordinate firstLine = m_offset / glyphSize.height(); KDCoordinate firstLinePixelOffset = m_offset - firstLine * glyphSize.height(); - char lineNumber[k_lineNumberCharLength]; + char lineNumberBuffer[m_numberOfDigits + 1]; int numberOfLines = bounds().height() / glyphSize.height() + 1; for (int i=0; i= 10) { - line.serialize(lineNumber, k_lineNumberCharLength); - } else { - // Add a leading "0" - lineNumber[0] = '0'; - line.serialize(lineNumber + 1, k_lineNumberCharLength - 1); + + int lineDigits = computeNumberOfDigitsFor(lineNumberValue); + + for (int j=0; j < m_numberOfDigits - lineDigits; j++) { + lineNumberBuffer[j] = ' '; } - KDCoordinate leftPadding = (2 - strlen(lineNumber)) * glyphSize.width(); + + line.serialize(lineNumberBuffer + (m_numberOfDigits - lineDigits), m_numberOfDigits + 1); + ctx->drawString( - lineNumber, - KDPoint(k_margin + leftPadding, i*glyphSize.height() - firstLinePixelOffset), + lineNumberBuffer, + KDPoint(k_margin, i*glyphSize.height() - firstLinePixelOffset), m_font, textColor, backgroundColor @@ -91,18 +97,36 @@ void EditorView::GutterView::drawRect(KDContext * ctx, KDRect rect) const { } } -void EditorView::GutterView::setOffset(KDCoordinate offset) { +bool EditorView::GutterView::setOffsetAndNeedResize(KDCoordinate offset) { if (m_offset == offset) { - return; + return false; } m_offset = offset; + + int numberOfDigits = computeMaxNumberOfDigits(); + if (numberOfDigits != m_numberOfDigits) { + m_numberOfDigits = numberOfDigits; + return true; + } + markRectAsDirty(bounds()); + return false; } +int EditorView::GutterView::computeWidth() { + return 2 * k_margin + (m_numberOfDigits) * m_font->glyphSize().width(); +} -KDSize EditorView::GutterView::minimalSizeForOptimalDisplay() const { - int numberOfChars = 2; // TODO: Could be computed - return KDSize(2 * k_margin + numberOfChars * Poincare::Preferences::sharedPreferences()->KDPythonFont()->glyphSize().width(), 0); +int EditorView::GutterView::computeMaxNumberOfDigits() { + return computeNumberOfDigitsFor((bounds().height() / m_font->glyphSize().height() + 1) + (m_offset / m_font->glyphSize().height())); +} + +int EditorView::GutterView::computeNumberOfDigitsFor(int value) { + int digits = 1; + while (value >= pow(10, digits)) { + digits++; + } + return digits; } } diff --git a/apps/code/editor_view.h b/apps/code/editor_view.h index 547f7340b53..6c91fdde617 100644 --- a/apps/code/editor_view.h +++ b/apps/code/editor_view.h @@ -29,6 +29,7 @@ class EditorView : public Responder, public View, public ScrollViewDelegate { void unloadSyntaxHighlighter() { m_textArea.unloadSyntaxHighlighter(); }; void scrollViewDidChangeOffset(ScrollViewDataSource * scrollViewDataSource) override; void didBecomeFirstResponder() override; + void internalLayoutSubviews(bool force); private: int numberOfSubviews() const override { return 2; } View * subviewAtIndex(int index) override; @@ -36,15 +37,21 @@ class EditorView : public Responder, public View, public ScrollViewDelegate { class GutterView : public View { public: - GutterView(const KDFont * font) : View(), m_font(font), m_offset(0) {} + GutterView(const KDFont * font) : View(), m_font(font), m_offset(0), m_numberOfDigits(2) {} + void drawRect(KDContext * ctx, KDRect rect) const override; - void setOffset(KDCoordinate offset); - KDSize minimalSizeForOptimalDisplay() const override; + bool setOffsetAndNeedResize(KDCoordinate offset); // Return true if the gutter view need to be resized + + int computeWidth(); + int computeMaxNumberOfDigits(); + static int computeNumberOfDigitsFor(int value); + private: static constexpr KDCoordinate k_margin = 2; - static constexpr int k_lineNumberCharLength = 3; + const KDFont * m_font; KDCoordinate m_offset; + int m_numberOfDigits; }; PythonTextArea m_textArea; diff --git a/apps/code/helpers.cpp b/apps/code/helpers.cpp index 18250e77f47..7a74f724bb5 100644 --- a/apps/code/helpers.cpp +++ b/apps/code/helpers.cpp @@ -10,9 +10,6 @@ const char * PythonTextForEvent(Ion::Events::Event event) { if (event.text() == pair.firstString()) { return pair.secondString(); } - if (event == Ion::Events::XNT) { - return "x"; - } } return nullptr; } diff --git a/apps/code/menu_controller.cpp b/apps/code/menu_controller.cpp index a857fc539a2..63a50c0a882 100644 --- a/apps/code/menu_controller.cpp +++ b/apps/code/menu_controller.cpp @@ -90,6 +90,11 @@ bool MenuController::handleEvent(Ion::Events::Event event) { footer()->setSelectedButton(0); return true; } + if (event == Ion::Events::ShiftBack) { + Ion::Storage::sharedStorage()->reinsertTrash("py"); + m_selectableTableView.reloadData(); + return true; + } if (event == Ion::Events::Up) { if (footer()->selectedButton() == 0) { footer()->setSelectedButton(-1); diff --git a/apps/code/python_text_area.cpp b/apps/code/python_text_area.cpp index ab0615f3bf4..3edf9eeae8f 100644 --- a/apps/code/python_text_area.cpp +++ b/apps/code/python_text_area.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "../global_preferences.h" extern "C" { #include "py/nlr.h" @@ -23,7 +24,20 @@ constexpr KDColor BackgroundColor = Palette::CodeBackground; constexpr KDColor HighlightColor = Palette::CodeBackgroundSelected; constexpr KDColor AutocompleteColor = KDColor::RGB24(0xC6C6C6); // TODO Palette change +bool isItalic(mp_token_kind_t tokenKind) { + if (!GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()) { + return false; + } + if (tokenKind == MP_TOKEN_STRING) { + return true; + } + return false; +} + static inline KDColor TokenColor(mp_token_kind_t tokenKind) { + if (!GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()) { + return Palette::CodeText; + } if (tokenKind == MP_TOKEN_STRING) { return StringColor; } @@ -65,7 +79,8 @@ static inline KDColor TokenColor(mp_token_kind_t tokenKind) { && MP_TOKEN_KW_TRY + 1 == MP_TOKEN_KW_WHILE && MP_TOKEN_KW_WHILE + 1 == MP_TOKEN_KW_WITH && MP_TOKEN_KW_WITH + 1 == MP_TOKEN_KW_YIELD - && MP_TOKEN_KW_YIELD + 1 == MP_TOKEN_OP_TILDE, + && MP_TOKEN_KW_YIELD + 1 == MP_TOKEN_OP_ASSIGN + && MP_TOKEN_OP_ASSIGN + 1 == MP_TOKEN_OP_TILDE, "MP_TOKEN order changed, so Code::PythonTextArea::TokenColor might need to change too."); if (tokenKind >= MP_TOKEN_KW_FALSE && tokenKind <= MP_TOKEN_KW_YIELD) { return KeywordColor; @@ -118,7 +133,8 @@ static inline KDColor TokenColor(mp_token_kind_t tokenKind) { if ((tokenKind >= MP_TOKEN_OP_TILDE && tokenKind <= MP_TOKEN_DEL_DBL_STAR_EQUAL) || tokenKind == MP_TOKEN_DEL_EQUAL - || tokenKind == MP_TOKEN_DEL_MINUS_MORE) + || tokenKind == MP_TOKEN_DEL_MINUS_MORE + || tokenKind == MP_TOKEN_OP_ASSIGN) { return OperatorColor; } @@ -156,7 +172,7 @@ PythonTextArea::AutocompletionType PythonTextArea::autocompletionType(const char const char * tokenEnd; _mp_token_kind_t currentTokenKind = lex->tok_kind; - while (currentTokenKind != MP_TOKEN_NEWLINE && currentTokenKind != MP_TOKEN_END) { + while (currentTokenKind != MP_TOKEN_NEWLINE && currentTokenKind != MP_TOKEN_END && currentTokenKind != MP_TOKEN_FSTRING_RAW) { tokenStart = firstNonSpace + lex->tok_column - 1; tokenEnd = tokenStart + TokenLength(lex, tokenStart); @@ -249,7 +265,8 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char BackgroundColor, selectionStart, selectionEnd, - HighlightColor); + HighlightColor, + false); } if (UTF8Helper::CodePointIs(firstNonSpace, UCodePointNull)) { return; @@ -265,7 +282,7 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char const char * tokenFrom = firstNonSpace; size_t tokenLength = 0; const char * tokenEnd = firstNonSpace; - while (lex->tok_kind != MP_TOKEN_NEWLINE && lex->tok_kind != MP_TOKEN_END) { + while (lex->tok_kind != MP_TOKEN_NEWLINE && lex->tok_kind != MP_TOKEN_END && lex->tok_kind != MP_TOKEN_FSTRING_RAW) { tokenFrom = firstNonSpace + lex->tok_column - 1; if (tokenFrom != tokenEnd) { // We passed over white spaces, we need to color them @@ -279,13 +296,15 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char BackgroundColor, selectionStart, selectionEnd, - HighlightColor); + HighlightColor, + false); } tokenLength = TokenLength(lex, tokenFrom); tokenEnd = tokenFrom + tokenLength; - // If the token is being autocompleted, use DefaultColor + // If the token is being autocompleted, use DefaultColor/Font KDColor color = (tokenFrom <= autocompleteStart && autocompleteStart < tokenEnd) ? Palette::CodeText : TokenColor(lex->tok_kind); + bool italic = (tokenFrom <= autocompleteStart && autocompleteStart < tokenEnd) ? false : isItalic(lex->tok_kind); LOG_DRAW("Draw \"%.*s\" for token %d\n", tokenLength, tokenFrom, lex->tok_kind); drawStringAt(ctx, line, @@ -296,7 +315,9 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char BackgroundColor, selectionStart, selectionEnd, - HighlightColor); + HighlightColor, + italic + ); mp_lexer_to_next(lex); LOG_DRAW("Pop token %d\n", lex->tok_kind); @@ -304,6 +325,10 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char tokenFrom += tokenLength; + KDColor color = CommentColor; + if (!GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()) { + color = Palette::CodeText; + } // Even if the token is being autocompleted, use CommentColor if (tokenFrom < text + byteLength) { LOG_DRAW("Draw comment \"%.*s\" from %d\n", byteLength - (tokenFrom - text), firstNonSpace, tokenFrom); @@ -311,11 +336,12 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char UTF8Helper::GlyphOffsetAtCodePoint(text, tokenFrom), tokenFrom, text + byteLength - tokenFrom, - CommentColor, + color, BackgroundColor, selectionStart, selectionEnd, - HighlightColor); + HighlightColor, + true); } mp_lexer_free(lex); @@ -335,7 +361,8 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char BackgroundColor, nullptr, nullptr, - HighlightColor); + HighlightColor, + false); } } @@ -399,14 +426,14 @@ bool PythonTextArea::handleEvent(Ion::Events::Event event) { return result; } -bool PythonTextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) { +bool PythonTextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText, bool shouldRemoveLastCharacter) { if (*text == 0) { return false; } if (m_contentView.isAutocompleting()) { removeAutocompletion(); } - bool result = TextArea::handleEventWithText(text, indentation, forceCursorRightOfText); + bool result = TextArea::handleEventWithText(text, indentation, forceCursorRightOfText, shouldRemoveLastCharacter); addAutocompletion(); return result; } @@ -444,6 +471,11 @@ void PythonTextArea::addAutocompletion() { } bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIndexToUpdate) { + // If Autocomplete disable, skip this step + if(!GlobalPreferences::sharedGlobalPreferences()->autocomplete()) { + return false; + } + // The variable box should be loaded at this point const char * autocompletionTokenBeginning = nullptr; const char * autocompletionLocation = const_cast(cursorLocation()); @@ -461,9 +493,10 @@ bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIn if (textToInsertLength > 0) { // Try to insert the text (this might fail if the buffer is full) - if (!m_contentView.insertTextAtLocation(textToInsert, const_cast(autocompletionLocation), textToInsertLength)) { + if (!m_contentView.isAbleToInsertTextAt(textToInsertLength, autocompletionLocation, false)) { return false; } + m_contentView.insertTextAtLocation(textToInsert, const_cast(autocompletionLocation), textToInsertLength); autocompletionLocation += textToInsertLength; m_contentView.setAutocompleting(true); m_contentView.setAutocompletionEnd(autocompletionLocation); @@ -475,7 +508,8 @@ bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIn assert(strlen(parentheses) == parenthesesLength); /* If couldInsertText is false, we should not try to add the parentheses as * there was already not enough space to add the autocompletion. */ - if (addParentheses && m_contentView.insertTextAtLocation(parentheses, const_cast(autocompletionLocation), parenthesesLength)) { + if (addParentheses && m_contentView.isAbleToInsertTextAt(parenthesesLength, autocompletionLocation, false)) { + m_contentView.insertTextAtLocation(parentheses, const_cast(autocompletionLocation), parenthesesLength); m_contentView.setAutocompleting(true); m_contentView.setAutocompletionEnd(autocompletionLocation + parenthesesLength); return true; diff --git a/apps/code/python_text_area.h b/apps/code/python_text_area.h index c1a64db5ffe..57a46a5f85c 100644 --- a/apps/code/python_text_area.h +++ b/apps/code/python_text_area.h @@ -23,14 +23,14 @@ class PythonTextArea : public TextArea { void loadSyntaxHighlighter() { m_contentView.loadSyntaxHighlighter(); } void unloadSyntaxHighlighter() { m_contentView.unloadSyntaxHighlighter(); } bool handleEvent(Ion::Events::Event event) override; - bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; + bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false, bool shouldRemoveLastCharacter = false) override; /* autocompletionType returns: * - EndOfIdentifier if there is currently autocompletion, or if the cursor is * at the end of an identifier, * - MiddleOfIdentifier is the cursor is in the middle of an identifier, * - No identifier otherwise. * The autocompletionLocation can be provided with autocompletionLocation, or - * retreived with autocompletionLocationBeginning and autocompletionLocationEnd. */ + * retrieved with autocompletionLocationBeginning and autocompletionLocationEnd. */ AutocompletionType autocompletionType(const char * autocompletionLocation = nullptr, const char ** autocompletionLocationBeginning = nullptr, const char ** autocompletionLocationEnd = nullptr) const; bool isAutocompleting() const { return m_contentView.isAutocompleting(); } protected: diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 4adf6279544..f8a74902223 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -10,22 +10,22 @@ extern "C" { namespace Code { const ToolboxMessageTree forLoopChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::ForInRange1ArgLoopWithArg, I18n::Message::Default, false, I18n::Message::ForInRange1ArgLoop), - ToolboxMessageTree::Leaf(I18n::Message::ForInRange2ArgsLoopWithArg, I18n::Message::Default, false, I18n::Message::ForInRange2ArgsLoop), - ToolboxMessageTree::Leaf(I18n::Message::ForInRange3ArgsLoopWithArg, I18n::Message::Default, false, I18n::Message::ForInRange3ArgsLoop), - ToolboxMessageTree::Leaf(I18n::Message::ForInListLoopWithArg, I18n::Message::Default, false, I18n::Message::ForInListLoop) + ToolboxMessageTree::Leaf(I18n::Message::ForInRange1ArgLoopWithArg, I18n::Message::Default, false, I18n::Message::ForInRange1ArgLoop, true, 2), + ToolboxMessageTree::Leaf(I18n::Message::ForInRange2ArgsLoopWithArg, I18n::Message::Default, false, I18n::Message::ForInRange2ArgsLoop, true, 2), + ToolboxMessageTree::Leaf(I18n::Message::ForInRange3ArgsLoopWithArg, I18n::Message::Default, false, I18n::Message::ForInRange3ArgsLoop, true, 2), + ToolboxMessageTree::Leaf(I18n::Message::ForInListLoopWithArg, I18n::Message::Default, false, I18n::Message::ForInListLoop, true, 2) }; const ToolboxMessageTree ifStatementChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::IfElseStatementWithArg, I18n::Message::Default, false, I18n::Message::IfElseStatement), - ToolboxMessageTree::Leaf(I18n::Message::IfThenStatementWithArg, I18n::Message::Default, false, I18n::Message::IfThenStatement), - ToolboxMessageTree::Leaf(I18n::Message::IfElifElseStatementWithArg, I18n::Message::Default, false, I18n::Message::IfElifElseStatement), - ToolboxMessageTree::Leaf(I18n::Message::IfAndIfElseStatementWithArg, I18n::Message::Default, false, I18n::Message::IfAndIfElseStatement), - ToolboxMessageTree::Leaf(I18n::Message::IfOrIfElseStatementWithArg, I18n::Message::Default, false, I18n::Message::IfOrIfElseStatement) + ToolboxMessageTree::Leaf(I18n::Message::IfElseStatementWithArg, I18n::Message::Default, false, I18n::Message::IfElseStatement, true, 4), + ToolboxMessageTree::Leaf(I18n::Message::IfThenStatementWithArg, I18n::Message::Default, false, I18n::Message::IfThenStatement, true, 2), + ToolboxMessageTree::Leaf(I18n::Message::IfElifElseStatementWithArg, I18n::Message::Default, false, I18n::Message::IfElifElseStatement, true, 6), + ToolboxMessageTree::Leaf(I18n::Message::IfAndIfElseStatementWithArg, I18n::Message::Default, false, I18n::Message::IfAndIfElseStatement, true, 4), + ToolboxMessageTree::Leaf(I18n::Message::IfOrIfElseStatementWithArg, I18n::Message::Default, false, I18n::Message::IfOrIfElseStatement, true, 4) }; const ToolboxMessageTree whileLoopChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::WhileLoopWithArg, I18n::Message::Default, false, I18n::Message::WhileLoop) + ToolboxMessageTree::Leaf(I18n::Message::WhileLoopWithArg, I18n::Message::Default, false, I18n::Message::WhileLoop, true, 2) }; const ToolboxMessageTree conditionsChildren[] = { @@ -75,6 +75,7 @@ const ToolboxMessageTree MathModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAtan2, I18n::Message::PythonAtan2), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCeil, I18n::Message::PythonCeil), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCopySign, I18n::Message::PythonCopySign), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFactorial, I18n::Message::PythonFactorial), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFabs, I18n::Message::PythonFabs), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFloor, I18n::Message::PythonFloor), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFmod, I18n::Message::PythonFmod), @@ -135,6 +136,184 @@ const ToolboxMessageTree MatplotlibPyplotModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGray, I18n::Message::PythonColorGray, false) }; +#if defined(INCLUDE_ULAB) + +const ToolboxMessageTree NumpyNdarrayModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArray), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArange), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyConcatenate), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDiag), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyZeros), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyOnes), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyEmpty), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyEye), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFull), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLinspace), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLogspace), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFromBuffer), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCopy, I18n::Message::Default, false, I18n::Message::PythonCommandNumpyCopyWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDtype, I18n::Message::Default, false, I18n::Message::PythonCommandNumpyDtypeWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFlat, I18n::Message::Default, false, I18n::Message::PythonCommandNumpyFlatWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFlatten, I18n::Message::Default, false, I18n::Message::PythonCommandNumpyFlattenWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyShape, I18n::Message::Default, false, I18n::Message::PythonCommandNumpyShapeWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyReshape, I18n::Message::Default, false, I18n::Message::PythonCommandNumpyReshapeWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySize, I18n::Message::Default, false, I18n::Message::PythonCommandNumpySizeWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTranspose, I18n::Message::Default, false, I18n::Message::PythonCommandNumpyTransposeWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySort, I18n::Message::Default, false, I18n::Message::PythonCommandNumpySortWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyToBytes, I18n::Message::Default, false, I18n::Message::PythonCommandNumpyToBytesWithoutArg) +}; + +const ToolboxMessageTree NumpyFunctionsModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNdinfo), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAll), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAny), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArgmax), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArgmin), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArgsort), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyClip), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyConvolve), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDiff), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInterp), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDot), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCross), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyEqual), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNot_equal), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFlip), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyIsfinite), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyIsinf), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMean), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMin), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMax), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMedian), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMinimum), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyMaximum), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyPolyfit), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyPolyval), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyRoll), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySortWithArguments), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyStd), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySum), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTrace), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTrapz), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyWhere), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyVectorize), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAcos), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAcosh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyArctan2), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAround), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAsin), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAsinh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAtan), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyAtanh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCeil), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCos), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCosh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDegrees), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyExp), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyExpm1), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFloor), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLog), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLog10), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLog2), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyRadians), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySin), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySinh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySqrt), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTan), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyTanh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyBool), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFloat), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyUint8), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInt8), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyUint16), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInt16), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNan), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInf), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyE), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyPi), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpySetPrintOptions), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyGetPrintOptions) +}; + +const ToolboxMessageTree NumpyFftModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFftFunction, I18n::Message::PythonNumpyFftFunction, false, I18n::Message::PythonCommandNumpyFftFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFft), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyIfft) +}; + +const ToolboxMessageTree NumpyLinalgModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyLinalgFunction, I18n::Message::PythonNumpyLinalgFunction, false, I18n::Message::PythonCommandNumpyLinalgFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyDet), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyEig), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyCholesky), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyInv), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyNorm) +}; + +const ToolboxMessageTree NumpyModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromNumpy, I18n::Message::PythonImportNumpy, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandNumpyFunction, I18n::Message::PythonNumpyFunction, false, I18n::Message::PythonCommandNumpyFunctionWithoutArg), + ToolboxMessageTree::Node(I18n::Message::NumpyNdarray, NumpyNdarrayModuleChildren), + ToolboxMessageTree::Node(I18n::Message::Functions, NumpyFunctionsModuleChildren), + ToolboxMessageTree::Node(I18n::Message::NumpyFftModule, NumpyFftModuleChildren), + ToolboxMessageTree::Node(I18n::Message::NumpyLinalgModule, NumpyLinalgModuleChildren) +}; + +const ToolboxMessageTree ScipyLinalgModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyLinalgFunction, I18n::Message::PythonScipyLinalgFunction, false, I18n::Message::PythonCommandScipyLinalgFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyLinalgChoSolve), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyLinalgSolveTriangular) +}; + +const ToolboxMessageTree ScipyOptimizeModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyOptimizeFunction, I18n::Message::PythonScipyOptimizeFunction, false, I18n::Message::PythonCommandScipyOptimizeFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyOptimizeBisect), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyOptimizeFmin), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyOptimizeNewton) +}; + +const ToolboxMessageTree ScipySignalModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipySignalFunction, I18n::Message::PythonScipySignalFunction, false, I18n::Message::PythonCommandScipySignalFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipySignalSosfilt), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipySignalSpectrogram) +}; + +const ToolboxMessageTree ScipySpecialModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipySpecialFunction, I18n::Message::PythonScipySpecialFunction, false, I18n::Message::PythonCommandScipySpecialFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipySpecialErf), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipySpecialErfc), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipySpecialGamma), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipySpecialGammaln), +}; + +const ToolboxMessageTree ScipyModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromScipy, I18n::Message::PythonImportScipy, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScipyFunction, I18n::Message::PythonScipyFunction, false, I18n::Message::PythonCommandScipyFunctionWithoutArg), + ToolboxMessageTree::Node(I18n::Message::ScipyLinalgModule, ScipyLinalgModuleChildren), + ToolboxMessageTree::Node(I18n::Message::ScipyOptimizeModule, ScipyOptimizeModuleChildren), + ToolboxMessageTree::Node(I18n::Message::ScipySignalModule, ScipySignalModuleChildren), + ToolboxMessageTree::Node(I18n::Message::ScipySpecialModule, ScipySpecialModuleChildren), +}; + +const ToolboxMessageTree UtilsModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromUtils, I18n::Message::PythonImportUtils, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandUtilsFunction, I18n::Message::PythonUtilsFunction, false, I18n::Message::PythonCommandUtilsFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandUtilsSpectrogram), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandUtilsFromInt16Buffer), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandUtilsFromUint16Buffer), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandUtilsFromInt32Buffer), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandUtilsFromUint32Buffer), +}; + +const ToolboxMessageTree UlabModuleChildren[] = { + ToolboxMessageTree::Node(I18n::Message::NumpyModule, NumpyModuleChildren), + ToolboxMessageTree::Node(I18n::Message::ScipyModule, ScipyModuleChildren), + ToolboxMessageTree::Node(I18n::Message::UtilsModule, UtilsModuleChildren), + ToolboxMessageTree::Leaf(I18n::Message::UlabDocumentation, I18n::Message::UlabDocumentationLink) +}; + +#endif + const ToolboxMessageTree TurtleModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportTurtle, I18n::Message::PythonImportTurtle, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromTurtle, I18n::Message::PythonImportTurtle, false), @@ -194,7 +373,60 @@ const ToolboxMessageTree KandinskyModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColor, I18n::Message::PythonColor), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawString, I18n::Message::PythonDrawString), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawLine, I18n::Message::PythonDrawLine), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillRect, I18n::Message::PythonFillRect) + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawCircle, I18n::Message::PythonDrawCircle), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillRect, I18n::Message::PythonFillRect), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillCircle, I18n::Message::PythonFillCircle), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillPolygon, I18n::Message::PythonFillPolygon), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPalette, I18n::Message::PythonGetPalette) +}; + +const ToolboxMessageTree IonKeyModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyLeft, I18n::Message::PythonKeyLeft, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyUp, I18n::Message::PythonKeyUp, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyDown, I18n::Message::PythonKeyDown, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyRight, I18n::Message::PythonKeyRight, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyOk, I18n::Message::PythonKeyOk, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyBack, I18n::Message::PythonKeyBack, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyHome, I18n::Message::PythonKeyHome, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyOnOff, I18n::Message::PythonKeyOnOff, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyShift, I18n::Message::PythonKeyShift, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyAlpha, I18n::Message::PythonKeyAlpha, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyXnt, I18n::Message::PythonKeyXnt, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyVar, I18n::Message::PythonKeyVar, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyToolbox, I18n::Message::PythonKeyToolbox, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyBackspace, I18n::Message::PythonKeyBackspace, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyExp, I18n::Message::PythonKeyExp, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyLn, I18n::Message::PythonKeyLn, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyLog, I18n::Message::PythonKeyLog, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyImaginary, I18n::Message::PythonKeyImaginary, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyComma, I18n::Message::PythonKeyComma, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyPower, I18n::Message::PythonKeyPower, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeySine, I18n::Message::PythonKeySine, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyCosine, I18n::Message::PythonKeyCosine, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyTangent, I18n::Message::PythonKeyTangent, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyPi, I18n::Message::PythonKeyPi, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeySqrt, I18n::Message::PythonKeySqrt, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeySquare, I18n::Message::PythonKeySquare, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeySeven, I18n::Message::PythonKeySeven, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyEight, I18n::Message::PythonKeyEight, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyNine, I18n::Message::PythonKeyNine, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyLeftParenthesis, I18n::Message::PythonKeyLeftParenthesis, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyRightParenthesis, I18n::Message::PythonKeyRightParenthesis, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyFour, I18n::Message::PythonKeyFour, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyFive, I18n::Message::PythonKeyFive, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeySix, I18n::Message::PythonKeySix, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyMultiplication, I18n::Message::PythonKeyMultiplication, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyDivision, I18n::Message::PythonKeyDivision, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyOne, I18n::Message::PythonKeyOne, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyTwo, I18n::Message::PythonKeyTwo, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyThree, I18n::Message::PythonKeyThree, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyPlus, I18n::Message::PythonKeyPlus, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyMinus, I18n::Message::PythonKeyMinus, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyZero, I18n::Message::PythonKeyZero, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyDot, I18n::Message::PythonKeyDot, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyEe, I18n::Message::PythonKeyEe, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyAns, I18n::Message::PythonKeyAns, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyExe, I18n::Message::PythonKeyExe, false) }; const ToolboxMessageTree IonModuleChildren[] = { @@ -202,15 +434,29 @@ const ToolboxMessageTree IonModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromIon, I18n::Message::PythonImportIon, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIonFunction, I18n::Message::PythonIonFunction, false, I18n::Message::PythonCommandIonFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIsKeyDown, I18n::Message::PythonIsKeyDown), - ToolboxMessageTree::Leaf(I18n::Message::IonSelector, I18n::Message::IonSelector) + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetKeys, I18n::Message::PythonGetKeys), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBattery, I18n::Message::PythonBattery), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBatteryLevel, I18n::Message::PythonBatteryLevel), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBatteryIscharging, I18n::Message::PythonBatteryIscharging), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSetBrightness, I18n::Message::PythonSetBrightness), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetBrightness, I18n::Message::PythonGetBrightness), + // This is a special case, because it is handled separately, so the sub-tree is unused. + ToolboxMessageTree::Node(I18n::Message::IonSelector, IonKeyModuleChildren), + ToolboxMessageTree::Node(I18n::Message::IonKeyList, IonKeyModuleChildren) }; const ToolboxMessageTree TimeModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportTime, I18n::Message::PythonImportTime, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromTime, I18n::Message::PythonImportTime, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandTimeFunction, I18n::Message::PythonTimeFunction, false, I18n::Message::PythonCommandTimeFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandTime, I18n::Message::PythonTime), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandMonotonic, I18n::Message::PythonMonotonic, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSleep, I18n::Message::PythonSleep) + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSleep, I18n::Message::PythonSleep), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandLocalTime, I18n::Message::PythonLocalTime), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandMktime, I18n::Message::PythonMktime), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSetLocaltime, I18n::Message::PythonSetLocaltime), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRTCmode, I18n::Message::PythonRTCmode), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSetRTCmode, I18n::Message::PythonSetRTCmode), }; const ToolboxMessageTree OsModuleChildren[] = { @@ -223,20 +469,49 @@ const ToolboxMessageTree OsModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonOsCommandListdir, I18n::Message::PythonOsListdir, false) }; +#if MICROPY_PY_SYS +const ToolboxMessageTree SysModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportSys, I18n::Message::PythonImportSys, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromSys, I18n::Message::PythonImportSys, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonSysCommandExit, I18n::Message::PythonSysExit, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonSysCommandPrintexception, I18n::Message::PythonSysPrintexception, false, I18n::Message::PythonSysCommandPrintexceptionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonSysCommandByteorder, I18n::Message::PythonSysByteorder, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonSysCommandImplementation, I18n::Message::PythonSysImplementation, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonSysCommandModules, I18n::Message::PythonSysModules, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonSysCommandVersion, I18n::Message::PythonSysVersion, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonSysCommandVersioninfo, I18n::Message::PythonSysVersioninfo, false) +}; +#endif + const ToolboxMessageTree modulesChildren[] = { ToolboxMessageTree::Node(I18n::Message::MathModule, MathModuleChildren), ToolboxMessageTree::Node(I18n::Message::CmathModule, CMathModuleChildren), ToolboxMessageTree::Node(I18n::Message::MatplotlibPyplotModule, MatplotlibPyplotModuleChildren), +#if defined(INCLUDE_ULAB) + ToolboxMessageTree::Node(I18n::Message::UlabModule, UlabModuleChildren), +#endif ToolboxMessageTree::Node(I18n::Message::TurtleModule, TurtleModuleChildren), ToolboxMessageTree::Node(I18n::Message::RandomModule, RandomModuleChildren), ToolboxMessageTree::Node(I18n::Message::KandinskyModule, KandinskyModuleChildren), ToolboxMessageTree::Node(I18n::Message::IonModule, IonModuleChildren), ToolboxMessageTree::Node(I18n::Message::OsModule, OsModuleChildren), +#if MICROPY_PY_SYS + ToolboxMessageTree::Node(I18n::Message::SysModule, SysModuleChildren), +#endif ToolboxMessageTree::Node(I18n::Message::TimeModule, TimeModuleChildren) }; const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPound, I18n::Message::PythonPound, false), + #ifdef _FXCG + // There is no question mark button on the fx-CG calculators + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColon, I18n::Message::PythonColon, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSemicon, I18n::Message::PythonSemicon, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandExclamationMark, I18n::Message::PythonExclamationMark, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandLessThan, I18n::Message::PythonLessThan, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGreaterThan, I18n::Message::PythonGreaterThan, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandQuestionMark, I18n::Message::PythonQuestionMark, false), + #endif ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPercent, I18n::Message::PythonPercent, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommand1J, I18n::Message::Python1J, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandLF, I18n::Message::PythonLF, false), @@ -273,6 +548,7 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCosh, I18n::Message::PythonCosh), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDegrees, I18n::Message::PythonDegrees), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDivMod, I18n::Message::PythonDivMod), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawCircle, I18n::Message::PythonDrawCircle), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawLine, I18n::Message::PythonDrawLine), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawString, I18n::Message::PythonDrawString), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandConstantE, I18n::Message::PythonConstantE, false), @@ -282,6 +558,8 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandExp, I18n::Message::PythonExp), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandExpm1, I18n::Message::PythonExpm1), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFabs, I18n::Message::PythonFabs), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillCircle, I18n::Message::PythonFillCircle), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillPolygon, I18n::Message::PythonFillPolygon), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillRect, I18n::Message::PythonFillRect), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFloat, I18n::Message::PythonFloat), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFloor, I18n::Message::PythonFloor), @@ -324,6 +602,7 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIsNaN, I18n::Message::PythonIsNaN), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKandinskyFunction, I18n::Message::PythonKandinskyFunction, false, I18n::Message::PythonCommandKandinskyFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIsKeyDown, I18n::Message::PythonIsKeyDown), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBattery, I18n::Message::PythonBattery), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandLdexp, I18n::Message::PythonLdexp), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandLeft, I18n::Message::PythonTurtleLeft), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandLength, I18n::Message::PythonLength), @@ -424,10 +703,10 @@ const ToolboxMessageTree fileChildren[] { }; const ToolboxMessageTree exceptionsChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::TryExcept1ErrorWithArg, I18n::Message::Default, false, I18n::Message::TryExcept1Error), - ToolboxMessageTree::Leaf(I18n::Message::TryExcept1ErrorElseWithArg, I18n::Message::Default, false, I18n::Message::TryExcept1ErrorElse), - ToolboxMessageTree::Leaf(I18n::Message::TryExcept2ErrorWithArg, I18n::Message::Default, false, I18n::Message::TryExcept2Error), - ToolboxMessageTree::Leaf(I18n::Message::WithInstructionWithArg, I18n::Message::Default, false, I18n::Message::WithInstruction), + ToolboxMessageTree::Leaf(I18n::Message::TryExcept1ErrorWithArg, I18n::Message::Default, false, I18n::Message::TryExcept1Error, true, 4), + ToolboxMessageTree::Leaf(I18n::Message::TryExcept1ErrorElseWithArg, I18n::Message::Default, false, I18n::Message::TryExcept1ErrorElse, true, 6), + ToolboxMessageTree::Leaf(I18n::Message::TryExcept2ErrorWithArg, I18n::Message::Default, false, I18n::Message::TryExcept2Error, true, 4), + ToolboxMessageTree::Leaf(I18n::Message::WithInstructionWithArg, I18n::Message::Default, false, I18n::Message::WithInstruction, true, 2), }; const ToolboxMessageTree menu[] = { @@ -445,6 +724,10 @@ const ToolboxMessageTree toolboxModel = ToolboxMessageTree::Node(I18n::Message:: PythonToolbox::PythonToolbox() : Toolbox(nullptr, rootModel()->label()) { + for (int i=0; i < k_maxNumberOfDisplayedRows; i++) { + m_leafCells[i].setMessageFont(KDFont::LargeFont); + m_nodeCells[i].setMessageFont(KDFont::LargeFont); + } } const ToolboxMessageTree * PythonToolbox::moduleChildren(const char * name, int * numberOfNodes) const { @@ -475,31 +758,28 @@ bool PythonToolbox::handleEvent(Ion::Events::Event event) { return false; } +void PythonToolbox::willDisplayCellForIndex(HighlightCell * cell, int index) { + Toolbox::willDisplayCellForIndex(cell, index); + const ToolboxMessageTree * messageTree = static_cast(m_messageTreeModel->childAtIndex(index)); + MessageTableCell * myCell = static_cast *>(cell); + myCell->setMessageFont(messageTree->isMultiLine() ? KDFont::SmallFont : KDFont::LargeFont); +} + KDCoordinate PythonToolbox::rowHeight(int j) { - if (typeAtLocation(0, j) == Toolbox::LeafCellType && (m_messageTreeModel->label() == I18n::Message::IfStatementMenu || m_messageTreeModel->label() == I18n::Message::Exceptions)) { - /* To get the exact height needed for each cell, we have to compute its - * text size, which means scan the text char by char to look for '\n' - * chars. This is very costly and ruins the speed performance when - * scrolling at the bottom of a long table: to compute a position on the - * kth row, we call cumulatedHeightFromIndex(k), which calls rowHeight k - * times. - * We thus decided to compute the real height only for the ifStatement - * children of the toolbox, which is the only menu that has special height - * rows. */ - const ToolboxMessageTree * messageTree = static_cast(m_messageTreeModel->childAtIndex(j)); - return k_font->stringSize(I18n::translate(messageTree->label())).height() + 2*Metric::TableCellVerticalMargin + (messageTree->text() == I18n::Message::Default ? 0 : Toolbox::rowHeight(j)); + const ToolboxMessageTree * messageTree = static_cast(m_messageTreeModel->childAtIndex(j)); + if (messageTree->isMultiLine()) { + return k_fontForMultiLine->glyphSize().height() * messageTree->numberOfLines() + 2*Metric::TableCellVerticalMargin + (messageTree->text() == I18n::Message::Default ? 0 : Toolbox::rowHeight(j)); } return Toolbox::rowHeight(j); } -bool PythonToolbox::selectLeaf(int selectedRow) { - m_selectableTableView.deselectTable(); +bool PythonToolbox::selectLeaf(int selectedRow, bool quitToolbox) { ToolboxMessageTree * node = (ToolboxMessageTree *)m_messageTreeModel->childAtIndex(selectedRow); - if(node->insertedText() == I18n::Message::IonSelector){ - m_ionKeys.setSender(sender()); - Container::activeApp()->displayModalViewController(static_cast(&m_ionKeys), 0.f, 0.f, Metric::PopUpTopMargin, Metric::PopUpLeftMargin, 0, Metric::PopUpRightMargin); +#if defined(INCLUDE_ULAB) + if(node->text() == I18n::Message::UlabDocumentationLink){ return true; } +#endif const char * editedText = I18n::translate(node->insertedText()); // strippedEditedText array needs to be in the same scope as editedText char strippedEditedText[k_maxMessageSize]; @@ -510,20 +790,35 @@ bool PythonToolbox::selectLeaf(int selectedRow) { editedText = strippedEditedText; } sender()->handleEventWithText(editedText, true); - Container::activeApp()->dismissModalViewController(); + if (quitToolbox) { + m_selectableTableView.deselectTable(); + Container::activeApp()->dismissModalViewController(); + } return true; } +// This is the same function as in the Toolbox class, but we need to override it because we need to handle the Key selector differently. +bool PythonToolbox::selectSubMenu(int selectedRow) { + // If the selected row is a is the Key selector, we display the IonKeySelectorViewController + if (m_messageTreeModel->childAtIndex(selectedRow)->label() == I18n::Message::IonSelector) { + m_ionKeys.setSender(sender()); + Container::activeApp()->displayModalViewController(static_cast(&m_ionKeys), 0.f, 0.f, Metric::PopUpTopMargin, Metric::PopUpLeftMargin, 0, Metric::PopUpRightMargin); + return true; + } + return Toolbox::selectSubMenu(selectedRow); +} + + const ToolboxMessageTree * PythonToolbox::rootModel() const { return &toolboxModel; } -MessageTableCellWithMessage * PythonToolbox::leafCellAtIndex(int index) { +MessageTableCellWithMessage * PythonToolbox::leafCellAtIndex(int index) { assert(index >= 0 && index < k_maxNumberOfDisplayedRows); return &m_leafCells[index]; } -MessageTableCellWithChevron* PythonToolbox::nodeCellAtIndex(int index) { +MessageTableCellWithChevron * PythonToolbox::nodeCellAtIndex(int index) { assert(index >= 0 && index < k_maxNumberOfDisplayedRows); return &m_nodeCells[index]; } diff --git a/apps/code/python_toolbox.h b/apps/code/python_toolbox.h index 7c66af03f2c..c641536a405 100644 --- a/apps/code/python_toolbox.h +++ b/apps/code/python_toolbox.h @@ -18,22 +18,28 @@ class PythonToolbox : public Toolbox { // Toolbox bool handleEvent(Ion::Events::Event event) override; const ToolboxMessageTree * rootModel() const override; + + // ListViewDataSource + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + protected: KDCoordinate rowHeight(int j) override; - bool selectLeaf(int selectedRow) override; - MessageTableCellWithMessage * leafCellAtIndex(int index) override; - MessageTableCellWithChevron* nodeCellAtIndex(int index) override; + bool selectLeaf(int selectedRow, bool quitToolbox) override; + bool selectSubMenu(int selectedRow) override; + MessageTableCellWithMessage * leafCellAtIndex(int index) override; + MessageTableCellWithChevron * nodeCellAtIndex(int index) override; int maxNumberOfDisplayedRows() override; + bool canStayInMenu() override { return true; } constexpr static int k_maxNumberOfDisplayedRows = 13; // = 240/(13+2*3) // 13 = minimal string height size // 3 = vertical margins private: - constexpr static const KDFont * k_font = KDFont::SmallFont; + constexpr static const KDFont * k_fontForMultiLine = KDFont::SmallFont; void scrollToLetter(char letter); void scrollToAndSelectChild(int i); - MessageTableCellWithMessage m_leafCells[k_maxNumberOfDisplayedRows]; - MessageTableCellWithChevron m_nodeCells[k_maxNumberOfDisplayedRows]; - toolboxIonKeys m_ionKeys; + MessageTableCellWithMessage m_leafCells[k_maxNumberOfDisplayedRows]; + MessageTableCellWithChevron m_nodeCells[k_maxNumberOfDisplayedRows]; + ToolboxIonKeys m_ionKeys; }; } diff --git a/apps/code/script_node_cell.cpp b/apps/code/script_node_cell.cpp index 0dc8675c359..b51abdf86a4 100644 --- a/apps/code/script_node_cell.cpp +++ b/apps/code/script_node_cell.cpp @@ -21,10 +21,10 @@ void ScriptNodeCell::ScriptNodeView::drawRect(KDContext * ctx, KDRect rect) cons const int nodeNameLength = m_scriptNode->nameLength(); KDSize nameSize = k_font->stringSize(nodeName, nodeNameLength); const KDCoordinate nodeNameY = k_topMargin; - ctx->drawString(nodeName, KDPoint(0, nodeNameY), k_font, KDColorBlack, backgroundColor, nodeNameLength); + ctx->drawString(nodeName, KDPoint(0, nodeNameY), k_font, Palette::PrimaryText, backgroundColor, nodeNameLength); // If it is needed, draw the parentheses if (m_scriptNode->type() == ScriptNode::Type::WithParentheses) { - ctx->drawString(ScriptNodeCell::k_parentheses, KDPoint(nameSize.width(), nodeNameY), k_font, KDColorBlack, backgroundColor); + ctx->drawString(ScriptNodeCell::k_parentheses, KDPoint(nameSize.width(), nodeNameY), k_font, Palette::PrimaryText, backgroundColor); } /* If it exists, draw the source name. If it did not fit, we would have put diff --git a/apps/code/script_parameter_controller.h b/apps/code/script_parameter_controller.h index e7065e7334f..193879b683c 100644 --- a/apps/code/script_parameter_controller.h +++ b/apps/code/script_parameter_controller.h @@ -34,11 +34,11 @@ class ScriptParameterController : public ViewController, public SimpleListViewDa constexpr static int k_totalNumberOfCell = 6; StackViewController * stackViewController(); I18n::Message m_pageTitle; - MessageTableCell m_executeScript; - MessageTableCell m_renameScript; + MessageTableCell<> m_executeScript; + MessageTableCell<> m_renameScript; MessageTableCellWithSwitch m_autoImportScript; - MessageTableCell m_deleteScript; - MessageTableCell m_duplicateScript; + MessageTableCell<> m_deleteScript; + MessageTableCell<> m_duplicateScript; MessageTableCellWithBuffer m_size; void GetScriptSize(MessageTableCellWithBuffer* myCell); SelectableTableView m_selectableTableView; diff --git a/apps/code/script_store.cpp b/apps/code/script_store.cpp index ebc35882753..ad790811b53 100644 --- a/apps/code/script_store.cpp +++ b/apps/code/script_store.cpp @@ -8,11 +8,9 @@ bool ScriptStore::ScriptNameIsFree(const char * baseName) { return ScriptBaseNamed(baseName).isNull(); } +// Here we add "base" script ScriptStore::ScriptStore() { - addScriptFromTemplate(ScriptTemplate::Squares()); - addScriptFromTemplate(ScriptTemplate::Parabola()); - addScriptFromTemplate(ScriptTemplate::Mandelbrot()); - addScriptFromTemplate(ScriptTemplate::Polynomial()); + } void ScriptStore::deleteAllScripts() { diff --git a/apps/code/script_store.h b/apps/code/script_store.h index ad9b59ff7c1..00beec5d3c7 100644 --- a/apps/code/script_store.h +++ b/apps/code/script_store.h @@ -49,7 +49,7 @@ class ScriptStore : public MicroPython::ScriptProvider { * k_fullFreeSpaceSizeLimit, we consider the script store as full. * To be able to add a new empty record, the available space should at least * be able to store a Script with default name and its extension, the - * importation status (1 char), the default content "from math import *\n" + * importation status (1 char), the cursor (2 char), the default content "from math import *\n" * (20 char) and 10 char of free space. */ static constexpr int k_fullFreeSpaceSizeLimit = sizeof(Ion::Storage::record_size_t)+Script::k_defaultScriptNameMaxSize+k_scriptExtensionLength+1+20+10; }; diff --git a/apps/code/script_template.cpp b/apps/code/script_template.cpp index 8b59c912af1..c5bde929ea4 100644 --- a/apps/code/script_template.cpp +++ b/apps/code/script_template.cpp @@ -5,103 +5,9 @@ namespace Code { constexpr ScriptTemplate emptyScriptTemplate(".py", "\x01" R"(from math import * )"); -constexpr ScriptTemplate squaresScriptTemplate("squares.py", "\x01" R"(from math import * -from turtle import * -def squares(angle=0.5): - reset() - L=330 - speed(10) - penup() - goto(-L/2,-L/2) - pendown() - for i in range(660): - forward(L) - left(90+angle) - L=L-L*sin(angle*pi/180) - hideturtle())"); - -constexpr ScriptTemplate mandelbrotScriptTemplate("mandelbrot.py", "\x01" R"(# This script draws a Mandelbrot fractal set -# N_iteration: degree of precision -import kandinsky -def mandelbrot(N_iteration): - for x in range(320): - for y in range(222): -# Compute the mandelbrot sequence for the point c = (c_r, c_i) with start value z = (z_r, z_i) - z = complex(0,0) -# Rescale to fit the drawing screen 320x222 - c = complex(3.5*x/319-2.5, -2.5*y/221+1.25) - i = 0 - while (i < N_iteration) and abs(z) < 2: - i = i + 1 - z = z*z+c -# Choose the color of the dot from the Mandelbrot sequence - rgb = int(255*i/N_iteration) - col = kandinsky.color(int(rgb*0.82),int(rgb*0.13),int(rgb*0.18)) -# Draw a pixel colored in 'col' at position (x,y) - kandinsky.set_pixel(x,y,col))"); - -constexpr ScriptTemplate polynomialScriptTemplate("polynomial.py", "\x01" R"(from math import * -# roots(a,b,c) computes the solutions of the equation a*x**2+b*x+c=0 -def roots(a,b,c): - delta = b*b-4*a*c - if delta == 0: - return -b/(2*a) - elif delta > 0: - x_1 = (-b-sqrt(delta))/(2*a) - x_2 = (-b+sqrt(delta))/(2*a) - return x_1, x_2 - else: - return None)"); - -constexpr ScriptTemplate parabolaScriptTemplate("parabola.py", "\x01" R"(from matplotlib.pyplot import * -from math import * - -g=9.81 - -def x(t,v_0,alpha): - return v_0*cos(alpha)*t -def y(t,v_0,alpha,h_0): - return -0.5*g*t**2+v_0*sin(alpha)*t+h_0 - -def vx(v_0,alpha): - return v_0*cos(alpha) -def vy(t,v_0,alpha): - return -g*t+v_0*sin(alpha) - -def t_max(v_0,alpha,h_0): - return (v_0*sin(alpha)+sqrt((v_0**2)*(sin(alpha)**2)+2*g*h_0))/g - -def simulation(v_0=15,alpha=pi/4,h_0=2): - tMax=t_max(v_0,alpha,h_0) - accuracy=1/10**(floor(log10(tMax))-1) - T_MAX=floor(tMax*accuracy)+1 - X=[x(t/accuracy,v_0,alpha) for t in range(T_MAX)] - Y=[y(t/accuracy,v_0,alpha,h_0) for t in range(T_MAX)] - VX=[vx(v_0,alpha) for t in range(T_MAX)] - VY=[vy(t/accuracy,v_0,alpha) for t in range(T_MAX)] - for i in range(T_MAX): - arrow(X[i],Y[i],VX[i]/accuracy,VY[i]/accuracy) - grid() - show())"); const ScriptTemplate * ScriptTemplate::Empty() { return &emptyScriptTemplate; } -const ScriptTemplate * ScriptTemplate::Squares() { - return &squaresScriptTemplate; -} - -const ScriptTemplate * ScriptTemplate::Mandelbrot() { - return &mandelbrotScriptTemplate; -} - -const ScriptTemplate * ScriptTemplate::Polynomial() { - return &polynomialScriptTemplate; -} - -const ScriptTemplate * ScriptTemplate::Parabola() { - return ¶bolaScriptTemplate; -} - } diff --git a/apps/code/script_template.h b/apps/code/script_template.h index ec32e7052a5..6eac56d34b1 100644 --- a/apps/code/script_template.h +++ b/apps/code/script_template.h @@ -9,10 +9,6 @@ class ScriptTemplate { public: constexpr ScriptTemplate(const char * name, const char * value) : m_name(name), m_value(value) {} static const ScriptTemplate * Empty(); - static const ScriptTemplate * Squares(); - static const ScriptTemplate * Mandelbrot(); - static const ScriptTemplate * Polynomial(); - static const ScriptTemplate * Parabola(); const char * name() const { return m_name; } const char * content() const { return m_value + Script::StatusSize(); } const char * value() const { return m_value; } diff --git a/apps/code/test/toolbox_ion_keys_dummy.cpp b/apps/code/test/toolbox_ion_keys_dummy.cpp index 9f38bb541e4..969b20e3c42 100644 --- a/apps/code/test/toolbox_ion_keys_dummy.cpp +++ b/apps/code/test/toolbox_ion_keys_dummy.cpp @@ -1,26 +1,26 @@ #include "../toolbox_ion_keys.h" namespace Code { - toolboxIonKeys::toolboxIonKeys() : + ToolboxIonKeys::ToolboxIonKeys() : ViewController(nullptr), m_view() { } - bool toolboxIonKeys::handleEvent(Ion::Events::Event e) { + bool ToolboxIonKeys::handleEvent(Ion::Events::Event e) { return false; } - toolboxIonKeys::toolboxIonView::toolboxIonView(): + ToolboxIonKeys::toolboxIonView::toolboxIonView(): View() { } - void toolboxIonKeys::toolboxIonView::drawRect(KDContext * ctx, KDRect rect) const { + void ToolboxIonKeys::toolboxIonView::drawRect(KDContext * ctx, KDRect rect) const { return; } - View * toolboxIonKeys::view(){ + View * ToolboxIonKeys::view(){ return &m_view; } diff --git a/apps/code/test/variable_box_controller.cpp b/apps/code/test/variable_box_controller.cpp index 2c7e4abfcbe..4dec7e43a1f 100644 --- a/apps/code/test/variable_box_controller.cpp +++ b/apps/code/test/variable_box_controller.cpp @@ -40,7 +40,7 @@ void assert_variables_are(const char * script, const char * nameToComplete, cons &addParentheses, i, &index); - quiz_assert(i == index); // If false, the autompletion has cycled: there are not as many results as expected + quiz_assert(i == index); // If false, the autocompletion has cycled: there are not as many results as expected quiz_assert(strncmp(*(expectedVariables + i), autocompletionI - nameToCompleteLength, textToInsertLength + nameToCompleteLength) == 0); index++; } @@ -63,7 +63,7 @@ QUIZ_CASE(variable_box_controller) { }; // FIXME This test does not load imported variables for now assert_variables_are( - "\x01 from math import *\nfroo=3", + "\x01\x01\x01 from math import *\nfroo=3", "fr", expectedVariables, sizeof(expectedVariables) / sizeof(const char *)); diff --git a/apps/code/toolbox.de.i18n b/apps/code/toolbox.de.i18n index 34840329bfb..c71afcc5254 100644 --- a/apps/code/toolbox.de.i18n +++ b/apps/code/toolbox.de.i18n @@ -4,3 +4,7 @@ Modules = "Module" LoopsAndTests = "Schleifen und Tests" Files = "Dateien" Exceptions = "Ausnahmen" +UlabDocumentation = "Dokumentation" +IonSelector = "Schlüsselauswahl" +PressAKey = "drücke eine Taste" +IonKeyList = "Liste der Schlüssel" diff --git a/apps/code/toolbox.en.i18n b/apps/code/toolbox.en.i18n index 81e60c7da2b..820680d2f55 100644 --- a/apps/code/toolbox.en.i18n +++ b/apps/code/toolbox.en.i18n @@ -4,3 +4,7 @@ Modules = "Modules" LoopsAndTests = "Loops and tests" Files = "Files" Exceptions = "Exceptions" +UlabDocumentation = "Documentation" +IonSelector = "Key selector" +PressAKey = "Press a key" +IonKeyList = "List of keys" diff --git a/apps/code/toolbox.es.i18n b/apps/code/toolbox.es.i18n index 81e60c7da2b..8744a2872f5 100644 --- a/apps/code/toolbox.es.i18n +++ b/apps/code/toolbox.es.i18n @@ -4,3 +4,7 @@ Modules = "Modules" LoopsAndTests = "Loops and tests" Files = "Files" Exceptions = "Exceptions" +UlabDocumentation = "Documentación" +IonSelector = "Selector de llave" +PressAKey = "presione una tecla" +IonKeyList = "Lista de llaves" diff --git a/apps/code/toolbox.fr.i18n b/apps/code/toolbox.fr.i18n index 724abb7a518..b2c2dfe97cf 100644 --- a/apps/code/toolbox.fr.i18n +++ b/apps/code/toolbox.fr.i18n @@ -4,3 +4,7 @@ Modules = "Modules" LoopsAndTests = "Boucles et tests" Files = "Fichiers" Exceptions = "Exceptions" +UlabDocumentation = "Documentation" +IonSelector = "Sélecteur de touche" +PressAKey = "Appuyez sur une touche" +IonKeyList = "Liste des touches" diff --git a/apps/code/toolbox.hu.i18n b/apps/code/toolbox.hu.i18n index 89015122024..da82c67b22f 100644 --- a/apps/code/toolbox.hu.i18n +++ b/apps/code/toolbox.hu.i18n @@ -4,3 +4,7 @@ Modules = "Modulok" LoopsAndTests = "Hurkok és tesztek" Files = "Fájlok" Exceptions = "Kivételek" +UlabDocumentation = "Dokumentáció" +IonSelector = "Kulcsválasztó" +PressAKey = "Nyomj meg egy gombot" +IonKeyList = "A kulcsok listája" diff --git a/apps/code/toolbox.it.i18n b/apps/code/toolbox.it.i18n index d7b219d872e..fe6546b30c5 100644 --- a/apps/code/toolbox.it.i18n +++ b/apps/code/toolbox.it.i18n @@ -2,5 +2,9 @@ Functions = "Funzioni" Catalog = "Catalogo" Modules = "Moduli" LoopsAndTests = "Cicli e test" -Files = "Files" -Exceptions = "Exceptions" +Files = "File" +Exceptions = "Eccezioni" +UlabDocumentation = "Documentazione" +IonSelector = "Selettore tasti" +PressAKey = "Premi un tasto" +IonKeyList = "Elenco dei tasti" diff --git a/apps/code/toolbox.nl.i18n b/apps/code/toolbox.nl.i18n index 849bd76a6ab..137144df572 100644 --- a/apps/code/toolbox.nl.i18n +++ b/apps/code/toolbox.nl.i18n @@ -4,3 +4,7 @@ Modules = "Modules" LoopsAndTests = "Herhalingen en testen" Files = "Files" Exceptions = "Exceptions" +UlabDocumentation = "Documentatie" +IonSelector = "Toetsenkiezer" +PressAKey = "druk op een knop" +IonKeyList = "Lijst met sleutels" diff --git a/apps/code/toolbox.pt.i18n b/apps/code/toolbox.pt.i18n index f7cfad07b03..9884dfdc075 100644 --- a/apps/code/toolbox.pt.i18n +++ b/apps/code/toolbox.pt.i18n @@ -4,3 +4,7 @@ Modules = "Módulos" LoopsAndTests = "Laços e testes" Files = "Files" Exceptions = "Exceptions" +UlabDocumentation = "Documentação" +IonSelector = "Seletor de chave" +PressAKey = "Pressione uma tecla" +IonKeyList = "Lista de chaves" diff --git a/apps/code/toolbox.universal.i18n b/apps/code/toolbox.universal.i18n index 4dd5345def6..c05493a0564 100644 --- a/apps/code/toolbox.universal.i18n +++ b/apps/code/toolbox.universal.i18n @@ -3,9 +3,21 @@ IonModule = "ion" KandinskyModule = "kandinsky" MathModule = "math" MatplotlibPyplotModule = "matplotlib.pyplot" +NumpyModule = "numpy" +NumpyFftModule = "fft" +NumpyLinalgModule = "linalg" +ScipyModule = "scipy" +ScipyLinalgModule = "linalg" +ScipyOptimizeModule = "optimize" +ScipySignalModule = "signal" +ScipySpecialModule = "special" +NumpyNdarray = "ndarray" +UtilsModule = "utils" OsModule = "os" +SysModule = "sys" TimeModule = "time" TurtleModule = "turtle" +UlabModule = "ulab" ForLoopMenu = "For" IfStatementMenu = "If" WhileLoopMenu = "While" @@ -57,5 +69,4 @@ PythonCommandDef = "def \x11():\n " PythonCommandDefWithArg = "def function(x):" PythonCommandReturn = "return " RandomModule = "random" -IonSelector = "Key selector" -PressAKey = "Press a key" +UlabDocumentationLink = "micropython-ulab.readthedocs.io" diff --git a/apps/code/toolbox_ion_keys.cpp b/apps/code/toolbox_ion_keys.cpp index b3e67f63f04..edfd490aafa 100644 --- a/apps/code/toolbox_ion_keys.cpp +++ b/apps/code/toolbox_ion_keys.cpp @@ -6,45 +6,52 @@ extern "C" { #include #include } -extern const mp_rom_map_elem_t modion_module_globals_table[48]; +extern "C" const mp_rom_map_elem_t modion_module_globals_table[55]; namespace Code { - toolboxIonKeys::toolboxIonKeys() : +ToolboxIonKeys::ToolboxIonKeys() : ViewController(nullptr), m_view() - { - } +{ +} - bool toolboxIonKeys::handleEvent(Ion::Events::Event e) { - Ion::Keyboard::State state = Ion::Keyboard::scan(); - for(uint16_t i = 0; i < sizeof(modion_module_globals_table)/sizeof(_mp_rom_map_elem_t); i++){ - _mp_rom_map_elem_t element = modion_module_globals_table[i]; - if(mp_obj_is_small_int(element.value)){ - int key = mp_obj_get_int(element.value); - if(state.keyDown(static_cast(key))){ - m_sender->handleEventWithText(qstr_str(MP_OBJ_QSTR_VALUE(element.key)), true); - } +bool ToolboxIonKeys::handleEvent(Ion::Events::Event e) { + // FIXME: Use event data to get the pressed key and fix the EXE key. + Ion::Keyboard::State state = Ion::Keyboard::scan(); + for (uint16_t i = 0; i < sizeof(modion_module_globals_table) / sizeof(_mp_rom_map_elem_t); i++) { + _mp_rom_map_elem_t element = modion_module_globals_table[i]; + if (mp_obj_is_small_int(element.value)) { + int key = mp_obj_get_int(element.value); + if (state.keyDown(static_cast(key))) { + m_sender->handleEventWithText(qstr_str(MP_OBJ_QSTR_VALUE(element.key)), true); } } - Container::activeApp()->dismissModalViewController(); - AppsContainer::sharedAppsContainer()->redrawWindow(); - return true; } + Container::activeApp()->dismissModalViewController(); + AppsContainer::sharedAppsContainer()->redrawWindow(); + return true; +} - toolboxIonKeys::toolboxIonView::toolboxIonView(): - View() - { - } +ToolboxIonKeys::toolboxIonView::toolboxIonView() : + View() +{ +} - void toolboxIonKeys::toolboxIonView::drawRect(KDContext * ctx, KDRect rect) const { - ctx->fillRect(rect, Palette::GrayBright); - ctx->strokeRect(rect, Palette::GrayDark); - ctx->drawString(I18n::translate(I18n::Message::PressAKey),KDPoint(rect.left()+80, rect.top()+20)); +void ToolboxIonKeys::toolboxIonView::drawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(rect, Palette::WallScreen); + ctx->strokeRect(rect, Palette::ListCellBorder); + KDSize fontSize = KDFont::LargeFont->glyphSize(); + const char * message = I18n::translate(I18n::Message::PressAKey); + // Get the starting position of the text to center it. + KDPoint textPosition = KDPoint(rect.size().width() / 2 - strlen(message) * fontSize.width() / 2, + rect.size().height() / 2 - fontSize.height() / 2); - } + ctx->drawString(message, textPosition, KDFont::LargeFont, Palette::PrimaryText, Palette::WallScreen); - View * toolboxIonKeys::view(){ - return &m_view; - } +} + +View * ToolboxIonKeys::view() { + return &m_view; +} } diff --git a/apps/code/toolbox_ion_keys.h b/apps/code/toolbox_ion_keys.h index 4efe2036d9e..d6706ea3498 100644 --- a/apps/code/toolbox_ion_keys.h +++ b/apps/code/toolbox_ion_keys.h @@ -2,9 +2,9 @@ namespace Code { - class toolboxIonKeys : public ViewController { + class ToolboxIonKeys : public ViewController { public : - toolboxIonKeys(); + ToolboxIonKeys(); View * view() override; bool handleEvent(Ion::Events::Event e) override; void setSender(InputEventHandler * sender) { m_sender = sender; } diff --git a/apps/code/variable_box_controller.cpp b/apps/code/variable_box_controller.cpp index 279c9c57c44..ac39f9986a3 100644 --- a/apps/code/variable_box_controller.cpp +++ b/apps/code/variable_box_controller.cpp @@ -128,7 +128,7 @@ void VariableBoxController::willDisplayCellForIndex(HighlightCell * cell, int in I18n::Message::BuiltinsAndKeywords, I18n::Message::ImportedModulesAndScripts }; - static_cast(cell)->setMessage(subtitleMessages[(int)cellOrigin]); + static_cast *>(cell)->setMessage(subtitleMessages[(int)cellOrigin]); } void VariableBoxController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { @@ -365,7 +365,7 @@ int VariableBoxController::typeAndOriginAtLocation(int i, NodeOrigin * resultOri } -bool VariableBoxController::selectLeaf(int rowIndex) { +bool VariableBoxController::selectLeaf(int rowIndex, bool quitToolbox) { assert(rowIndex >= 0 && rowIndex < numberOfRows()); m_selectableTableView.deselectTable(); @@ -516,7 +516,7 @@ void VariableBoxController::loadBuiltinNodes(const char * textToAutocomplete, in assert(sizeof(builtinNames) / sizeof(builtinNames[0]) == k_totalBuiltinNodesCount); for (int i = 0; i < k_totalBuiltinNodesCount; i++) { if (addNodeIfMatches(textToAutocomplete, textToAutocompleteLength, builtinNames[i].type, NodeOrigin::Builtins, builtinNames[i].name)) { - /* We can leverage on the fact that buitin nodes are stored in + /* We can leverage on the fact that builtin nodes are stored in * alphabetical order. */ return; } @@ -575,7 +575,7 @@ void VariableBoxController::loadImportedVariablesInScript(const char * scriptCon } void VariableBoxController::loadCurrentVariablesInScript(const char * scriptContent, const char * textToAutocomplete, int textToAutocompleteLength) { - /* To find variable and funtion names: we lex the script and keep all + /* To find variable and function names: we lex the script and keep all * MP_TOKEN_NAME that complete the text to autocomplete and are not already in * the builtins or imported scripts. */ @@ -653,7 +653,7 @@ void VariableBoxController::loadGlobalAndImportedVariablesInScriptAsImported(Scr /* At this point, if the script node is not of type "file_input_2", it * will not have main structures of the wanted type. * We look for structures at first level (not inside nested scopes) that - * are either dunction definitions, variables statements or imports. */ + * are either function definitions, variables statements or imports. */ size_t n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); for (size_t i = 0; i < n; i++) { mp_parse_node_t child = pns->nodes[i]; @@ -823,18 +823,18 @@ bool VariableBoxController::importationSourceIsModule(const char * sourceName, c return true; } // The sourceName might be a module that is not in the toolbox - return mp_module_get(qstr_from_str(sourceName)) != MP_OBJ_NULL; + return mp_module_get_loaded_or_builtin(qstr_from_str(sourceName)) != MP_OBJ_NULL; } -bool VariableBoxController::importationSourceIsScript(const char * sourceName, const char * * scriptFullName, Script * retreivedScript) { +bool VariableBoxController::importationSourceIsScript(const char * sourceName, const char * * scriptFullName, Script * retrievedScript) { // Try fetching the nodes from a script Script importedScript = ScriptStore::ScriptBaseNamed(sourceName); if (importedScript.isNull()) { return false; } *scriptFullName = importedScript.fullName(); - if (retreivedScript != nullptr) { - *retreivedScript = importedScript; + if (retrievedScript != nullptr) { + *retrievedScript = importedScript; } return true; } diff --git a/apps/code/variable_box_controller.h b/apps/code/variable_box_controller.h index f8968b41b7f..1a5a525eb45 100644 --- a/apps/code/variable_box_controller.h +++ b/apps/code/variable_box_controller.h @@ -43,9 +43,9 @@ class VariableBoxController : public AlternateEmptyNestedMenuController { private: constexpr static size_t k_maxNumberOfDisplayedItems = (Ion::Display::Height - Metric::TitleBarHeight - Metric::PopUpTopMargin) / ScriptNodeCell::k_simpleItemHeight + 2; // +2 if the cells are cropped on top and at the bottom - constexpr static size_t k_maxScriptNodesCount = 32; // Chosen without particular reasons + constexpr static size_t k_maxScriptNodesCount = 64; // Chosen without particular reasons (Number of functions in the variables box) constexpr static int k_totalBuiltinNodesCount = 107; - constexpr static uint8_t k_scriptOriginsCount = 3; + constexpr static uint8_t k_scriptOriginsCount = 8; // Number of scripts loaded in the variable box constexpr static uint8_t k_subtitleCellType = NodeCellType; // We don't care as it is not selectable constexpr static uint8_t k_itemCellType = LeafCellType; // So that upper class NestedMenuController knows it's a leaf constexpr static KDCoordinate k_subtitleRowHeight = 23; @@ -80,7 +80,7 @@ class VariableBoxController : public AlternateEmptyNestedMenuController { // NestedMenuController HighlightCell * leafCellAtIndex(int index) override { assert(false); return nullptr; } HighlightCell * nodeCellAtIndex(int index) override { assert(false); return nullptr; } - bool selectLeaf(int rowIndex) override; + bool selectLeaf(int rowIndex, bool quitToolbox) override; void insertTextInCaller(const char * text, int textLength = -1); // Loading @@ -92,7 +92,7 @@ class VariableBoxController : public AlternateEmptyNestedMenuController { bool addNodesFromImportMaybe(mp_parse_node_struct_t * parseNode, const char * textToAutocomplete, int textToAutocompleteLength, bool importFromModules = true); const char * importationSourceNameFromNode(mp_parse_node_t & node); bool importationSourceIsModule(const char * sourceName, const ToolboxMessageTree * * moduleChildren = nullptr, int * numberOfModuleChildren = nullptr); - bool importationSourceIsScript(const char * sourceName, const char * * scriptFullName, Script * retreivedScript = nullptr); + bool importationSourceIsScript(const char * sourceName, const char * * scriptFullName, Script * retrievedScript = nullptr); bool addImportStructFromScript(mp_parse_node_struct_t * pns, uint structKind, const char * scriptName, const char * textToAutocomplete, int textToAutocompleteLength); /* Add a node if it completes the text to autocomplete and if it is not * already contained in the variable box. The returned boolean means we @@ -104,7 +104,7 @@ class VariableBoxController : public AlternateEmptyNestedMenuController { ScriptNode m_builtinNodes[k_totalBuiltinNodesCount]; ScriptNode m_importedNodes[k_maxScriptNodesCount]; ScriptNodeCell m_itemCells[k_maxNumberOfDisplayedItems]; - MessageTableCell m_subtitleCells[k_scriptOriginsCount]; + MessageTableCell<> m_subtitleCells[k_scriptOriginsCount]; ScriptStore * m_scriptStore; size_t m_currentScriptNodesCount; size_t m_builtinNodesCount; diff --git a/apps/dummy_timer_manager.cpp b/apps/dummy_timer_manager.cpp new file mode 100644 index 00000000000..1855d494017 --- /dev/null +++ b/apps/dummy_timer_manager.cpp @@ -0,0 +1,6 @@ +#include + +// This is the dummy implementation used in tests + +void TimerManager::AddTimer(Timer * timer) { } +void TimerManager::RemoveTimer(Timer * timer) { } diff --git a/apps/external/app/sample.c b/apps/external/app/sample.c index dbc13fd9545..a779bf2f42d 100644 --- a/apps/external/app/sample.c +++ b/apps/external/app/sample.c @@ -1,6 +1,15 @@ #include +#if defined _FXCG || defined NSPIRE_NEWLIB +// On the port, we use the Built-in file manager to import files. +void host_filemanager(); +void extapp_main() { + host_filemanager(); +} +#else +// Elsewhere, just draw a rectangle to test the extapp API. void extapp_main() { extapp_pushRectUniform(10, 10, LCD_WIDTH-20, LCD_HEIGHT-20, 0); extapp_msleep(1000); } +#endif diff --git a/apps/external/archive.cpp b/apps/external/archive.cpp index 8f3d000bf7c..285e262a586 100644 --- a/apps/external/archive.cpp +++ b/apps/external/archive.cpp @@ -40,9 +40,10 @@ bool isExamModeAndFileNotExecutable(const TarHeader* tar) { } bool fileAtIndex(size_t index, File &entry) { - if (index == -1) + if (index == -1) { return false; - + } + const TarHeader* tar = reinterpret_cast(0x90200000); unsigned size = 0; @@ -74,6 +75,8 @@ bool fileAtIndex(size_t index, File &entry) { entry.data = reinterpret_cast(tar) + sizeof(TarHeader); entry.dataLength = size; entry.isExecutable = (tar->mode[4] & 0x01) == 1; + // TODO: Handle the trash + entry.readable = true; return true; } else { @@ -111,18 +114,31 @@ uint32_t executeFile(const char *name, void * heap, const uint32_t heapSize) { return -1; } -int indexFromName(const char *name) { - File entry; - for (int i = 0; fileAtIndex(i, entry); i++) { - if (strcmp(name, entry.name) == 0) { - return i; - } +#else + +bool fileAtIndex(size_t index, File &entry) { + if (index != 0) { + return false; } - return -1; + entry.name = "Built-in"; + entry.data = NULL; + entry.dataLength = 0; + entry.isExecutable = true; + entry.readable = true; + return true; +} + +extern "C" void extapp_main(void); + +uint32_t executeFile(const char *name, void * heap, const uint32_t heapSize) { + extapp_main(); + return 0; } +#endif + size_t numberOfFiles() { File dummy; size_t count; @@ -132,6 +148,18 @@ size_t numberOfFiles() { return count; } +int indexFromName(const char *name) { + File entry; + + for (int i = 0; fileAtIndex(i, entry); i++) { + if (entry.readable && strcmp(name, entry.name) == 0) { + return i; + } + } + + return -1; +} + bool executableAtIndex(size_t index, File &entry) { File dummy; size_t count; @@ -144,16 +172,19 @@ bool executableAtIndex(size_t index, File &entry) { entry.data = dummy.data; entry.dataLength = dummy.dataLength; entry.isExecutable = dummy.isExecutable; + entry.readable = dummy.readable; return true; } final_count++; } } - return false; } size_t numberOfExecutables() { + if (!GlobalPreferences::sharedGlobalPreferences()->externalAppShown()) { + return false; + } File dummy; size_t count; size_t final_count = 0; @@ -165,48 +196,5 @@ size_t numberOfExecutables() { return final_count; } - - -#else - -bool fileAtIndex(size_t index, File &entry) { - if (index != 0) - return false; - - entry.name = "Built-in"; - entry.data = NULL; - entry.dataLength = 0; - entry.isExecutable = true; - return true; -} - -bool executableAtIndex(size_t index, File &entry) { - return fileAtIndex(index, entry); -} - -size_t numberOfExecutables() { - return 1; -} - -extern "C" void extapp_main(void); - -uint32_t executeFile(const char *name, void * heap, const uint32_t heapSize) { - extapp_main(); - return 0; -} - -int indexFromName(const char *name) { - if (strcmp(name, "Built-in") == 0) - return 0; - else - return -1; -} - -size_t numberOfFiles() { - return 1; -} - -#endif - } } diff --git a/apps/external/archive.h b/apps/external/archive.h index 670bd138f42..de96e6d204f 100644 --- a/apps/external/archive.h +++ b/apps/external/archive.h @@ -14,6 +14,7 @@ struct File { const uint8_t *data; size_t dataLength; bool isExecutable; + bool readable; }; bool fileAtIndex(size_t index, File &entry); diff --git a/apps/external/base.it.i18n b/apps/external/base.it.i18n index 49b8a217333..d1341ea6708 100644 --- a/apps/external/base.it.i18n +++ b/apps/external/base.it.i18n @@ -1,9 +1,9 @@ -ExternalApp = "External" -ExternalAppCapital = "EXTERNAL" -ExternalAppApiMismatch = "API mismatch" -ExternalAppExecError = "Cannot execute file" -ExternalNotCompatible = "External is not compatible" -WithSimulator = "with the simulator" -WithN0100 = "with n0100" -GetMoreAppsAt = "Get more apps at" -NoAppsInstalled = "No apps installed" +ExternalApp = "Esterna" +ExternalAppCapital = "ESTERNA" +ExternalAppApiMismatch = "Discordanza di API" +ExternalAppExecError = "Impossibile eseguire file" +ExternalNotCompatible = "Esterna non compatibile" +WithSimulator = "con il simulatore" +WithN0100 = "con n0100" +GetMoreAppsAt = "Ottieni altre app a" +NoAppsInstalled = "Nessuna app installata" diff --git a/apps/external/extapp_api.cpp b/apps/external/extapp_api.cpp index eefd19f5810..23254ab9146 100644 --- a/apps/external/extapp_api.cpp +++ b/apps/external/extapp_api.cpp @@ -9,10 +9,16 @@ #include "../apps_container.h" #include "../global_preferences.h" +#ifdef DEVICE +#include +#include +#include +#endif + #include extern "C" { - #include +#include } uint64_t extapp_millis() { @@ -30,22 +36,34 @@ uint64_t extapp_scanKeyboard() { void extapp_pushRect(int16_t x, int16_t y, uint16_t w, uint16_t h, const uint16_t * pixels) { KDRect rect(x, y, w, h); - Ion::Display::pushRect(rect, reinterpret_cast(pixels)); + Ion::Display::pushRect(rect, reinterpret_cast(pixels)); + #ifndef DEVICE + // Refresh the display. + Ion::Keyboard::scan(); + #endif } void extapp_pushRectUniform(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t color) { KDRect rect(x, y, w, h); Ion::Display::pushRectUniform(rect, KDColor::RGB16(color)); + #ifndef DEVICE + // Refresh the display. + Ion::Keyboard::scan(); + #endif } void extapp_pullRect(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t * pixels) { KDRect rect(x, y, w, h); - Ion::Display::pullRect(rect, (KDColor *) pixels); + Ion::Display::pullRect(rect, (KDColor *)pixels); + #ifndef DEVICE + // Refresh the display. + Ion::Keyboard::scan(); + #endif } -int16_t extapp_drawTextLarge(const char *text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { +int16_t extapp_drawTextLarge(const char * text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { KDPoint point(x, y); auto ctx = KDIonContext::sharedContext(); @@ -53,10 +71,15 @@ int16_t extapp_drawTextLarge(const char *text, int16_t x, int16_t y, uint16_t fg ctx->setOrigin(KDPoint(0, 0)); point = ctx->drawString(text, point, KDFont::LargeFont, KDColor::RGB16(fg), KDColor::RGB16(bg)); + #ifndef DEVICE + // Refresh the display. + Ion::Keyboard::scan(); + #endif + return point.x(); } -int16_t extapp_drawTextSmall(const char *text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { +int16_t extapp_drawTextSmall(const char * text, int16_t x, int16_t y, uint16_t fg, uint16_t bg, bool fake) { KDPoint point(x, y); auto ctx = KDIonContext::sharedContext(); @@ -64,6 +87,11 @@ int16_t extapp_drawTextSmall(const char *text, int16_t x, int16_t y, uint16_t fg ctx->setOrigin(KDPoint(0, 0)); point = ctx->drawString(text, point, KDFont::SmallFont, KDColor::RGB16(fg), KDColor::RGB16(bg)); + #ifndef DEVICE + // Refresh the display. + Ion::Keyboard::scan(); + #endif + return point.x(); } @@ -71,7 +99,7 @@ bool extapp_waitForVBlank() { return Ion::Display::waitForVBlank(); } -void extapp_clipboardStore(const char *text) { +void extapp_clipboardStore(const char * text) { Clipboard::sharedClipboard()->store(text); } @@ -79,101 +107,103 @@ const char * extapp_clipboardText() { return Clipboard::sharedClipboard()->storedText(); } +bool match(const char * filename, const char * extension) { + return strcmp(filename + strlen(filename) - strlen(extension), extension) == 0; +} + int extapp_fileListWithExtension(const char ** filenames, int maxrecords, const char * extension, int storage) { - if(storage == EXTAPP_RAM_FILE_SYSTEM) { + int j = 0; + if (storage == EXTAPP_RAM_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { int n = Ion::Storage::sharedStorage()->numberOfRecordsWithExtension(extension); if (n > maxrecords) { n = maxrecords; } - for(int i = 0; i < n; i++) { - filenames[i] = Ion::Storage::sharedStorage()->recordWithExtensionAtIndex(extension, i).fullName(); + for (; j < n; j++) { + filenames[j] = Ion::Storage::sharedStorage()->recordWithExtensionAtIndex(extension, j).fullName(); } - return n; - } else if(storage == EXTAPP_FLASH_FILE_SYSTEM) { - // TODO: filter by extension - int n = External::Archive::numberOfFiles(); - if (n > maxrecords) { - n = maxrecords; + if (j == maxrecords) { + return j; } - for(int i = 0; i < n; i++) { + } + // Don't read external files the exam mode is enabled + if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) return j; + if (storage == EXTAPP_FLASH_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { + int n = External::Archive::numberOfFiles(); + for (int i = 0; i < n && j < maxrecords; i++) { External::Archive::File entry; - External::Archive::fileAtIndex(i, entry); - filenames[i] = entry.name; + // Filter extension + if (External::Archive::fileAtIndex(i, entry) && match(entry.name, extension)) { + filenames[j] = entry.name; + ++j; + } } - return n; - } else { - return 0; } + return j; } bool extapp_fileExists(const char * filename, int storage) { - if(storage == EXTAPP_RAM_FILE_SYSTEM) { - return !Ion::Storage::sharedStorage()->recordNamed(filename).isNull(); - } else if(storage == EXTAPP_FLASH_FILE_SYSTEM) { + if (storage == EXTAPP_RAM_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { + if (!Ion::Storage::sharedStorage()->recordNamed(filename).isNull()) + return true; + } + if (storage == EXTAPP_FLASH_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { return External::Archive::indexFromName(filename) >= 0; - } else { - return false; } + return false; } bool extapp_fileErase(const char * filename, int storage) { - if(storage == EXTAPP_RAM_FILE_SYSTEM) { + if (storage == EXTAPP_RAM_FILE_SYSTEM) { Ion::Storage::Record record = Ion::Storage::sharedStorage()->recordNamed(filename); - if(record.isNull()) { + if (record.isNull()) { return false; - } else { - record.destroy(); - return true; } - } else { - return false; + record.destroy(); + return true; } + return false; } -const char * extapp_fileRead(const char * filename, size_t *len, int storage) { - if(storage == EXTAPP_RAM_FILE_SYSTEM) { +const char * extapp_fileRead(const char * filename, size_t * len, int storage) { + if (storage == EXTAPP_RAM_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { const Ion::Storage::Record record = Ion::Storage::sharedStorage()->recordNamed(filename); - if (record.isNull()) { - return NULL; - } else { - if(len) { - *len = record.value().size; - } - return (const char *) record.value().buffer; + if (!record.isNull()){ + int delta = 0; + if (match(filename, ".py") || match(filename, ".xw")) + delta++; + // skip record type + if (len) + *len = record.value().size - delta; + return (const char *)record.value().buffer + delta; } - } else if(storage == EXTAPP_FLASH_FILE_SYSTEM) { + } + if (storage == EXTAPP_FLASH_FILE_SYSTEM || storage == EXTAPP_BOTH_FILE_SYSTEM) { int index = External::Archive::indexFromName(filename); if (index >= 0) { External::Archive::File entry; External::Archive::fileAtIndex(index, entry); - if(len) { + if (len) { *len = entry.dataLength; } return (const char *)entry.data; - } else { - return NULL; } - } else { - return NULL; } + return NULL; } bool extapp_fileWrite(const char * filename, const char * content, size_t len, int storage) { - if(storage == EXTAPP_RAM_FILE_SYSTEM) { + if (storage == EXTAPP_RAM_FILE_SYSTEM) { Ion::Storage::Record::ErrorStatus status = Ion::Storage::sharedStorage()->createRecordWithFullName(filename, content, len); if (status == Ion::Storage::Record::ErrorStatus::NameTaken) { Ion::Storage::Record::Data data; data.buffer = content; data.size = len; return Ion::Storage::sharedStorage()->recordNamed(filename).setValue(data) == Ion::Storage::Record::ErrorStatus::None; - } else if (status == Ion::Storage::Record::ErrorStatus::None) { - return true; - } else { - return false; } - } else { - return false; + if (status == Ion::Storage::Record::ErrorStatus::None) + return true; } + return false; } static void reloadTitleBar() { @@ -244,19 +274,42 @@ const int16_t translated_keys[] = #define TICKS_PER_MINUTE 11862 #endif -int extapp_getKey(bool allowSuspend, bool *alphaWasActive) { + +int extapp_restoreBackup(int mode) { + // Restoring the backup is allowed even if the write protection is enabled, because it may have been writted by Khi.x + if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) + return 0; + size_t length = 32 * 1024; + if (mode == -1) { // restore backup saved when exam mode was set + uint8_t * src = (uint8_t *)(0x90800000 - 2 * length); + if (src[0] == 0xba && src[1] == 0xdd && src[2] == 0x0b && src[3] == 0xee) { + memcpy((uint8_t *)Ion::storageAddress(), src, length); + return 1; + } + } + if (mode >= 0 && mode < 16) { + uint8_t * src = (uint8_t *)(0x90180000 + mode * length); + if (src[0] == 0xba && src[1] == 0xdd && src[2] == 0x0b && src[3] == 0xee) { + memcpy((uint8_t *)Ion::storageAddress(), src, length); + return 1; + } + } + return 0; +} + +int extapp_getKey(int allowSuspend, bool * alphaWasActive) { int key = -1; size_t t1 = Ion::Timing::millis(); for (;;) { int timeout = 10000; - if(alphaWasActive) { + if (alphaWasActive) { *alphaWasActive = Ion::Events::isAlphaActive(); } Ion::Events::Event event = Ion::Events::getEvent(&timeout); reloadTitleBar(); if (event == Ion::Events::None) { size_t t2 = Ion::Timing::millis(); - if (t2 - t1 > 2 * TICKS_PER_MINUTE) { + if (t2 - t1 > 3 * TICKS_PER_MINUTE) { event = Ion::Events::OnOff; } } else { @@ -270,10 +323,10 @@ int extapp_getKey(bool allowSuspend, bool *alphaWasActive) { } if (event.isKeyboardEvent()) { key = static_cast(event); - if (key == 17 || key == 4 || key == 5 || key == 52) { + if (key == (int)Ion::Keyboard::Key::Backspace || key == (int)Ion::Keyboard::Key::OK || key == (int)Ion::Keyboard::Key::Back || key == (int)Ion::Keyboard::Key::EXE) { extapp_resetKeyboard(); } - if (allowSuspend && (key == 7 || key == 8)) { // power + if (allowSuspend && key == (int)Ion::Keyboard::Key::OnOff) { Ion::Power::suspend(true); extapp_pushRectUniform(0, 0, 320, 240, 65535); Ion::Backlight::setBrightness(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()); @@ -285,6 +338,45 @@ int extapp_getKey(bool allowSuspend, bool *alphaWasActive) { return translated_keys[key]; } +bool extapp_isKeydown(int key) { + Ion::Keyboard::State scan = Ion::Keyboard::scan(); + return scan.keyDown(Ion::Keyboard::Key(key)); +} + +bool extapp_eraseSector(void * ptr) { +#ifdef DEVICE + if (ptr == 0) + Ion::Device::Reset::core(); + // Disable flash writting + if (GlobalPreferences::sharedGlobalPreferences()->externalAppWritePermission()) { + int i = Ion::Device::Flash::SectorAtAddress((size_t)ptr); + if (i < 0) + return false; + Ion::Device::Flash::EraseSector(i); + return true; + } +#endif + return false; +} + +bool extapp_writeMemory(unsigned char * dest, const unsigned char * data, size_t length) { +#ifdef DEVICE + // Disable flash writting + if (GlobalPreferences::sharedGlobalPreferences()->externalAppWritePermission()) { + int n = Ion::Device::Flash::SectorAtAddress((uint32_t)dest); + if (n < 0) + return false; + Ion::Device::Flash::WriteMemory(dest, (unsigned char *)data, length); + return true; + } +#endif + return false; +} + +bool extapp_inExamMode() { + return GlobalPreferences::sharedGlobalPreferences()->isInExamMode(); +} + extern "C" void (* const apiPointers[])(void) = { (void (*)(void)) extapp_millis, (void (*)(void)) extapp_msleep, @@ -305,5 +397,10 @@ extern "C" void (* const apiPointers[])(void) = { (void (*)(void)) extapp_lockAlpha, (void (*)(void)) extapp_resetKeyboard, (void (*)(void)) extapp_getKey, + (void (*)(void)) extapp_isKeydown, + (void (*)(void)) extapp_restoreBackup, + (void (*)(void)) extapp_eraseSector, + (void (*)(void)) extapp_writeMemory, + (void (*)(void)) extapp_inExamMode, (void (*)(void)) nullptr, -}; +}; \ No newline at end of file diff --git a/apps/external/extapp_api.h b/apps/external/extapp_api.h index b17b8643673..9a98e5526d4 100644 --- a/apps/external/extapp_api.h +++ b/apps/external/extapp_api.h @@ -18,6 +18,7 @@ #define EXTAPP_RAM_FILE_SYSTEM 0 #define EXTAPP_FLASH_FILE_SYSTEM 1 +#define EXTAPP_BOTH_FILE_SYSTEM 2 #define SCANCODE_Left ((uint64_t)1 << 0) #define SCANCODE_Up ((uint64_t)1 << 1) @@ -251,6 +252,10 @@ EXTERNC const char * extapp_fileRead(const char * filename, size_t *len, int sto EXTERNC bool extapp_fileWrite(const char * filename, const char * content, size_t len, int storage); EXTERNC void extapp_lockAlpha(); EXTERNC void extapp_resetKeyboard(); -EXTERNC int extapp_getKey(bool allowSuspend, bool *alphaWasActive); +EXTERNC int extapp_getKey(int allowSuspend, bool *alphaWasActive); +EXTERNC bool extapp_isKeydown(int key); +EXTERNC int extapp_restoreBackup(int mode); // Keep for compatibility with KhiCAS on Khi +EXTERNC bool extapp_eraseSector(void * ptr); +EXTERNC bool extapp_writeMemory(unsigned char * dest,const unsigned char * data,size_t length); #endif diff --git a/apps/global_preferences.cpp b/apps/global_preferences.cpp index 938cff8616a..c0eac31c866 100644 --- a/apps/global_preferences.cpp +++ b/apps/global_preferences.cpp @@ -41,6 +41,25 @@ void GlobalPreferences::setBrightnessLevel(int brightnessLevel) { brightnessLevel = brightnessLevel < 0 ? 0 : brightnessLevel; brightnessLevel = brightnessLevel > Ion::Backlight::MaxBrightness ? Ion::Backlight::MaxBrightness : brightnessLevel; m_brightnessLevel = brightnessLevel; - Ion::Backlight::setBrightness(m_brightnessLevel); } } + +void GlobalPreferences::setIdleBeforeSuspendSeconds(int idleBeforeSuspendSeconds) { + if (m_idleBeforeSuspendSeconds != idleBeforeSuspendSeconds) { + idleBeforeSuspendSeconds = idleBeforeSuspendSeconds < 5 ? 5 : idleBeforeSuspendSeconds; + idleBeforeSuspendSeconds = idleBeforeSuspendSeconds > 7200 ? 7200 : idleBeforeSuspendSeconds; + m_idleBeforeSuspendSeconds = idleBeforeSuspendSeconds; + } +} + +void GlobalPreferences::setIdleBeforeDimmingSeconds(int idleBeforeDimmingSeconds) { + if (m_idleBeforeDimmingSeconds != idleBeforeDimmingSeconds) { + idleBeforeDimmingSeconds = idleBeforeDimmingSeconds < 3 ? 3 : idleBeforeDimmingSeconds; + idleBeforeDimmingSeconds = idleBeforeDimmingSeconds > 1200 ? 1200 : idleBeforeDimmingSeconds; + m_idleBeforeDimmingSeconds = idleBeforeDimmingSeconds; + } +} + +void GlobalPreferences::setBrightnessShortcut(int brightnessShortcut){ + m_brightnessShortcut = brightnessShortcut; +} diff --git a/apps/global_preferences.h b/apps/global_preferences.h index 4367c20f788..3077b93fee2 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -30,11 +30,27 @@ class GlobalPreferences { void setTempExamMode(ExamMode examMode); bool showPopUp() const { return m_showPopUp; } void setShowPopUp(bool showPopUp) { m_showPopUp = showPopUp; } + bool dfuUnlocked() const { return m_dfuUnlocked; } + void setDfuUnlocked(bool unlocked) { m_dfuUnlocked = unlocked; } + bool autocomplete() const { return m_autoComplete; } + void setAutocomplete(bool autocomple) { m_autoComplete = autocomple; } + bool syntaxhighlighting() const { return m_syntaxhighlighting; } + void setSyntaxhighlighting(bool syntaxhighlight) { m_syntaxhighlighting = syntaxhighlight; } int brightnessLevel() const { return m_brightnessLevel; } void setBrightnessLevel(int brightnessLevel); const KDFont * font() const { return m_font; } void setFont(const KDFont * font) { m_font = font; } constexpr static int NumberOfBrightnessStates = 15; + int idleBeforeSuspendSeconds() const { return m_idleBeforeSuspendSeconds; } + void setIdleBeforeSuspendSeconds(int m_idleBeforeSuspendSeconds); + int idleBeforeDimmingSeconds() const { return m_idleBeforeDimmingSeconds; } + void setIdleBeforeDimmingSeconds(int m_idleBeforeDimmingSeconds); + int brightnessShortcut() const { return m_brightnessShortcut; } + void setBrightnessShortcut(int m_BrightnessShortcut); + bool externalAppWritePermission() const { return m_externalAppWritePermission; } + void setExternalAppWritePermission(bool extapp_write) { m_externalAppWritePermission = extapp_write; } + bool externalAppShown() const { return m_externalAppShown; } + void setExternalAppShown(bool externalAppShown) { m_externalAppShown = externalAppShown; } private: static_assert(I18n::NumberOfLanguages > 0, "I18n::NumberOfLanguages is not superior to 0"); // There should already have been an error when processing an empty EPSILON_I18N flag static_assert(I18n::NumberOfCountries > 0, "I18n::NumberOfCountries is not superior to 0"); // There should already have been an error when processing an empty EPSILON_COUNTRIES flag @@ -44,7 +60,15 @@ class GlobalPreferences { m_examMode(ExamMode::Unknown), m_tempExamMode(ExamMode::Standard), m_showPopUp(true), + m_dfuUnlocked(false), + m_autoComplete(true), + m_syntaxhighlighting(true), m_brightnessLevel(Ion::Backlight::MaxBrightness), + m_idleBeforeSuspendSeconds(55), + m_idleBeforeDimmingSeconds(45), + m_brightnessShortcut(4), + m_externalAppWritePermission(false), + m_externalAppShown(true), m_font(KDFont::LargeFont) {} I18n::Language m_language; I18n::Country m_country; @@ -53,7 +77,15 @@ class GlobalPreferences { mutable ExamMode m_examMode; mutable ExamMode m_tempExamMode; bool m_showPopUp; + bool m_dfuUnlocked; + bool m_autoComplete; + bool m_syntaxhighlighting; int m_brightnessLevel; + int m_idleBeforeSuspendSeconds; + int m_idleBeforeDimmingSeconds; + int m_brightnessShortcut; + bool m_externalAppWritePermission; + bool m_externalAppShown; const KDFont * m_font; }; diff --git a/apps/graph/app.cpp b/apps/graph/app.cpp index 68a4b4a9e7f..b4a74d8e4fc 100644 --- a/apps/graph/app.cpp +++ b/apps/graph/app.cpp @@ -27,7 +27,8 @@ const Image * App::Descriptor::icon() { App::Snapshot::Snapshot() : Shared::FunctionApp::Snapshot::Snapshot(), m_functionStore(), - m_graphRange() + m_graphRange(), + m_shouldDisplayDerivative(false) { } @@ -40,6 +41,7 @@ void App::Snapshot::reset() { for (int i = 0; i < Shared::ContinuousFunction::k_numberOfPlotTypes; i++) { m_interval[i].reset(); } + m_shouldDisplayDerivative = false; } App::Descriptor * App::Snapshot::descriptor() { @@ -58,7 +60,7 @@ App::App(Snapshot * snapshot) : m_listFooter(&m_listHeader, &m_listController, &m_listController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGray), m_listHeader(&m_listStackViewController, &m_listFooter, &m_listController), m_listStackViewController(&m_tabViewController, &m_listHeader), - m_graphController(&m_graphAlternateEmptyViewController, this, snapshot->graphRange(), snapshot->cursor(), snapshot->indexFunctionSelectedByCursor(), snapshot->rangeVersion(), &m_graphHeader), + m_graphController(&m_graphAlternateEmptyViewController, this, snapshot->graphRange(), snapshot->cursor(), snapshot->indexFunctionSelectedByCursor(), snapshot->rangeVersion(), &m_graphHeader, snapshot->shouldDisplayDerivative()), m_graphAlternateEmptyViewController(&m_graphHeader, &m_graphController, &m_graphController), m_graphHeader(&m_graphStackViewController, &m_graphAlternateEmptyViewController, &m_graphController), m_graphStackViewController(&m_tabViewController, &m_graphHeader), diff --git a/apps/graph/app.h b/apps/graph/app.h index f9255732908..715c8bd2d7f 100644 --- a/apps/graph/app.h +++ b/apps/graph/app.h @@ -31,11 +31,13 @@ class App : public Shared::FunctionApp { Shared::Interval * intervalForType(Shared::ContinuousFunction::PlotType plotType) { return m_interval + static_cast(plotType); } + bool * shouldDisplayDerivative() { return &m_shouldDisplayDerivative; } private: void tidy() override; ContinuousFunctionStore m_functionStore; Shared::InteractiveCurveViewRange m_graphRange; Shared::Interval m_interval[Shared::ContinuousFunction::k_numberOfPlotTypes]; + bool m_shouldDisplayDerivative; }; static App * app() { return static_cast(Container::activeApp()); diff --git a/apps/graph/graph/calculation_parameter_controller.cpp b/apps/graph/graph/calculation_parameter_controller.cpp index 93f98086fda..3e726666263 100644 --- a/apps/graph/graph/calculation_parameter_controller.cpp +++ b/apps/graph/graph/calculation_parameter_controller.cpp @@ -109,7 +109,7 @@ void CalculationParameterController::willDisplayCellForIndex(HighlightCell * cel assert(index >= 0 && index <= numberOfRows()); if (cell != &m_preimageCell) { I18n::Message titles[] = {I18n::Message::Intersection, I18n::Message::Maximum, I18n::Message::Minimum, I18n::Message::Zeros, I18n::Message::Tangent, I18n::Message::Integral}; - static_cast(cell)->setMessage(titles[index - 1 + !shouldDisplayIntersection()]); + static_cast *>(cell)->setMessage(titles[index - 1 + !shouldDisplayIntersection()]); } } diff --git a/apps/graph/graph/calculation_parameter_controller.h b/apps/graph/graph/calculation_parameter_controller.h index ffb38e6919a..0297b5b38c3 100644 --- a/apps/graph/graph/calculation_parameter_controller.h +++ b/apps/graph/graph/calculation_parameter_controller.h @@ -32,9 +32,9 @@ class CalculationParameterController : public ViewController, public ListViewDat void setRecord(Ion::Storage::Record record); private: bool shouldDisplayIntersection() const; - MessageTableCellWithChevron m_preimageCell; + MessageTableCellWithChevron<> m_preimageCell; constexpr static int k_totalNumberOfReusableCells = 6; - MessageTableCell m_cells[k_totalNumberOfReusableCells]; + MessageTableCell<> m_cells[k_totalNumberOfReusableCells]; SelectableTableView m_selectableTableView; Ion::Storage::Record m_record; PreimageParameterController m_preimageParameterController; diff --git a/apps/graph/graph/curve_parameter_controller.h b/apps/graph/graph/curve_parameter_controller.h index d5a587af16d..a14e329564c 100644 --- a/apps/graph/graph/curve_parameter_controller.h +++ b/apps/graph/graph/curve_parameter_controller.h @@ -25,7 +25,7 @@ class CurveParameterController : public Shared::FunctionCurveParameterController Shared::FunctionGoToParameterController * goToParameterController() override; Shared::FunctionGoToParameterController m_goToParameterController; GraphController * m_graphController; - MessageTableCellWithChevron m_calculationCell; + MessageTableCellWithChevron<> m_calculationCell; MessageTableCellWithSwitch m_derivativeCell; CalculationParameterController m_calculationParameterController; }; diff --git a/apps/graph/graph/graph_controller.cpp b/apps/graph/graph/graph_controller.cpp index a6de8426862..a07d49b39c9 100644 --- a/apps/graph/graph/graph_controller.cpp +++ b/apps/graph/graph/graph_controller.cpp @@ -7,13 +7,13 @@ using namespace Shared; namespace Graph { -GraphController::GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, ButtonRowController * header) : +GraphController::GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, ButtonRowController * header, bool * shouldDisplayDerivative) : FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, curveViewRange, &m_view, cursor, indexFunctionSelectedByCursor, rangeVersion), m_bannerView(this, inputEventHandlerDelegate, this), 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) + m_displayDerivativeInBanner(shouldDisplayDerivative) { m_graphRange->setDelegate(this); } @@ -47,7 +47,7 @@ void GraphController::selectFunctionWithCursor(int functionIndex) { void GraphController::reloadBannerView() { Ion::Storage::Record record = functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor()); - bool displayDerivative = m_displayDerivativeInBanner && + bool displayDerivative = *m_displayDerivativeInBanner && functionStore()->modelForRecord(record)->plotType() == ContinuousFunction::PlotType::Cartesian; m_bannerView.setNumberOfSubviews(Shared::XYBannerView::k_numberOfSubviews + displayDerivative); FunctionGraphController::reloadBannerView(); diff --git a/apps/graph/graph/graph_controller.h b/apps/graph/graph/graph_controller.h index 433ab9ebe23..a740f4adcb5 100644 --- a/apps/graph/graph/graph_controller.h +++ b/apps/graph/graph/graph_controller.h @@ -15,13 +15,13 @@ namespace Graph { class GraphController : public Shared::FunctionGraphController, public GraphControllerHelper { public: - GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, ButtonRowController * header); + GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, ButtonRowController * header, bool * shouldDisplayDerivative); I18n::Message emptyMessage() override; void viewWillAppear() override; - bool displayDerivativeInBanner() const { return m_displayDerivativeInBanner; } - void setDisplayDerivativeInBanner(bool displayDerivative) { m_displayDerivativeInBanner = displayDerivative; } + bool displayDerivativeInBanner() const { return *m_displayDerivativeInBanner; } + void setDisplayDerivativeInBanner(bool displayDerivative) { *m_displayDerivativeInBanner = displayDerivative; } private: - int estimatedBannerNumberOfLines() const override { return 1 + m_displayDerivativeInBanner; } + int estimatedBannerNumberOfLines() const override { return 1 + *m_displayDerivativeInBanner; } void selectFunctionWithCursor(int functionIndex) override; BannerView * bannerView() override { return &m_bannerView; } void reloadBannerView() override; @@ -41,7 +41,7 @@ class GraphController : public Shared::FunctionGraphController, public GraphCont GraphView m_view; Shared::InteractiveCurveViewRange * m_graphRange; CurveParameterController m_curveParameterController; - bool m_displayDerivativeInBanner; + bool * m_displayDerivativeInBanner; }; } diff --git a/apps/graph/graph/graph_view.h b/apps/graph/graph/graph_view.h index ded212f6db3..87688114037 100644 --- a/apps/graph/graph/graph_view.h +++ b/apps/graph/graph/graph_view.h @@ -18,7 +18,13 @@ class GraphView : public Shared::FunctionGraphView { * 10.0938275501223 which are hopefully rare enough. * TODO: The drawCurve algorithm should use the derivative function to know * how fast the function moves... */ + #ifndef _FXCG static constexpr float k_graphStepDenominator = 10.0938275501223f; + #else + // This value rounded down has to be a factor of the horizontal resolution / 2 + // On the Casio calculator the resolution is 396 pixels, so 11 is close but works + static constexpr float k_graphStepDenominator = 11.0938275501223f; + #endif GraphView(Shared::InteractiveCurveViewRange * graphRange, Shared::CurveViewCursor * cursor, Shared::BannerView * bannerView, Shared::CursorView * cursorView); diff --git a/apps/graph/list/list_controller.h b/apps/graph/list/list_controller.h index 12deaf33bb2..b116f34a172 100644 --- a/apps/graph/list/list_controller.h +++ b/apps/graph/list/list_controller.h @@ -21,6 +21,8 @@ class ListController : public Shared::FunctionListController, public Shared::Tex bool textFieldDidAbortEditing(TextField * textField) override; bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; +protected: + virtual const char * recordExtension() const override { return Ion::Storage::funcExtension; } private: constexpr static int k_maxNumberOfDisplayableRows = 5; Shared::ListParameterController * parameterController() override; diff --git a/apps/graph/list/list_parameter_controller.cpp b/apps/graph/list/list_parameter_controller.cpp index 29b7bbdef70..01d266db2f2 100644 --- a/apps/graph/list/list_parameter_controller.cpp +++ b/apps/graph/list/list_parameter_controller.cpp @@ -41,7 +41,7 @@ bool ListParameterController::handleEvent(Ion::Events::Event event) { } if (event == Ion::Events::Right) { int selectedR = selectedRow(); - if (selectedR == 0 || selectedR == 1) { + if (selectedR == 0 || selectedR == 1 || selectedR == 3) { // Go in the submenu return handleEnterOnRow(selectedR); } diff --git a/apps/graph/list/list_parameter_controller.h b/apps/graph/list/list_parameter_controller.h index 31c311ba4be..2246ab8666f 100644 --- a/apps/graph/list/list_parameter_controller.h +++ b/apps/graph/list/list_parameter_controller.h @@ -28,7 +28,7 @@ class ListParameterController : public Shared::ListParameterController { MessageTableCellWithChevronAndBuffer m_functionDomain; TypeParameterController m_typeParameterController; DomainParameterController m_domainParameterController; - MessageTableCell m_renameCell; + MessageTableCell<> m_renameCell; }; } diff --git a/apps/graph/list/text_field_function_title_cell.h b/apps/graph/list/text_field_function_title_cell.h index 05d6dcab7e7..7dc4d962a39 100644 --- a/apps/graph/list/text_field_function_title_cell.h +++ b/apps/graph/list/text_field_function_title_cell.h @@ -11,7 +11,7 @@ class ListController; class TextFieldFunctionTitleCell : public Shared::FunctionTitleCell, public Responder { public: - TextFieldFunctionTitleCell(ListController * listController, Orientation orientation = Orientation::VerticalIndicator, const KDFont * font = KDFont::LargeFont); + TextFieldFunctionTitleCell(ListController * listController, Orientation orientation = Orientation::VerticalIndicator, const KDFont * font = KDFont::ItalicLargeFont); TextField * textField() { return &m_textField; } void setEditing(bool editing); bool isEditing() const; diff --git a/apps/graph/values/derivative_parameter_controller.h b/apps/graph/values/derivative_parameter_controller.h index 10d64bec0a4..2c353a1d947 100644 --- a/apps/graph/values/derivative_parameter_controller.h +++ b/apps/graph/values/derivative_parameter_controller.h @@ -33,9 +33,9 @@ class DerivativeParameterController : public ViewController, public SimpleListVi #endif constexpr static int k_maxNumberOfCharsInTitle = Shared::Function::k_maxNameWithArgumentSize + 1; // +1 for the ' of the derivative char m_pageTitle[k_maxNumberOfCharsInTitle]; - MessageTableCell m_hideColumn; + MessageTableCell<> m_hideColumn; #if COPY_COLUMN - MessageTableCellWithChevron m_copyColumn; + MessageTableCellWithChevron<> m_copyColumn; #endif SelectableTableView m_selectableTableView; Ion::Storage::Record m_record; diff --git a/apps/graph/values/interval_parameter_selector_controller.cpp b/apps/graph/values/interval_parameter_selector_controller.cpp index 80cd68f7431..7707c83ed83 100644 --- a/apps/graph/values/interval_parameter_selector_controller.cpp +++ b/apps/graph/values/interval_parameter_selector_controller.cpp @@ -69,7 +69,7 @@ int IntervalParameterSelectorController::reusableCellCount() const { void IntervalParameterSelectorController::willDisplayCellForIndex(HighlightCell * cell, int index) { assert(0 <= index && index < numberOfRows()); Shared::ContinuousFunction::PlotType plotType = plotTypeAtRow(index); - static_cast(cell)->setMessage(messageForType(plotType)); + static_cast *>(cell)->setMessage(messageForType(plotType)); } Shared::ContinuousFunction::PlotType IntervalParameterSelectorController::plotTypeAtRow(int j) const { diff --git a/apps/graph/values/interval_parameter_selector_controller.h b/apps/graph/values/interval_parameter_selector_controller.h index eef6e1b6a99..a37e714ad5d 100644 --- a/apps/graph/values/interval_parameter_selector_controller.h +++ b/apps/graph/values/interval_parameter_selector_controller.h @@ -24,7 +24,7 @@ class IntervalParameterSelectorController : public ViewController, public Simple private: Shared::ContinuousFunction::PlotType plotTypeAtRow(int j) const; I18n::Message messageForType(Shared::ContinuousFunction::PlotType plotType); - MessageTableCellWithChevron m_intervalParameterCell[Shared::ContinuousFunction::k_numberOfPlotTypes]; + MessageTableCellWithChevron<> m_intervalParameterCell[Shared::ContinuousFunction::k_numberOfPlotTypes]; SelectableTableView m_selectableTableView; }; diff --git a/apps/graph/values/values_controller.cpp b/apps/graph/values/values_controller.cpp index d5783efce66..56bebd1c14a 100644 --- a/apps/graph/values/values_controller.cpp +++ b/apps/graph/values/values_controller.cpp @@ -343,7 +343,7 @@ EvenOddBufferTextCell * ValuesController::floatCells(int j) { /* ValuesController::ValuesSelectableTableView */ -int writeMatrixBrakets(char * buffer, const int bufferSize, int type) { +int writeMatrixBrackets(char * buffer, const int bufferSize, int type) { /* Write the double brackets required in matrix notation. * - type == 1: "[[" * - type == 0: "][" @@ -365,14 +365,14 @@ bool ValuesController::ValuesSelectableTableView::handleEvent(Ion::Events::Event constexpr int bufferSize = 2*PrintFloat::k_maxFloatCharSize + 6; // "[[a][b]]" gives 6 characters in addition to the 2 floats char buffer[bufferSize]; int currentChar = 0; - currentChar += writeMatrixBrakets(buffer + currentChar, bufferSize - currentChar, -1); + currentChar += writeMatrixBrackets(buffer + currentChar, bufferSize - currentChar, -1); assert(currentChar < bufferSize-1); size_t semiColonPosition = UTF8Helper::CopyUntilCodePoint(buffer+currentChar, TextField::maxBufferSize() - currentChar, text+1, ';'); currentChar += semiColonPosition; - currentChar += writeMatrixBrakets(buffer + currentChar, bufferSize - currentChar, 0); + currentChar += writeMatrixBrackets(buffer + currentChar, bufferSize - currentChar, 0); assert(currentChar < bufferSize-1); currentChar += UTF8Helper::CopyUntilCodePoint(buffer+currentChar, TextField::maxBufferSize() - currentChar, text+1+semiColonPosition+1, ')'); - currentChar += writeMatrixBrakets(buffer + currentChar, bufferSize - currentChar, 1); + currentChar += writeMatrixBrackets(buffer + currentChar, bufferSize - currentChar, 1); assert(currentChar < bufferSize-1); buffer[currentChar] = 0; Clipboard::sharedClipboard()->store(buffer); diff --git a/apps/home/Makefile b/apps/home/Makefile index 880302a5262..d23bcdc30f2 100644 --- a/apps/home/Makefile +++ b/apps/home/Makefile @@ -12,7 +12,7 @@ i18n_files += $(call i18n_without_universal_for,home/base) # Apps layout file generation -# The header is refered to as so make sure it's +# The header is referred to as so make sure it's # findable this way SFLAGS += -I$(BUILD_DIR) diff --git a/apps/home/apps_layout.csv b/apps/home/apps_layout.csv index 77e47e4b882..423aa17ce23 100644 --- a/apps/home/apps_layout.csv +++ b/apps/home/apps_layout.csv @@ -1,2 +1,2 @@ -Default,calculation,rpn,graph,code,statistics,probability,solver,atomic,sequence,regression,settings -HidePython,calculation,rpn,graph,code,statistics,probability,solver,atomic,sequence,regression,settings +Default,calculation,graph,rpn,code,statistics,probability,solver,atomic,sequence,regression,reader,settings +HidePython,calculation,graph,rpn,code,statistics,probability,solver,atomic,sequence,regression,reader,settings diff --git a/apps/home/base.de.i18n b/apps/home/base.de.i18n index 30ac1c9c256..e360771484b 100644 --- a/apps/home/base.de.i18n +++ b/apps/home/base.de.i18n @@ -1,4 +1,4 @@ Apps = "Anwendungen" -AppsCapital = "OMEGA" -ForbidenAppInExamMode1 = "Diese Anwendung ist im" -ForbidenAppInExamMode2 = "Prüfungsmodus nicht erlaubt." +AppsCapital = "UPSILON" +ForbiddenAppInExamMode1 = "Diese Anwendung ist im" +ForbiddenAppInExamMode2 = "Prüfungsmodus nicht erlaubt." diff --git a/apps/home/base.en.i18n b/apps/home/base.en.i18n index dde4e21299b..2a65458e814 100644 --- a/apps/home/base.en.i18n +++ b/apps/home/base.en.i18n @@ -1,4 +1,4 @@ Apps = "Applications" -AppsCapital = "OMEGA" -ForbidenAppInExamMode1 = "This application is" -ForbidenAppInExamMode2 = "forbidden in exam mode" +AppsCapital = "UPSILON" +ForbiddenAppInExamMode1 = "This application is" +ForbiddenAppInExamMode2 = "forbidden in exam mode" diff --git a/apps/home/base.es.i18n b/apps/home/base.es.i18n index 93e7bdf4914..8d71a322920 100644 --- a/apps/home/base.es.i18n +++ b/apps/home/base.es.i18n @@ -1,4 +1,4 @@ Apps = "Aplicaciones" -AppsCapital = "OMEGA" -ForbidenAppInExamMode1 = "Esta aplicación está prohibida" -ForbidenAppInExamMode2 = "en el modo de examen" +AppsCapital = "UPSILON" +ForbiddenAppInExamMode1 = "Esta aplicación está prohibida" +ForbiddenAppInExamMode2 = "en el modo de examen" diff --git a/apps/home/base.fr.i18n b/apps/home/base.fr.i18n index 8280b458409..8c7270e051c 100644 --- a/apps/home/base.fr.i18n +++ b/apps/home/base.fr.i18n @@ -1,4 +1,4 @@ Apps = "Applications" -AppsCapital = "OMEGA" -ForbidenAppInExamMode1 = "Cette application n'est" -ForbidenAppInExamMode2 = "pas autorisée en mode examen." +AppsCapital = "UPSILON" +ForbiddenAppInExamMode1 = "Cette application n'est" +ForbiddenAppInExamMode2 = "pas autorisée en mode examen." diff --git a/apps/home/base.hu.i18n b/apps/home/base.hu.i18n index 6b0b188157c..aa3c0f1e3b7 100644 --- a/apps/home/base.hu.i18n +++ b/apps/home/base.hu.i18n @@ -1,4 +1,4 @@ Apps = "Alkalmazások" -AppsCapital = "OMEGA" -ForbidenAppInExamMode1 = "Ez az alkalmazás" -ForbidenAppInExamMode2 = "tilos vizsga módban" +AppsCapital = "UPSILON" +ForbiddenAppInExamMode1 = "Ez az alkalmazás" +ForbiddenAppInExamMode2 = "tilos vizsga módban" diff --git a/apps/home/base.it.i18n b/apps/home/base.it.i18n index 95373c0401a..a4bf010540b 100644 --- a/apps/home/base.it.i18n +++ b/apps/home/base.it.i18n @@ -1,4 +1,4 @@ Apps = "Applicazioni" -AppsCapital = "OMEGA" -ForbidenAppInExamMode1 = "Questa applicazione è" -ForbidenAppInExamMode2 = "proibita nella modalità d'esame" +AppsCapital = "UPSILON" +ForbiddenAppInExamMode1 = "Questa applicazione è" +ForbiddenAppInExamMode2 = "proibita nella modalità d'esame" diff --git a/apps/home/base.nl.i18n b/apps/home/base.nl.i18n index dd6a8991935..4e9b4470eff 100644 --- a/apps/home/base.nl.i18n +++ b/apps/home/base.nl.i18n @@ -1,4 +1,4 @@ Apps = "Applicaties" -AppsCapital = "OMEGA" -ForbidenAppInExamMode1 = "Deze applicatie is" -ForbidenAppInExamMode2 = "uitgesloten in examenstand" +AppsCapital = "UPSILON" +ForbiddenAppInExamMode1 = "Deze applicatie is" +ForbiddenAppInExamMode2 = "uitgesloten in examenstand" diff --git a/apps/home/base.pt.i18n b/apps/home/base.pt.i18n index f3efc8ffdee..7f5c4d004e7 100644 --- a/apps/home/base.pt.i18n +++ b/apps/home/base.pt.i18n @@ -1,4 +1,4 @@ Apps = "Aplicações" -AppsCapital = "OMEGA" -ForbidenAppInExamMode1 = "Esta aplicação é" -ForbidenAppInExamMode2 = "proibida no Modo de Exame" +AppsCapital = "UPSILON" +ForbiddenAppInExamMode1 = "Esta aplicação é" +ForbiddenAppInExamMode2 = "proibida no Modo de Exame" diff --git a/apps/home/controller.cpp b/apps/home/controller.cpp index df725f10fdc..1936b47eff7 100644 --- a/apps/home/controller.cpp +++ b/apps/home/controller.cpp @@ -75,7 +75,7 @@ Controller::Controller(Responder * parentResponder, SelectableTableViewDataSourc m_view.backgroundView()->setDefaultColor(Palette::HomeBackground); - + #ifdef HOME_DISPLAY_EXTERNALS int index = External::Archive::indexFromName("wallpaper.obm"); if(index > -1) { @@ -86,6 +86,13 @@ Controller::Controller(Responder * parentResponder, SelectableTableViewDataSourc #endif } +static constexpr Ion::Events::Event home_fast_navigation_events[] = { + Ion::Events::Seven, Ion::Events::Eight, Ion::Events::Nine, + Ion::Events::Four, Ion::Events::Five, Ion::Events::Six, + Ion::Events::One, Ion::Events::Two, Ion::Events::Three, + Ion::Events::Zero, Ion::Events::Dot, Ion::Events::EE +}; + bool Controller::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK || event == Ion::Events::EXE) { AppsContainer * container = AppsContainer::sharedAppsContainer(); @@ -94,7 +101,7 @@ bool Controller::handleEvent(Ion::Events::Event event) { #ifdef HOME_DISPLAY_EXTERNALS if (index >= container->numberOfApps()) { if (GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::Dutch || GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::NoSymNoText || GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::NoSym) { - App::app()->displayWarning(I18n::Message::ForbidenAppInExamMode1, I18n::Message::ForbidenAppInExamMode2); + App::app()->displayWarning(I18n::Message::ForbiddenAppInExamMode1, I18n::Message::ForbiddenAppInExamMode2); } else { External::Archive::File executable; if (External::Archive::executableAtIndex(index - container->numberOfApps(), executable)) { @@ -120,7 +127,7 @@ bool Controller::handleEvent(Ion::Events::Event event) { #endif ::App::Snapshot * selectedSnapshot = container->appSnapshotAtIndex(index); if (ExamModeConfiguration::appIsForbiddenInExamMode(selectedSnapshot->descriptor()->examinationLevel(), GlobalPreferences::sharedGlobalPreferences()->examMode())) { - App::app()->displayWarning(I18n::Message::ForbidenAppInExamMode1, I18n::Message::ForbidenAppInExamMode2); + App::app()->displayWarning(I18n::Message::ForbiddenAppInExamMode1, I18n::Message::ForbiddenAppInExamMode2); } else { bool switched = container->switchTo(selectedSnapshot); assert(switched); @@ -143,6 +150,21 @@ bool Controller::handleEvent(Ion::Events::Event event) { return m_view.selectableTableView()->selectCellAtLocation(numberOfColumns() - 1, selectionDataSource()->selectedRow() - 1); } + // Handle fast home navigation + for(int i = 0; i < std::min((int) (sizeof(home_fast_navigation_events) / sizeof(Ion::Events::Event)), this->numberOfIcons()); i++) { + if (event == home_fast_navigation_events[i]) { + int row = i / k_numberOfColumns; + int column = i % k_numberOfColumns; + // Get if app is already selected + if (selectionDataSource()->selectedRow() == row && selectionDataSource()->selectedColumn() == column) { + // If app is already selected, launch it + return handleEvent(Ion::Events::OK); + } + // Else, select the app + return m_view.selectableTableView()->selectCellAtLocation(column, row); + } + } + return false; } @@ -254,7 +276,7 @@ void Controller::tableViewDidChangeSelection(SelectableTableView * t, int previo * (so the previous one is always visible). */ int appIndex = (t->selectedColumn()+t->selectedRow()*k_numberOfColumns)+1; if (appIndex >= this->numberOfIcons()+1) { - t->selectCellAtLocation((this->numberOfIcons()%3)-1, (this->numberOfIcons() / k_numberOfColumns)); + t->selectCellAtLocation((this->numberOfIcons()%k_numberOfColumns)-1, (this->numberOfIcons() / k_numberOfColumns)); } } diff --git a/apps/home/controller.h b/apps/home/controller.h index 3301fac78fb..a330a75641c 100644 --- a/apps/home/controller.h +++ b/apps/home/controller.h @@ -47,10 +47,19 @@ class Controller : public ViewController, public SimpleTableViewDataSource, publ static constexpr KDCoordinate k_sideMargin = 4; static constexpr KDCoordinate k_bottomMargin = 14; static constexpr KDCoordinate k_indicatorMargin = 61; + + #ifndef _FXCG static constexpr int k_numberOfColumns = 3; - static constexpr int k_maxNumberOfCells = 16; static constexpr int k_cellHeight = 104; static constexpr int k_cellWidth = 104; + #else + // A different screen resolution so different dimensions + static constexpr int k_numberOfColumns = 4; + static constexpr int k_cellHeight = 96; + static constexpr int k_cellWidth = 97; + #endif + + static constexpr int k_maxNumberOfCells = 16; ContentView m_view; AppCell m_cells[k_maxNumberOfCells]; App * m_app; diff --git a/apps/host_filemanager.cpp b/apps/host_filemanager.cpp new file mode 100644 index 00000000000..86d5b4ca8d5 --- /dev/null +++ b/apps/host_filemanager.cpp @@ -0,0 +1,859 @@ +// #include +// // #include "../apps_container.h" +// #include "stddef.h" +// #include "string.h" +// #include "math.h" +// #include "app.h" +#include +#include "apps_container.h" +#include "global_preferences.h" +// #include "../exam_mode_configuration.h" + +extern "C" { +#include +} + +#ifdef NSPIRE_NEWLIB +const char * storage_name="/documents/nwstore.nws.tns"; +#else +const char * storage_name="nwstore.nws"; +#endif + +const char * calc_storage_name="nwcalc.txt"; +void * storage_address(); // ion/src/simulator/shared/platform_info.cpp +const char * retrieve_calc_history(); +#if defined _FXCG || defined NSPIRE_NEWLIB +bool save_calc_history(); +void display_host_help(); +int load_state(const char * fname); +#endif + +// Additional code by B. Parisse for host file system support and persistence +// on Casio Graph 90/FXCG50 and TI Nspire +void erase_record(const char * name){ + unsigned char * ptr=(unsigned char *)storage_address(); + for (ptr+=4;;){ // skip header + size_t L=ptr[1]*256+ptr[0]; + if (L==0) return; + if (strcmp((const char *)ptr+2,name)==0){ + unsigned char * newptr=ptr; + int S=0,erased=L; + ptr+=L; + for (;;){ + L=ptr[1]*256+ptr[0]; + if (L==0){ + for (int i=0;inewptr+S) + memmove(newptr+S,ptr,L); + S+=L; + ptr+=L; + } + return; + } + ptr+=L; + } +} + + +// record filtering on read +void filter(unsigned char * ptr){ + unsigned char * newptr=ptr; + int S; ptr+=4; + for (S=4;;){ + size_t L=ptr[1]*256+ptr[0]; + if (L==0) break; + int l=strlen((const char *)ptr+2); + // filter py records + if (l>3 && strncmp((const char *)ptr+2+l-3,".py",3)==0){ + // if (ptr>newptr+S) + memmove(newptr+S,ptr,L); + S+=L; + } +#if 0 // def STRING_STORAGE + if (l>5 && strncmp((const char *)ptr+2+l-5,".func",5)==0){ + int shift=l+4+13; + Ion::Storage::Record * record=(Ion::Storage::Record *)malloc(1024); + memcpy(record,ptr,L); + //ExpressionModelHandle + Poincare::Expression e=Poincare::Expression::Parse((const char *)ptr+shift,NULL); + //ExpressionModel::setContent(Ion::Storage::Record * record, const char * c, Context * context, CodePoint symbol); + Shared::ExpressionModel md; + Ion::Storage::Record::ErrorStatus err=md.setExpressionContent(record, e); + if (1){ + // if (ptr>newptr+S) + int newL=record->value().size; + memmove(newptr+S,record,newL); + S+=newL; + } + free(record); + } +#endif + ptr+=L; + } +} + + +#ifdef NSPIRE_NEWLIB +#include "../../ion/src/simulator/fxcg/platform.h" +#include "../../ion/src/simulator/fxcg/menuHandler.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "../../ion/src/simulator/nspire/k_csdk.h" +#include "../calculation/calculation_store.h" + +#define C_WHITE SDK_WHITE +#define C_BLACK SDK_BLACK +#define C_RED (31<<11) + +int do_getkey(){ + os_wait_1ms(50); + return getkey(0); +} + +void dtext(int x,int y,int fg,const char * s){ + os_draw_string_medium(x,y,fg,SDK_WHITE,s); +} + +void dclear(int c){ + os_fill_rect(0,0,LCD_WIDTH_PX,LCD_HEIGHT_PX,c); +} + +void dupdate(){ + sync_screen(); +} + +const int storage_length=60000; // 60000 in Upsilon, 32768 in Epsilon +// k_storageSize = 60000; in ion/include/ion/internal_storage.h +extern void * last_calculation_history; + +int load_state(const char * fname){ + FILE * f=fopen(fname,"rb"); + if (f){ + unsigned char * ptr=(unsigned char *)storage_address(); + fread(ptr,1,storage_length,f); + fclose(f); +#ifdef FILTER_STORE + filter(ptr); +#endif + return 1; + } + return 0; +} + +int save_state(const char * fname){ + save_calc_history(); + if (1 || Ion::Storage::sharedStorage()->numberOfRecords()){ + const unsigned char * ptr=(const unsigned char *)storage_address(); + // find store size + int S=4; + for (ptr+=4;;){ // skip header + size_t L=ptr[1]*256+ptr[0]; + ptr+=L; + S+=L; + if (L==0) break; + } + S = ((S+1023)/1024)*1024; +#ifdef FILTER_STORE + // keep only python scripts + unsigned char * newptr=(unsigned char *) malloc(S); + bzero(newptr,S); + ptr=(const unsigned char *) storage_address(); + memcpy(newptr,ptr,4); ptr+=4; + for (S=4;;){ + size_t L=ptr[1]*256+ptr[0]; + if (L==0) break; + int l=strlen((const char *)ptr+2); + if (l>3 && strncmp((const char *)ptr+2+l-3,".py",3)==0){ + memcpy(newptr+S,ptr,L); + S+=L; + } + ptr+=L; + } + S = ((S+1023)/1024)*1024; + FILE * f; + f=fopen(fname,"wb"); + if (f){ + fwrite(newptr,S,1,f); + //fwrite(ptr+4,1,S-4,f); + fclose(f); + free(newptr); + return S; + } + free(newptr); + return 0; +#else + ptr=(const unsigned char *)storage_address(); + FILE * f; + f=fopen(fname,"wb"); + if (f){ + fwrite(ptr,S,1,f); + //fwrite(ptr+4,1,S-4,f); + fclose(f); + return S; + } + return 0; +#endif + } + return 2; +} + +#endif // NSPIRE_NEWLIB + +#ifdef _FXCG +#include "../../ion/src/simulator/fxcg/platform.h" +#include "../../ion/src/simulator/fxcg/menuHandler.h" +#include "../calculation/calculation_store.h" +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#define KEY_CTRL_OK KEY_EXE +int do_getkey(){ + return getkey().key; +} + +const int storage_length=60000; // 60000 in Upsilon, 32768 in Epsilon +// k_storageSize = 60000; in ion/include/ion/internal_storage.h +extern void * last_calculation_history; + +int load_state(const char * fname){ + FILE * f=fopen(fname,"rb"); + if (f){ + unsigned char * ptr=(unsigned char *)storage_address(); + ptr[3]=fgetc(f); + ptr[2]=fgetc(f); + ptr[1]=fgetc(f); + ptr[0]=fgetc(f); + fread(ptr+4,1,storage_length-4,f); + fclose(f); + return 1; + } + return 0; +} + +int save_state(const char * fname){ + save_calc_history(); + if (Ion::Storage::sharedStorage()->numberOfRecords()){ +#if 0 + unsigned short pFile[512]; + convert(fname,pFile); + int hf = BFile_Open(pFile, BFile_WriteOnly); // Get handle + // cout << hf << endl << "f:" << filename << endl; Console_Disp(); + if (hf<0){ + int l=storage_length; + BFile_Create(pFile,0,&l); + hf = BFile_Open(pFile, BFile_WriteOnly); + } + if (hf < 0) + return 0; + int l=BFile_Write(hf,storage_address(),storage_length); + BFile_Close(hf); + if (l==storage_length) + return 1; + return -1; +#else + const unsigned char * ptr=(const unsigned char *)storage_address(); + // find store size + int S=4; + for (ptr+=4;;){ // skip header + size_t L=ptr[1]*256+ptr[0]; + ptr+=L; + S+=L; + if (L==0) break; + } + S = ((S+1023)/1024)*1024; + FILE * f=fopen(fname,"wb"); + if (f){ + ptr=(const unsigned char *) storage_address(); + fputc(ptr[3],f); + fputc(ptr[2],f); + fputc(ptr[1],f); + fputc(ptr[0],f); + //fwrite(ptr+4,1,S-4,f); + fwrite(ptr+4,S-4,1,f); + fclose(f); + return S; + } + return 0; +#endif + } + return 2; +} +#endif // _FXCG + +const char * retrieve_calc_history(){ +#if defined _FXCG || defined NSPIRE_NEWLIB + static bool firstrun=true; + if (firstrun){ +#ifdef _FXCG + int l=gint_world_switch(GINT_CALL(load_state,storage_name)); +#else + int l=load_state(storage_name); +#endif + if (l==0){ + display_host_help(); + // ((App*)m_app)->redraw(); + } + firstrun=false; + } +#endif + unsigned char * ptr=(unsigned char *)storage_address(); + for (ptr+=4;;){ // skip header + size_t L=ptr[1]*256+ptr[0]; + if (L==0) return 0; + if (strcmp((const char *)ptr+2,calc_storage_name)==0){ + const char * buf=(const char *)ptr+2+strlen(calc_storage_name)+1; + return buf; + } + ptr += L; + } + return 0; +} + +#if defined _FXCG || defined NSPIRE_NEWLIB +bool save_calc_history(){ + if (!last_calculation_history) + return false; + erase_record(calc_storage_name); + std::string s; + Calculation::CalculationStore * store=(Calculation::CalculationStore *) last_calculation_history; + int N=store->numberOfCalculations(); + for (int i=N-1;i>=0;--i){ + s += store->calculationAtIndex(i)->inputText(); + s += '\n'; + } + if (s.empty()) + return false; + Ion::Storage::Record::ErrorStatus res= Ion::Storage::sharedStorage()->createRecordWithFullName(calc_storage_name,&s[0],s.size()); + if (res==Ion::Storage::Record::ErrorStatus::NotEnoughSpaceAvailable) + return false; + return true; +} + +void confirm_load_state(const char * buf){ + dclear(C_WHITE); + dtext(1,1, C_BLACK, "Loading from state file"); + dtext(1,17,C_BLACK,buf); + dtext(1,33,C_BLACK,"Current context will be lost!"); + dtext(1,49,C_BLACK,"Press EXE to confirm"); + dupdate(); + int k=do_getkey(); + if (k==KEY_EXE || k==KEY_CTRL_OK){ +#ifdef _FXCG + int l=gint_world_switch(GINT_CALL(load_state,buf)); +#else + int l=load_state(buf); +#endif + char buf2[]="0"; + buf2[0] += l; + if (l==0) + dtext(1,65,C_BLACK,"Error reading state"); + if (l==1) + dtext(1,65,C_BLACK,"Success reading state"); + dtext(1,81,C_BLACK,buf2); + dtext(1,97,C_BLACK,"Press any key"); + dupdate(); + do_getkey(); + } +} + +static void convert(const char * fname,unsigned short * pFile){ + for ( ;*fname;++fname,++pFile) + *pFile=*fname; + *pFile=0; +} + +struct file { + std::string s; + int length; + bool isdir; +}; + +void host_scripts(std::vector & v,const char * dirname,const char * extension){ + v.clear(); + file f={".._parent_dir",0,true}; + if (strlen(dirname)>1) + v.push_back(f); + DIR *dp; + struct dirent *ep; + dp = opendir (dirname); + int l=extension?strlen(extension):0; + if (dp != NULL){ + int t; + while ( (ep = readdir (dp)) ){ + if (strlen(ep->d_name)>=1 && ep->d_name[0]=='.') + continue; + f.s=ep->d_name; + if (f.s=="@MainMem") + continue; +#ifdef NSPIRE_NEWLIB + DIR * chk=opendir((dirname+f.s).c_str()); + f.isdir=true; + if (chk) + closedir(chk); + else + f.isdir=false; +#else + f.isdir=ep->d_type==DT_DIR; +#endif +#if 1 + if (f.isdir) + f.length=0; + else { + struct stat st; + stat((dirname+f.s).c_str(), &st); + f.length = st.st_size; + if (f.length>=32768) + continue; + } +#else + f.length=f.isdir?0:-1; +#endif + if (f.isdir || !extension) + v.push_back(f); + else { + t=strlen(ep->d_name); + if (t>l && strncmp(ep->d_name+t-l,extension,l)==0) + v.push_back(f); + } + } + closedir (dp); + } +} + +void nw_scripts(std::vector & v,const char * extension){ + v.clear(); +#if 0 + int n=Ion::Storage::sharedStorage()->numberOfRecords(); + for (int i=0;irecordAtIndex(i).fullName()); + } +#else + const unsigned char * ptr=(const unsigned char *)storage_address(); + int l=extension?strlen(extension):0; + for (ptr+=4;;){ // skip header + size_t L=ptr[1]*256+ptr[0]; + ptr+=2; + if (L==0) break; + L-=2; + file f={(const char *)ptr,(int)L,false}; + if (!extension) + v.push_back(f); + else { + int namesize=strlen((const char *)ptr); + if (namesize>l && strncmp((const char *)ptr+namesize-l,extension,l)==0) + v.push_back(f); + } + ptr+=L; + } +#endif +} + +int copy_nw_to_host(const char * nwname,const char * hostname){ +#ifdef NSPIRE_NEWLIB + int s=strlen(hostname); + if (s<4 || strncmp(hostname+s-4,".tns",4)){ + std::string S(hostname); + S+=".tns"; + return copy_nw_to_host(nwname,S.c_str()); + } +#endif + const unsigned char * ptr=(const unsigned char *)storage_address(); + for (ptr+=4;;){ // skip header + size_t L=ptr[1]*256+ptr[0]; + if (L==0) return 3; // not found + //dclear(C_WHITE); + //dtext(1,1,C_BLACK,ptr+2); + //dtext(1,17,C_BLACK,nwname); + //dupdate(); + //getkey(); + if (strcmp((const char *)ptr+2,nwname)){ + ptr += L; + continue; + } + ptr+=2; + L-=2; + int l=strlen((const char *)ptr); + ptr += l+2; + L -= l; + L = 2*((L+1)/2); + FILE * f=fopen(hostname,"wb"); + if (!f) + return 2; + fwrite(ptr,1,L,f); + fclose(f); + return 0; + } + return 1; +} + +int copy_host_to_nw(const char * hostname,const char * nwname,int autoexec){ + FILE * f=fopen(hostname,"rb"); + if (!f) + return -1; + std::vector v(1,autoexec?1:0); + for (;;){ + unsigned char c=fgetc(f); + if (feof(f)){ + if (c>=' ' && c<=0x7e) + v.push_back(c); + break; + } + if (c==0xa && !v.empty() && v.back()==0xd) + v.back()=0xa; + else + v.push_back(c); + } + if (!v.empty() && v.back()!=0xa) + v.push_back(0xa); + v.push_back(0); + fclose(f); + if (Ion::Storage::sharedStorage()->hasRecord(Ion::Storage::sharedStorage()->recordNamed(nwname))) + Ion::Storage::sharedStorage()-> destroyRecord(Ion::Storage::sharedStorage()->recordNamed(nwname)); + Ion::Storage::Record::ErrorStatus res= Ion::Storage::sharedStorage()->createRecordWithFullName(nwname,&v.front(),v.size()); + if (res==Ion::Storage::Record::ErrorStatus::NotEnoughSpaceAvailable) + return -2; + return 0; +} + +bool filesort(const file & a,const file & b){ + if (a.isdir!=b.isdir) + return a.isdir; + return a.s v,w; +#ifdef NSPIRE_NEWLIB + std::string hostdir="/documents/"; +#else + std::string hostdir="/"; +#endif + bool onlypy=true; + for (;;){ + if (reload){ + nw_scripts(v,onlypy?".py":0); + sort(v.begin(),v.end(),filesort); +#ifdef NSPIRE_NEWLIB + host_scripts(w,hostdir.c_str(),onlypy?".py.tns":0); +#else + host_scripts(w,hostdir.c_str(),onlypy?".py":0); +#endif + sort(w.begin(),w.end(),filesort); + reload=false; + } + dclear(C_WHITE); + dtext(1,1, C_BLACK,"EXIT: leave; key 1 to 9: load state from file"); +#ifdef _FXCG + dtext(1,17,C_BLACK,"Cursor keys: move, /: rootdir, OPTN: all/py files"); +#else + dtext(1,17,C_BLACK,"Cursor keys: move, /: rootdir"); +#endif + dtext(1,33,C_BLACK,"EXE or STO key: copy selection from/to host"); + dtext(1,49,C_BLACK,("Upsilon records Host "+hostdir).c_str()); + int nitems=9; + if (posnw<0) + posnw=v.size()-1; + if (posnw>=int(v.size())) + posnw=0; + if (posnwstartnw+nitems) + startnw=posnw-4; + if (startnw>=int(v.size())-nitems) + startnw=v.size()-nitems; + if (startnw<0) + startnw=0; + if (v.empty()) + nw=false; + for (int i=0;i<=nitems;++i){ + int I=i+startnw; + if (I>=int(v.size())) + break; + dtext(1,65+16*i,(nw && I==posnw)?C_RED:C_BLACK,v[I].s.c_str()); + char buf[256]; + sprintf(buf,"%i",v[I].length); + dtext(90,65+16*i,(nw && I==posnw)?C_RED:C_BLACK,buf); + } + if (w.empty()) + nw=true; + if (poshost<0) + poshost=w.size()-1; + if (poshost>=int(w.size())) + poshost=0; + if (poshoststarthost+nitems) + starthost=poshost-4; + if (starthost>=int(w.size())-nitems) + starthost=w.size()-nitems; + if (starthost<0) + starthost=0; + for (int i=0;i<=nitems;++i){ + int I=i+starthost; + if (I>=int(w.size())) + break; + std::string fname=w[I].s; + if (fname.size()>16) + fname=fname.substr(0,16)+"..."; + dtext(192,65+16*i,(!nw && I==poshost)?C_RED:C_BLACK,fname.c_str()); + if (w[I].isdir) + dtext(154,65+16*i,(!nw && I==poshost)?C_RED:C_BLACK,""); + else { + char buf[256]; + sprintf(buf,"%i",w[I].length); +#ifdef _FXCG + dtext(340,65+16*i,(!nw && I==poshost)?C_RED:C_BLACK,buf); +#else + dtext(285,65+16*i,(!nw && I==poshost)?C_RED:C_BLACK,buf); +#endif + } + } + dupdate(); + int key=do_getkey(); + if (key==KEY_EXIT || key==KEY_MENU) + break; + if (key==KEY_OPTN || key=='\t'){ + onlypy=!onlypy; + reload=true; + continue; + } + if (key==KEY_DIV){ +#ifdef NSPIRE_NEWLIB + hostdir="/documents/"; +#else + hostdir="/"; +#endif + reload=true; + continue; + } + if (key==KEY_DEL){ + if (!nw && w[poshost].isdir) // can not remove directory + continue; + dclear(C_WHITE); + dtext(1,17,C_BLACK,nw?"About to suppress Upsilon record:":"About to suppress Host file:"); + dtext(1,33,C_BLACK,(nw?v[posnw].s:w[poshost].s).c_str()); + dtext(1,49,C_BLACK,"Press EXE or OK to confirm"); + dupdate(); + int ev=do_getkey(); + if (ev!=KEY_EXE && ev!=KEY_CTRL_OK) + continue; + if (nw){ +#if 1 + erase_record(v[posnw].s.c_str()); +#else + char buf[256]; + strcpy(buf,v[posnw].s.c_str()); + int l=strlen(buf)-4; + buf[l]=0; + Ion::Storage::sharedStorage()-> destroyRecordWithBaseNameAndExtension(buf,buf+l+1); +#endif + } + else + remove((hostdir+w[poshost].s).c_str()); + reload=true; + } + if (key==KEY_LEFT){ + nw=true; + continue; + } + if (key==KEY_RIGHT){ + nw=false; + continue; + } + if (key==KEY_PLUS){ + if (nw) + posnw+=5; + else + poshost+=5; + continue; + } + if (key==KEY_MINUS){ + if (nw) + posnw-=5; + else + poshost-=5; + continue; + } + if (key==KEY_DOWN){ + if (nw) + ++posnw; + else + ++poshost; + continue; + } + if (key==KEY_UP){ + if (nw) + --posnw; + else + --poshost; + continue; + } + int autoexec = key==KEY_EXE || key==KEY_CTRL_OK; + if (key==KEY_STORE || autoexec){ + if (nw && posnw>=0 && posnw=0 && poshost=0;--j){ + if (hostdir[j]=='/'){ + hostdir=hostdir.substr(0,j+1); + break; + } + } + reload=true; + continue; + } + // lookup if poshost is in directories + if (w[poshost].isdir){ + hostdir += w[poshost].s; + hostdir += "/"; + reload=true; + continue; + } + size_t i; + std::string fname=w[poshost].s; +#ifdef NSPIRE_NEWLIB + if (fname.size()>4 && fname.substr(fname.size()-4,4)==".tns") + fname=fname.substr(0,fname.size()-4); +#endif + for (i=0;i12){ + dclear(C_WHITE); + dtext(1,33,C_BLACK,"Host filename too long"); + dtext(1,49,C_BLACK,fname.c_str()); + dupdate(); + do_getkey(); + continue; + } + if (fname.size()>4 && fname.substr(fname.size()-4,4)==".nws") + confirm_load_state((hostdir+fname).c_str()); + else { +#ifdef _FXCG + gint_world_switch(GINT_CALL(copy_host_to_nw,(hostdir+fname).c_str(),nwname.c_str(),autoexec)); +#else + copy_host_to_nw((hostdir+w[poshost].s).c_str(),nwname.c_str(),autoexec); +#endif + } + reload=true; + } + } + if (key>=KEY_1 && key<=KEY_9){ +#ifdef NSPIRE_NEWLIB + char buf[]="nwstate0.nws.tns"; +#else + char buf[]="nwstate0.nws"; +#endif + buf[7]='1'+(key-KEY_1); + confirm_load_state(buf); + reload=true; + } + } +} +#endif // FXCG || NSPIRE \ No newline at end of file diff --git a/apps/main.cpp b/apps/main.cpp index b73b73e77be..ec1db8b8073 100644 --- a/apps/main.cpp +++ b/apps/main.cpp @@ -1,6 +1,7 @@ #include "apps_container.h" #include "global_preferences.h" #include +#include #define DUMMY_MAIN 0 #if DUMMY_MAIN @@ -50,13 +51,29 @@ void ion_main(int argc, const char * const argv[]) { } continue; } + + /* Option should be given at run-time: + * $ ./epsilon.elf --open-app code + */ + const char * appNames[] = {"home", EPSILON_APPS_NAMES}; + if (strcmp(argv[i], "--open-app") == 0 && argc > i+1) { + const char * requestedAppName = argv[i+1]; + for (int j = 0; j < AppsContainer::sharedAppsContainer()->numberOfApps(); j++) { + App::Snapshot * snapshot = AppsContainer::sharedAppsContainer()->appSnapshotAtIndex(j); + if (strcmp(requestedAppName, appNames[j]) == 0) { + AppsContainer::sharedAppsContainer()->setStartApp(snapshot); + break; + } + } + continue; + } + /* Option should be given at run-time: * $ ./epsilon.elf --[app_name]-[option] [arguments] * For example: * $ make -j8 PLATFORM=emscripten EPSILON_APPS=code * $ ./epsilon.elf --code-script hello_world.py:print("hello") --code-lock-on-console */ - const char * appNames[] = {"home", EPSILON_APPS_NAMES}; for (int j = 0; j < AppsContainer::sharedAppsContainer()->numberOfApps(); j++) { App::Snapshot * snapshot = AppsContainer::sharedAppsContainer()->appSnapshotAtIndex(j); // Compare name in order to find if the firsts chars which are different are NULL and '-' diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index ad82cb08158..1d4900c9785 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -30,7 +30,7 @@ const ToolboxMessageTree calculChildren[] = { const ToolboxMessageTree complexChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::AbsCommandWithArg,I18n::Message::ComplexAbsoluteValue), - ToolboxMessageTree::Leaf(I18n::Message::ArgCommandWithArg, I18n::Message::Agument), + ToolboxMessageTree::Leaf(I18n::Message::ArgCommandWithArg, I18n::Message::Argument), ToolboxMessageTree::Leaf(I18n::Message::ReCommandWithArg, I18n::Message::RealPart), ToolboxMessageTree::Leaf(I18n::Message::ImCommandWithArg, I18n::Message::ImaginaryPart), ToolboxMessageTree::Leaf(I18n::Message::ConjCommandWithArg, I18n::Message::Conjugate) @@ -67,13 +67,10 @@ const ToolboxMessageTree arithmeticChildren[] = { }; const ToolboxMessageTree matricesChildren[] = { - ToolboxMessageTree::Leaf(I18n::Message::MatrixCommandWithArg, I18n::Message::NewMatrix, false, I18n::Message::MatrixCommand), - ToolboxMessageTree::Leaf(I18n::Message::IndentityCommandWithArg, I18n::Message::Identity), - ToolboxMessageTree::Leaf(I18n::Message::InverseCommandWithArg, I18n::Message::Inverse), ToolboxMessageTree::Leaf(I18n::Message::DeterminantCommandWithArg, I18n::Message::Determinant), - ToolboxMessageTree::Leaf(I18n::Message::TransposeCommandWithArg, I18n::Message::Transpose), + ToolboxMessageTree::Leaf(I18n::Message::InverseCommandWithArg, I18n::Message::Inverse), + ToolboxMessageTree::Leaf(I18n::Message::IndentityCommandWithArg, I18n::Message::Identity), ToolboxMessageTree::Leaf(I18n::Message::TraceCommandWithArg, I18n::Message::Trace), - ToolboxMessageTree::Leaf(I18n::Message::DimensionCommandWithArg, I18n::Message::Dimension), ToolboxMessageTree::Leaf(I18n::Message::RowEchelonFormCommandWithArg, I18n::Message::RowEchelonForm), ToolboxMessageTree::Leaf(I18n::Message::ReducedRowEchelonFormCommandWithArg, I18n::Message::ReducedRowEchelonForm) }; @@ -84,6 +81,14 @@ const ToolboxMessageTree vectorsChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::NormVectorCommandWithArg, I18n::Message::NormVector), }; +const ToolboxMessageTree matricesAndVectorsChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::MatrixCommandWithArg, I18n::Message::NewMatrix, false, I18n::Message::MatrixCommand), + ToolboxMessageTree::Leaf(I18n::Message::TransposeCommandWithArg, I18n::Message::Transpose), + ToolboxMessageTree::Leaf(I18n::Message::DimensionCommandWithArg, I18n::Message::Dimension), + ToolboxMessageTree::Node(I18n::Message::Matrices, matricesChildren), + ToolboxMessageTree::Node(I18n::Message::Vectors, vectorsChildren) +}; + #if LIST_ARE_DEFINED const ToolboxMessageTree listsChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::SortCommandWithArg, I18n::Message::Sort), @@ -252,7 +257,7 @@ const ToolboxMessageTree unitEnergyElectronVoltChildren[] = { }; const ToolboxMessageTree unitEnergyChildren[] = { ToolboxMessageTree::Node(I18n::Message::UnitEnergyJouleMenu, unitEnergyJouleChildren), - ToolboxMessageTree::Node(I18n::Message::UnitEnergyEletronVoltMenu, unitEnergyElectronVoltChildren)}; + ToolboxMessageTree::Node(I18n::Message::UnitEnergyElectronVoltMenu, unitEnergyElectronVoltChildren)}; const ToolboxMessageTree unitPowerWattChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattMicroSymbol, I18n::Message::UnitPowerWattMicro), @@ -711,6 +716,54 @@ const ToolboxMessageTree Electromagnetism[] = { }; +const ToolboxMessageTree Resistivity[] = { + ToolboxMessageTree::Leaf(I18n::Message::Silver, I18n::Message::Rstvt_Silver, false, I18n::Message::Rstvt_Silver), + ToolboxMessageTree::Leaf(I18n::Message::Copper, I18n::Message::Rstvt_Copper, false, I18n::Message::Rstvt_Copper), + ToolboxMessageTree::Leaf(I18n::Message::Gold, I18n::Message::Rstvt_Gold, false, I18n::Message::Rstvt_Gold), + ToolboxMessageTree::Leaf(I18n::Message::Aluminium, I18n::Message::Rstvt_Aluminium, false, I18n::Message::Rstvt_Aluminium), + ToolboxMessageTree::Leaf(I18n::Message::Calcium, I18n::Message::Rstvt_Calcium, false, I18n::Message::Rstvt_Calcium), + ToolboxMessageTree::Leaf(I18n::Message::Tungsten, I18n::Message::Rstvt_Tungsten, false, I18n::Message::Rstvt_Tungsten), + ToolboxMessageTree::Leaf(I18n::Message::Zinc, I18n::Message::Rstvt_Zinc, false, I18n::Message::Rstvt_Zinc), + ToolboxMessageTree::Leaf(I18n::Message::Cobalt, I18n::Message::Rstvt_Cobalt, false, I18n::Message::Rstvt_Cobalt), + ToolboxMessageTree::Leaf(I18n::Message::Nickel, I18n::Message::Rstvt_Nickel, false, I18n::Message::Rstvt_Nickel), + ToolboxMessageTree::Leaf(I18n::Message::Lithium, I18n::Message::Rstvt_Lithium, false, I18n::Message::Rstvt_Lithium), + ToolboxMessageTree::Leaf(I18n::Message::Iron, I18n::Message::Rstvt_Iron, false, I18n::Message::Rstvt_Iron), + ToolboxMessageTree::Leaf(I18n::Message::Platinum, I18n::Message::Rstvt_Platinum, false, I18n::Message::Rstvt_Platinum), + ToolboxMessageTree::Leaf(I18n::Message::Tin, I18n::Message::Rstvt_Tin, false, I18n::Message::Rstvt_Tin), + ToolboxMessageTree::Leaf(I18n::Message::Sea_water, I18n::Message::Rstvt_Sea_water, false, I18n::Message::Rstvt_Sea_water), + ToolboxMessageTree::Leaf(I18n::Message::Water, I18n::Message::Rstvt_Water, false, I18n::Message::Rstvt_Water), + ToolboxMessageTree::Leaf(I18n::Message::Air, I18n::Message::Rstvt_Air, false, I18n::Message::Rstvt_Air), + ToolboxMessageTree::Leaf(I18n::Message::Wood, I18n::Message::Rstvt_Wood, false, I18n::Message::Rstvt_Wood), + ToolboxMessageTree::Leaf(I18n::Message::Glass, I18n::Message::Rstvt_Glass, false, I18n::Message::Rstvt_Glass) +}; + +const ToolboxMessageTree Conductivity[] = { + ToolboxMessageTree::Leaf(I18n::Message::Silver, I18n::Message::Cndcvt_Silver, false, I18n::Message::Cndcvt_Silver), + ToolboxMessageTree::Leaf(I18n::Message::Copper, I18n::Message::Cndcvt_Copper, false, I18n::Message::Cndcvt_Copper), + ToolboxMessageTree::Leaf(I18n::Message::Gold, I18n::Message::Cndcvt_Gold, false, I18n::Message::Cndcvt_Gold), + ToolboxMessageTree::Leaf(I18n::Message::Aluminium, I18n::Message::Cndcvt_Aluminium, false, I18n::Message::Cndcvt_Aluminium), + ToolboxMessageTree::Leaf(I18n::Message::Calcium, I18n::Message::Cndcvt_Calcium, false, I18n::Message::Cndcvt_Calcium), + ToolboxMessageTree::Leaf(I18n::Message::Tungsten, I18n::Message::Cndcvt_Tungsten, false, I18n::Message::Cndcvt_Tungsten), + ToolboxMessageTree::Leaf(I18n::Message::Zinc, I18n::Message::Cndcvt_Zinc, false, I18n::Message::Cndcvt_Zinc), + ToolboxMessageTree::Leaf(I18n::Message::Cobalt, I18n::Message::Cndcvt_Cobalt, false, I18n::Message::Cndcvt_Cobalt), + ToolboxMessageTree::Leaf(I18n::Message::Nickel, I18n::Message::Cndcvt_Nickel, false, I18n::Message::Cndcvt_Nickel), + ToolboxMessageTree::Leaf(I18n::Message::Lithium, I18n::Message::Cndcvt_Lithium, false, I18n::Message::Cndcvt_Lithium), + ToolboxMessageTree::Leaf(I18n::Message::Iron, I18n::Message::Cndcvt_Iron, false, I18n::Message::Cndcvt_Iron), + ToolboxMessageTree::Leaf(I18n::Message::Platinum, I18n::Message::Cndcvt_Platinum, false, I18n::Message::Cndcvt_Platinum), + ToolboxMessageTree::Leaf(I18n::Message::Tin, I18n::Message::Cndcvt_Tin, false, I18n::Message::Cndcvt_Tin), + ToolboxMessageTree::Leaf(I18n::Message::Sea_water, I18n::Message::Cndcvt_Sea_water, false, I18n::Message::Cndcvt_Sea_water), + ToolboxMessageTree::Leaf(I18n::Message::Water, I18n::Message::Cndcvt_Water, false, I18n::Message::Cndcvt_Water), + ToolboxMessageTree::Leaf(I18n::Message::Air, I18n::Message::Cndcvt_Air, false, I18n::Message::Cndcvt_Air), + ToolboxMessageTree::Leaf(I18n::Message::Wood, I18n::Message::Cndcvt_Wood, false, I18n::Message::Cndcvt_Wood), + ToolboxMessageTree::Leaf(I18n::Message::Glass, I18n::Message::Cndcvt_Glass, false, I18n::Message::Cndcvt_Glass) +}; + +const ToolboxMessageTree Electricity[] = { + ToolboxMessageTree::Leaf(I18n::Message::ElementalChargeTag, I18n::Message::ElementalCharge, false, I18n::Message::ElementalCharge), + ToolboxMessageTree::Node(I18n::Message::ResistivityConstants, Resistivity), + ToolboxMessageTree::Node(I18n::Message::ConductivityConstants, Conductivity) +}; + const ToolboxMessageTree ParticleMass[] = { ToolboxMessageTree::Leaf(I18n::Message::ElectronMassTag, I18n::Message::ElectronMass, false, I18n::Message::ElectronMass), ToolboxMessageTree::Leaf(I18n::Message::MuonMassTag, I18n::Message::MuonMass, false, I18n::Message::MuonMass), @@ -788,6 +841,7 @@ const ToolboxMessageTree PlanckUnits[] = { const ToolboxMessageTree Physics[] = { ToolboxMessageTree::Node(I18n::Message::FundamentalConstants, FundamentalConstants), ToolboxMessageTree::Node(I18n::Message::Electromagnetism, Electromagnetism), + ToolboxMessageTree::Node(I18n::Message::Electricity, Electricity), ToolboxMessageTree::Node(I18n::Message::NuclearConstants, Nuclear), ToolboxMessageTree::Node(I18n::Message::Thermodynamics, Thermodynamics), ToolboxMessageTree::Node(I18n::Message::Gravitation, Gravitation), @@ -800,20 +854,23 @@ const ToolboxMessageTree Physics[] = { const ToolboxMessageTree menu[] = { + #ifdef _FXCG + // There is no factorial button on the fx-CG calculators + ToolboxMessageTree::Leaf(I18n::Message::FactorialCommandWithArg, I18n::Message::Factorial, false, I18n::Message::FactorialCommand), + #endif ToolboxMessageTree::Leaf(I18n::Message::AbsCommandWithArg, I18n::Message::AbsoluteValue), ToolboxMessageTree::Leaf(I18n::Message::RootCommandWithArg, I18n::Message::NthRoot), ToolboxMessageTree::Leaf(I18n::Message::LogCommandWithArg, I18n::Message::BasedLogarithm), ToolboxMessageTree::Node(I18n::Message::Calculation, calculChildren), ToolboxMessageTree::Node(I18n::Message::ComplexNumber, complexChildren), - ToolboxMessageTree::Node(I18n::Message::Combinatorics, combinatoricsChildren), - ToolboxMessageTree::Node(I18n::Message::Probability, probabilityChildren), + ToolboxMessageTree::Node(I18n::Message::Unit, unitChildren), ToolboxMessageTree::Node(I18n::Message::Arithmetic, arithmeticChildren), - ToolboxMessageTree::Node(I18n::Message::Matrices, matricesChildren), - ToolboxMessageTree::Node(I18n::Message::Vectors, vectorsChildren), + ToolboxMessageTree::Node(I18n::Message::MatricesAndVectors, matricesAndVectorsChildren), + ToolboxMessageTree::Node(I18n::Message::Probability, probabilityChildren), #if LIST_ARE_DEFINED ToolboxMessageTree::Node(I18n::Message::Lists,listsChildren), #endif - ToolboxMessageTree::Node(I18n::Message::Unit, unitChildren), + ToolboxMessageTree::Node(I18n::Message::Combinatorics, combinatoricsChildren), ToolboxMessageTree::Node(I18n::Message::RandomAndApproximation, randomAndApproximationChildren), ToolboxMessageTree::Node(I18n::Message::HyperbolicTrigonometry, trigonometryChildren), ToolboxMessageTree::Node(I18n::Message::Fluctuation, predictionChildren), @@ -826,9 +883,13 @@ const ToolboxMessageTree toolboxModel = ToolboxMessageTree::Node(I18n::Message:: MathToolbox::MathToolbox() : Toolbox(nullptr, rootModel()->label()) { + for (int i=0; i < k_maxNumberOfDisplayedRows; i++) { + m_leafCells[i].setMessageFont(KDFont::LargeFont); + m_nodeCells[i].setMessageFont(KDFont::LargeFont); + } } -bool MathToolbox::selectLeaf(int selectedRow) { +bool MathToolbox::selectLeaf(int selectedRow, bool quitToolbox) { ToolboxMessageTree * messageTree = (ToolboxMessageTree *)m_messageTreeModel->childAtIndex(selectedRow); m_selectableTableView.deselectTable(); @@ -851,12 +912,12 @@ const ToolboxMessageTree * MathToolbox::rootModel() const { return &toolboxModel; } -MessageTableCellWithMessage * MathToolbox::leafCellAtIndex(int index) { +MessageTableCellWithMessage * MathToolbox::leafCellAtIndex(int index) { assert(index >= 0 && index < k_maxNumberOfDisplayedRows); return &m_leafCells[index]; } -MessageTableCellWithChevron* MathToolbox::nodeCellAtIndex(int index) { +MessageTableCellWithChevron * MathToolbox::nodeCellAtIndex(int index) { assert(index >= 0 && index < k_maxNumberOfDisplayedRows); return &m_nodeCells[index]; } diff --git a/apps/math_toolbox.h b/apps/math_toolbox.h index 2ade3318f62..936cf42c94e 100644 --- a/apps/math_toolbox.h +++ b/apps/math_toolbox.h @@ -9,16 +9,16 @@ class MathToolbox : public Toolbox { MathToolbox(); const ToolboxMessageTree * rootModel() const override; protected: - bool selectLeaf(int selectedRow) override; - MessageTableCellWithMessage * leafCellAtIndex(int index) override; - MessageTableCellWithChevron* nodeCellAtIndex(int index) override; + bool selectLeaf(int selectedRow, bool quitToolbox) override; + MessageTableCellWithMessage * leafCellAtIndex(int index) override; + MessageTableCellWithChevron * nodeCellAtIndex(int index) override; int maxNumberOfDisplayedRows() override; constexpr static int k_maxNumberOfDisplayedRows = 6; // = 240/40 private: int indexAfterFork() const override; - MessageTableCellWithMessage m_leafCells[k_maxNumberOfDisplayedRows]; - MessageTableCellWithChevron m_nodeCells[k_maxNumberOfDisplayedRows]; + MessageTableCellWithMessage m_leafCells[k_maxNumberOfDisplayedRows]; + MessageTableCellWithChevron m_nodeCells[k_maxNumberOfDisplayedRows]; }; #endif diff --git a/apps/math_variable_box_controller.cpp b/apps/math_variable_box_controller.cpp index f8e01ea9f33..fb39e3b3958 100644 --- a/apps/math_variable_box_controller.cpp +++ b/apps/math_variable_box_controller.cpp @@ -98,7 +98,7 @@ int MathVariableBoxController::reusableCellCount(int type) { void MathVariableBoxController::willDisplayCellForIndex(HighlightCell * cell, int index) { if (m_currentPage == Page::RootMenu) { I18n::Message label = nodeLabelAtIndex(index); - MessageTableCell * myCell = (MessageTableCell *)cell; + MessageTableCell<> * myCell = (MessageTableCell<> *)cell; myCell->setMessage(label); myCell->reloadCell(); return; @@ -124,7 +124,7 @@ void MathVariableBoxController::willDisplayCellForIndex(HighlightCell * cell, in symbolName, Shared::Sequence::k_maxNameWithArgumentSize ); - Expression symbolExpression = Expression::ParseAndSimplify(symbolName, AppsContainer::sharedAppsContainer()->globalContext(), Poincare::Preferences::sharedPreferences()->complexFormat(), Poincare::Preferences::sharedPreferences()->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat()); + Expression symbolExpression = Expression::Parse(symbolName, AppsContainer::sharedAppsContainer()->globalContext()); symbolLayout = symbolExpression.createLayout(Poincare::Preferences::sharedPreferences()->displayMode(), Poincare::Preferences::sharedPreferences()->numberOfSignificantDigits()); } if (symbolLayout.isUninitialized()) { @@ -158,7 +158,7 @@ ExpressionTableCellWithExpression * MathVariableBoxController::leafCellAtIndex(i return &m_leafCells[index]; } -MessageTableCellWithChevron * MathVariableBoxController::nodeCellAtIndex(int index) { +MessageTableCellWithChevron<> * MathVariableBoxController::nodeCellAtIndex(int index) { assert(index >= 0 && index < k_numberOfMenuRows); return &m_nodeCells[index]; } @@ -193,7 +193,7 @@ bool MathVariableBoxController::returnToPreviousMenu() { return AlternateEmptyNestedMenuController::returnToPreviousMenu(); } -bool MathVariableBoxController::selectLeaf(int selectedRow) { +bool MathVariableBoxController::selectLeaf(int selectedRow, bool quitToolbox) { if (isDisplayingEmptyController()) { /* We do not want to handle OK/EXE events in that case. */ return false; diff --git a/apps/math_variable_box_controller.h b/apps/math_variable_box_controller.h index 86f968303a5..cf2f2fb0bb2 100644 --- a/apps/math_variable_box_controller.h +++ b/apps/math_variable_box_controller.h @@ -37,12 +37,12 @@ class MathVariableBoxController : public AlternateEmptyNestedMenuController { constexpr static int k_numberOfMenuRows = 3; constexpr static KDCoordinate k_leafMargin = 20; ExpressionTableCellWithExpression * leafCellAtIndex(int index) override; - MessageTableCellWithChevron * nodeCellAtIndex(int index) override; + MessageTableCellWithChevron<> * nodeCellAtIndex(int index) override; Page pageAtIndex(int index); void setPage(Page page); bool selectSubMenu(int selectedRow) override; bool returnToPreviousMenu() override; - bool selectLeaf(int selectedRow) override; + bool selectLeaf(int selectedRow, bool quitToolbox) override; I18n::Message nodeLabelAtIndex(int index); Poincare::Layout expressionLayoutForRecord(Ion::Storage::Record record, int index); const char * extension() const; @@ -53,7 +53,7 @@ class MathVariableBoxController : public AlternateEmptyNestedMenuController { Page m_currentPage; Page m_lockPageDelete; ExpressionTableCellWithExpression m_leafCells[k_maxNumberOfDisplayedRows]; - MessageTableCellWithChevron m_nodeCells[k_numberOfMenuRows]; + MessageTableCellWithChevron<> m_nodeCells[k_numberOfMenuRows]; MathVariableBoxEmptyController m_emptyViewController; // Layout memoization // TODO: make a helper doing the RingMemoizationOfConsecutiveObjets to factorize this code and ExpressionModelStore code diff --git a/apps/math_variable_box_empty_controller.cpp b/apps/math_variable_box_empty_controller.cpp index 4c832c19557..88a306fbc77 100644 --- a/apps/math_variable_box_empty_controller.cpp +++ b/apps/math_variable_box_empty_controller.cpp @@ -5,7 +5,7 @@ MathVariableBoxEmptyController::MathVariableBoxEmptyView::MathVariableBoxEmptyView() : ModalViewEmptyView(), - m_layoutExample(0.5f, 0.5f, KDColorBlack, Palette::WallScreen) + m_layoutExample(0.5f, 0.5f, Palette::PrimaryText, Palette::WallScreen) { initMessageViews(); } diff --git a/apps/on_boarding/app.cpp b/apps/on_boarding/app.cpp index 3e914f7d9d4..1b789338b92 100644 --- a/apps/on_boarding/app.cpp +++ b/apps/on_boarding/app.cpp @@ -18,22 +18,14 @@ App::App(Snapshot * snapshot) : m_localizationController(&m_modalViewController, Metric::CommonTopMargin, LocalizationController::Mode::Language), m_logoController() { -} - -int App::numberOfTimers() { - return firstResponder() == &m_logoController; -} - -Timer * App::timerAtIndex(int i) { - assert(i == 0); - return &m_logoController; + AppsContainer::sharedAppsContainer()->addTimer(&m_logoController); } bool App::processEvent(Ion::Events::Event e) { if (e == Ion::Events::Home) { return true; } - if (e == Ion::Events::OnOff) { + if (e == Ion::Events::OnOff && !GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { Ion::Power::standby(); // Force a core reset to exit } return ::App::processEvent(e); diff --git a/apps/on_boarding/app.h b/apps/on_boarding/app.h index d9dbe43a487..38233c5bdf4 100644 --- a/apps/on_boarding/app.h +++ b/apps/on_boarding/app.h @@ -15,9 +15,6 @@ class App : public ::App { App * unpack(Container * container) override; Descriptor * descriptor() override; }; - - int numberOfTimers() override; - Timer * timerAtIndex(int i) override; bool processEvent(Ion::Events::Event) override; void didBecomeActive(Window * window) override; private: diff --git a/apps/on_boarding/logo_controller.cpp b/apps/on_boarding/logo_controller.cpp index 13df87e4d01..fb870c3829d 100644 --- a/apps/on_boarding/logo_controller.cpp +++ b/apps/on_boarding/logo_controller.cpp @@ -16,7 +16,7 @@ LogoController::LogoController() : } bool LogoController::fire() { - Container::activeApp()->dismissModalViewController(); + Container::activeApp()->dismissModalViewController(); return true; } @@ -41,7 +41,6 @@ void LogoController::viewWillAppear() { if (!backlightInitialized) { Ion::Backlight::init(); } - ViewController::viewWillAppear(); } void LogoController::viewDidDisappear() { @@ -53,6 +52,7 @@ void LogoController::viewDidDisappear() { AppsContainer::sharedAppsContainer()->activateExamMode(GlobalPreferences::sharedGlobalPreferences()->examMode()); } } + AppsContainer::sharedAppsContainer()->removeTimer(this); ViewController::viewDidDisappear(); } diff --git a/apps/on_boarding/logo_icon.png b/apps/on_boarding/logo_icon.png index 0dc9c6174e3..bfec4d823a2 100644 Binary files a/apps/on_boarding/logo_icon.png and b/apps/on_boarding/logo_icon.png differ diff --git a/apps/on_boarding/prompt_controller.cpp b/apps/on_boarding/prompt_controller.cpp index e938a43a0c8..773a7704198 100644 --- a/apps/on_boarding/prompt_controller.cpp +++ b/apps/on_boarding/prompt_controller.cpp @@ -4,8 +4,8 @@ namespace OnBoarding { -PromptController::MessageViewWithSkip::MessageViewWithSkip(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages) : - MessageView(messages, colors, numberOfMessages), +PromptController::MessageViewWithSkip::MessageViewWithSkip(I18n::Message * messages, KDColor * fgcolors, KDColor * bgcolors, uint8_t numberOfMessages) : + MessageView(messages, fgcolors, bgcolors, numberOfMessages), m_skipView(KDFont::SmallFont, I18n::Message::Skip, 1.0f, 0.5f), m_okView() { @@ -42,9 +42,9 @@ void PromptController::MessageViewWithSkip::layoutSubviews(bool force) { m_okView.setFrame(KDRect(width - okSize.width()-k_okMargin, height-okSize.height()-k_okMargin, okSize), force); } -PromptController::PromptController(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages) : +PromptController::PromptController(I18n::Message * messages, KDColor * fgcolors, KDColor * bgcolors, uint8_t numberOfMessages) : ViewController(nullptr), - m_messageViewWithSkip(messages, colors, numberOfMessages) + m_messageViewWithSkip(messages, fgcolors, bgcolors, numberOfMessages) { } diff --git a/apps/on_boarding/prompt_controller.h b/apps/on_boarding/prompt_controller.h index b0ce7b61554..eb251868bf5 100644 --- a/apps/on_boarding/prompt_controller.h +++ b/apps/on_boarding/prompt_controller.h @@ -10,13 +10,13 @@ namespace OnBoarding { class PromptController : public ViewController { public: - PromptController(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages); + PromptController(I18n::Message * messages, KDColor * fgcolors, KDColor * bgcolors, uint8_t numberOfMessages); View * view() override { return &m_messageViewWithSkip; } bool handleEvent(Ion::Events::Event event) override; private: class MessageViewWithSkip : public MessageView { public: - MessageViewWithSkip(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages); + MessageViewWithSkip(I18n::Message * messages, KDColor * fgcolors, KDColor * bgcolors, uint8_t numberOfMessages); protected: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; diff --git a/apps/probability/app.h b/apps/probability/app.h index d93905c4690..2e8e3fddfd4 100644 --- a/apps/probability/app.h +++ b/apps/probability/app.h @@ -57,7 +57,7 @@ class App : public Shared::TextFieldDelegateApp { void deleteDistributionAndCalculation(); void initializeDistributionAndCalculation(); -#if __EMSCRIPTEN__ +#if (defined __EMSCRIPTEN__) || (defined _FXCG) constexpr static int k_distributionAlignments[] = {alignof(BinomialDistribution),alignof(ExponentialDistribution), alignof(NormalDistribution), alignof(PoissonDistribution), alignof(UniformDistribution), 0}; constexpr static size_t k_distributionAlignment = max(k_distributionAlignments); constexpr static int k_calculationAlignments[] = {alignof(LeftIntegralCalculation),alignof(FiniteIntegralCalculation), alignof(RightIntegralCalculation), 0}; diff --git a/apps/probability/calculation_controller.cpp b/apps/probability/calculation_controller.cpp index 2d4ddd1b639..3b69a7b3f7e 100644 --- a/apps/probability/calculation_controller.cpp +++ b/apps/probability/calculation_controller.cpp @@ -183,7 +183,7 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int constexpr int precision = Preferences::LargeNumberOfSignificantDigits; constexpr int bufferSize = PrintFloat::charSizeForFloatsWithPrecision(precision); char buffer[bufferSize]; - // FIXME: Leo has not decided yet if we should use the prefered mode instead of always using scientific mode + // FIXME: Leo has not decided yet if we should use the preferred mode instead of always using scientific mode PoincareHelpers::ConvertFloatToTextWithDisplayMode(m_calculation->parameterAtIndex(i-1), buffer, bufferSize, precision, Preferences::PrintFloatMode::Decimal); field->setText(buffer); } diff --git a/apps/probability/distribution/distribution.cpp b/apps/probability/distribution/distribution.cpp index aae3631d01a..d9872d177de 100644 --- a/apps/probability/distribution/distribution.cpp +++ b/apps/probability/distribution/distribution.cpp @@ -134,7 +134,7 @@ double Distribution::cumulativeDistributiveInverseForProbabilityUsingIncreasingF * the given ax bx bounds */ if (!(std::isnan(result.x2()) || std::fabs(result.x2()) <= FLT_EPSILON || std::fabs(result.x1()- ax) < FLT_EPSILON || std::fabs(result.x1() - bx) < FLT_EPSILON)) { /* TODO We would like to put this as an assertion, but sometimes we do get - * false result: we replace them with inf to make the problem obvisous to + * false result: we replace them with inf to make the problem obvious to * the student. */ return *probability > 0.5 ? INFINITY : -INFINITY; } diff --git a/apps/probability/distribution/student_distribution.cpp b/apps/probability/distribution/student_distribution.cpp index bdeb1acf84a..a4e6617d86d 100644 --- a/apps/probability/distribution/student_distribution.cpp +++ b/apps/probability/distribution/student_distribution.cpp @@ -33,7 +33,7 @@ double StudentDistribution::cumulativeDistributiveFunctionAtAbscissa(double x) c if (std::isinf(x)) { return x > 0 ? 1.0 : 0.0; } - /* TODO There are some computation errors, where the probability falsly jumps to 1. + /* TODO There are some computation errors, where the probability falsy jumps to 1. * k = 0.001 and P(x < 42000000) (for 41000000 it is around 0.5) * k = 0.01 and P(x < 8400000) (for 41000000 it is around 0.6) */ const double k = m_parameter1; diff --git a/apps/reader/Makefile b/apps/reader/Makefile new file mode 100644 index 00000000000..0af0aefdf41 --- /dev/null +++ b/apps/reader/Makefile @@ -0,0 +1,21 @@ +apps += Reader::App +app_headers += apps/reader/app.h + +SFLAGS += -DHAS_READER + +app_sreader_src = $(addprefix apps/reader/,\ + app.cpp \ + list_book_controller.cpp \ + utility.cpp \ + read_book_controller \ + word_wrap_view.cpp \ + tex_parser.cpp \ +) + +apps_src += $(app_sreader_src) + +app_images += apps/reader/reader_icon.png + +i18n_files += $(call i18n_without_universal_for,reader/base) + +$(eval $(call depends_on_image,apps/reader/app.cpp,apps/reader/reader_icon.png)) diff --git a/apps/reader/README.md b/apps/reader/README.md new file mode 100644 index 00000000000..1405f99e8f1 --- /dev/null +++ b/apps/reader/README.md @@ -0,0 +1,29 @@ +# Thanks +Thanks to [Gabriel79](https://github.com/Gabriel79) for the original reader app, his source code available [here](https://github.com/Gabriel79/OmegaWithReaderTutorial) and the [tutorial](https://www.codingame.com/playgrounds/55846/reader-faire-une-application-pour-omega-sur-numworks/introduction) to code it ! + +--- + +# Rich text format +Reader app supports now a rich text format : + + * `$` around a LaTeX expression to render it + * `%` around a color-code (see below) to change the color of the text +### LaTeX expressions +You can read the documentation for the LaTeX Parser [here](TexParser.md). +### Color codes : +|code|color| +| --:| ---:| +|`%\last_color%`|Stop using last color| +|`%r%`|Red| +|`%rl%`|Light red| +|`%m%`|Magenta| +|`%t%`|Turquoise| +|`%pk%`|Pink| +|`%pp%`|Purple| +|`%b%`|Blue| +|`%bl%`|Light blue| +|`%br%`|Brown| +|`%o%`|Orange| +|`%g%`|Green| +|`%gl%`|Light green| +|`%c%`|Cyan| \ No newline at end of file diff --git a/apps/reader/TexParser.html b/apps/reader/TexParser.html new file mode 100644 index 00000000000..fdf7fcbac5c --- /dev/null +++ b/apps/reader/TexParser.html @@ -0,0 +1,1901 @@ + + + + + TexParser + + + + + + + + +
+

LaTeX Parser

+ +

In the reader app, you can read a txt file. You can also read a txt file with LaTeX expression inside of it.

+

All the symbols you can use are listed here :

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandOutputCommandOutput
MathExpressions
\frac{ab}{cd} + + + + + a + b + + + c + d + + + + \frac{ab}{cd} + + \sqrt[n]{x} + + + + x + n + + + \sqrt[n]{x} + +
MathSymbols
\times + + + × + + \times + + \div + + + ÷ + + \div + +
\forall + + + + + \forall + + \exists + + + + + \exists + +
\partial + + + + + \partial + + \pm + + + ± + + \pm + +
\infty + + + + + \infty + + \approx + + + + + \approx + +
\neq + + + + + \neq + + \equiv + + + + + \equiv + +
\leq + + + + + \leq + + \geq + + + + + \geq + +
SimpleArrowsDoubleArrows
\leftarrow + + + + + \leftarrow + + \Leftarrow + + + + + \Leftarrow + +
\rightarrow + + + + + \rightarrow + + \Rightarrow + + + + + \Rightarrow + +
\uparrow + + + + + \uparrow + + \Uparrow + + + + + \Uparrow + +
\downarrow + + + + + \downarrow + + \Downarrow + + + + + \Downarrow + +
\leftrightarrow + + + + + \leftrightarrow + +
\updownarrow + + + + + \updownarrow + +
Greek CapitalLettersGreek SmallLetters
\Alpha + + + A + + \Alpha + + \alpha + + + α + + \alpha + +
\Beta + + + B + + \Beta + + \beta + + + β + + \beta + +
\Gamma + + + Γ + + \Gamma + + \gamma + + + γ + + \gamma + +
\Delta + + + Δ + + \Delta + + \delta + + + δ + + \delta + +
\Epsilon + + + E + + \Epsilon + + \epsilon + + + ϵ + + \epsilon + +
\Zeta + + + Z + + \Zeta + + \zeta + + + ζ + + \zeta + +
\Eta + + + H + + \Eta + + \eta + + + η + + \eta + +
\Theta + + + Θ + + \Theta + + \theta + + + θ + + \theta + +
\Iota + + + I + + \Iota + + \iota + + + ι + + \iota + +
\Kappa + + + K + + \Kappa + + \kappa + + + κ + + \kappa + +
\Lambda + + + Λ + + \Lambda + + \lambda + + + λ + + \lambda + +
\Mu + + + M + + \Mu + + \mu + + + μ + + \mu + +
\Nu + + + N + + \Nu + + \nu + + + ν + + \nu + +
\Xi + + + Ξ + + \Xi + + \xi + + + ξ + + \xi + +
\Omicron + + + O + + \Omicron + +
\Pi + + + Π + + \Pi + + \pi + + + π + + \pi + +
\Rho + + + P + + \Rho + + \rho + + + ρ + + \rho + +
\Sigma + + + Σ + + \Sigma + + \sigma + + + σ + + \sigma + +
\Tau + + + T + + \Tau + + \tau + + + τ + + \tau + +
\Upsilon + + + Υ + + \Upsilon + + \upsilon + + + υ + + \upsilon + +
\Phi + + + Φ + + \Phi + + \phi + + + ϕ + + \phi + +
\Chi + + + X + + \Chi + + \chi + + + χ + + \chi + +
\Psi + + + Ψ + + \Psi + + \psi + + + ψ + + \psi + +
\Omega + + + Ω + + \Omega + + \omega + + + ω + + \omega + +
+ +
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/reader/TexParser.md b/apps/reader/TexParser.md new file mode 100644 index 00000000000..2ff9e9865ad --- /dev/null +++ b/apps/reader/TexParser.md @@ -0,0 +1,51 @@ +# LaTeX Parser + +In the reader app, you can read a txt file. You can also read a .urt file with LaTeX expression inside of it. + +All the symbols you can use are listed here : + +|Command|Output||Command|Output| +|--:|:--:|--:|--:|:--:| +|Math|Expressions|||| +|`\frac{ab}{cd}`|$\frac{ab}{cd}$||`\sqrt[n]{x}`|$\sqrt[n]{x}$| +|Math|Symbols||| +|`\times`|$\times$||`\div`|$\div$| +|`\forall`|$\forall$||`\exists`|$\exists$| +|`\partial`|$\partial$||`\pm`|$\pm$| +|`\infty`|$\infty$||`\approx`|$\approx$| +|`\neq`|$\neq$||`\equiv`|$\equiv$| +|`\leq`|$\leq$||`\geq`|$\geq$| +|Simple|Arrows||Double|Arrows| +|`\leftarrow`|$\leftarrow$||`\Leftarrow`|$\Leftarrow$| +|`\rightarrow`|$\rightarrow$||`\Rightarrow`|$\Rightarrow$| +|`\uparrow`|$\uparrow$||`\Uparrow`|$\Uparrow$| +|`\downarrow`|$\downarrow$||`\Downarrow`|$\Downarrow$| +|`\leftrightarrow`|$\leftrightarrow$|||| +|`\updownarrow`|$\updownarrow$|||| +|Greek Capital|Letters||Greek Small|Letters| +|`\Alpha`|$\Alpha$||`\alpha`|$\alpha$| +|`\Beta`|$\Beta$||`\beta`|$\beta$| +|`\Gamma`|$\Gamma$||`\gamma`|$\gamma$| +|`\Delta`|$\Delta$||`\delta`|$\delta$| +|`\Epsilon`|$\Epsilon$||`\epsilon`|$\epsilon$| +|`\Zeta`|$\Zeta$||`\zeta`|$\zeta$| +|`\Eta`|$\Eta$||`\eta`|$\eta$| +|`\Theta`|$\Theta$||`\theta`|$\theta$| +|`\Iota`|$\Iota$||`\iota`|$\iota$| +|`\Kappa`|$\Kappa$||`\kappa`|$\kappa$| +|`\Lambda`|$\Lambda$||`\lambda`|$\lambda$| +|`\Mu`|$\Mu$||`\mu`|$\mu$| +|`\Nu`|$\Nu$||`\nu`|$\nu$| +|`\Xi`|$\Xi$||`\xi`|$\xi$| +|`\Omicron`|$\Omicron$||| +|`\Pi`|$\Pi$||`\pi`|$\pi$| +|`\Rho`|$\Rho$||`\rho`|$\rho$| +|`\Sigma`|$\Sigma$||`\sigma`|$\sigma$| +|`\Tau`|$\Tau$||`\tau`|$\tau$| +|`\Upsilon`|$\Upsilon$||`\upsilon`|$\upsilon$| +|`\Phi`|$\Phi$||`\phi`|$\phi$| +|`\Chi`|$\Chi$||`\chi`|$\chi$| +|`\Psi`|$\Psi$||`\psi`|$\psi$| +|`\Omega`|$\Omega$||`\omega`|$\omega$| + + diff --git a/apps/reader/app.cpp b/apps/reader/app.cpp new file mode 100644 index 00000000000..5d9b8b1563b --- /dev/null +++ b/apps/reader/app.cpp @@ -0,0 +1,40 @@ +#include "app.h" +#include "reader_icon.h" +#include "apps/apps_container.h" +#include "apps/i18n.h" + + +namespace Reader { + +I18n::Message App::Descriptor::name() { + return I18n::Message::ReaderApp; +} + +I18n::Message App::Descriptor::upperName() { + return I18n::Message::ReaderAppCapital; +} + +const Image * App::Descriptor::icon() { + return ImageStore::ReaderIcon; +} + + +App * App::Snapshot::unpack(Container * container) { + return new (container->currentAppBuffer()) App(this); +} + +App::Descriptor * App::Snapshot::descriptor() { + static Descriptor descriptor; + return &descriptor; +} + + +App::App(Snapshot * snapshot) : + ::App(snapshot, &m_stackViewController), + m_listBookController(&m_stackViewController), + m_alternateEmptyViewController(&m_stackViewController, &m_listBookController, &m_listBookController), + m_stackViewController(&m_modalViewController, &m_alternateEmptyViewController) +{ +} + +} diff --git a/apps/reader/app.h b/apps/reader/app.h new file mode 100644 index 00000000000..8de726e9f35 --- /dev/null +++ b/apps/reader/app.h @@ -0,0 +1,31 @@ +#ifndef READER_H +#define READER_H + +#include +#include "list_book_controller.h" + +namespace Reader { + +class App : public ::App { +public: + class Descriptor : public ::App::Descriptor { + public: + I18n::Message name() override; + I18n::Message upperName() override; + const Image * icon() override; + }; + class Snapshot : public ::App::Snapshot { + public: + App * unpack(Container * container) override; + Descriptor * descriptor() override; + }; +private: + App(Snapshot * snapshot); + ListBookController m_listBookController; + AlternateEmptyViewController m_alternateEmptyViewController; + StackViewController m_stackViewController; +}; + +} + +#endif \ No newline at end of file diff --git a/apps/reader/base.de.i18n b/apps/reader/base.de.i18n new file mode 100644 index 00000000000..873f577b188 --- /dev/null +++ b/apps/reader/base.de.i18n @@ -0,0 +1,3 @@ +ReaderApp = "Leser" +ReaderAppCapital = "LESER" +NoFileToDisplay = "Keine Dateien zum Anzeigen" diff --git a/apps/reader/base.en.i18n b/apps/reader/base.en.i18n new file mode 100644 index 00000000000..812b213b83f --- /dev/null +++ b/apps/reader/base.en.i18n @@ -0,0 +1,3 @@ +ReaderApp = "Reader" +ReaderAppCapital = "READER" +NoFileToDisplay = "No file to display" diff --git a/apps/reader/base.es.i18n b/apps/reader/base.es.i18n new file mode 100644 index 00000000000..674bec03e8b --- /dev/null +++ b/apps/reader/base.es.i18n @@ -0,0 +1,3 @@ +ReaderApp = "Lector" +ReaderAppCapital = "LECTOR" +NoFileToDisplay ="No hay archivos para mostrar" diff --git a/apps/reader/base.fr.i18n b/apps/reader/base.fr.i18n new file mode 100644 index 00000000000..e017f6bda44 --- /dev/null +++ b/apps/reader/base.fr.i18n @@ -0,0 +1,3 @@ +ReaderApp = "Liseuse" +ReaderAppCapital = "LISEUSE" +NoFileToDisplay = "Aucun fichier à afficher" diff --git a/apps/reader/base.hu.i18n b/apps/reader/base.hu.i18n new file mode 100644 index 00000000000..d0595b0c639 --- /dev/null +++ b/apps/reader/base.hu.i18n @@ -0,0 +1,3 @@ +ReaderApp = "Olvasó" +ReaderAppCapital = "OLVASÓ" +NoFileToDisplay = "Nincs megjeleníthető fájl" diff --git a/apps/reader/base.it.i18n b/apps/reader/base.it.i18n new file mode 100644 index 00000000000..7adef7e4aaf --- /dev/null +++ b/apps/reader/base.it.i18n @@ -0,0 +1,3 @@ +ReaderApp = "Lettore" +ReaderAppCapital = "LETTORE" +NoFileToDisplay = "Nessun file da visualizzare" diff --git a/apps/reader/base.nl.i18n b/apps/reader/base.nl.i18n new file mode 100644 index 00000000000..0e744c24cc1 --- /dev/null +++ b/apps/reader/base.nl.i18n @@ -0,0 +1,3 @@ +ReaderApp = "Lezer" +ReaderAppCapital = "LEZER" +NoFileToDisplay = "Geen bestanden om weer te geven" diff --git a/apps/reader/base.pt.i18n b/apps/reader/base.pt.i18n new file mode 100644 index 00000000000..0be9365227b --- /dev/null +++ b/apps/reader/base.pt.i18n @@ -0,0 +1,3 @@ +ReaderApp = "Leitor" +ReaderAppCapital = "LEITOR" +NoFileToDisplay = "Nenhum arquivo para exibir" diff --git a/apps/reader/list_book_controller.cpp b/apps/reader/list_book_controller.cpp new file mode 100644 index 00000000000..8dbf7aa3b56 --- /dev/null +++ b/apps/reader/list_book_controller.cpp @@ -0,0 +1,106 @@ +#include "list_book_controller.h" +#include "utility.h" +#include +#include "apps/i18n.h" + +namespace Reader +{ + +View* ListBookController::view() { + return &m_tableView; +} + +ListBookController::ListBookController(Responder * parentResponder): + ViewController(parentResponder), + m_tableView(this, this, this), + m_readBookController(this) +{ + m_txtFilesNumber = filesWithExtension(".txt", m_files, k_maxFilesNumber); + m_urtFilesNumber = filesWithExtension(".urt", m_files + m_txtFilesNumber, k_maxFilesNumber - m_txtFilesNumber); + cleanRemovedBookRecord(); +} + +int ListBookController::numberOfRows() const { + return m_txtFilesNumber + m_urtFilesNumber; +} + +KDCoordinate ListBookController::cellHeight() { + return Metric::StoreRowHeight; +} + +HighlightCell * ListBookController::reusableCell(int index) { + return &m_cells[index]; +} + +int ListBookController::reusableCellCount() const { + return k_cellsNumber; +} + +void ListBookController::willDisplayCellForIndex(HighlightCell * cell, int index) { + MessageTableCell<> * myTextCell = static_cast *>(cell); + MessageTextView* textView = static_cast(myTextCell->labelView()); + textView->setText(m_files[index].name); + myTextCell->setMessageFont(KDFont::LargeFont); //TODO set cell font at building ? +} + +void ListBookController::didBecomeFirstResponder() { + if (selectedRow() < 0) { + selectCellAtLocation(0, 0); + } + Container::activeApp()->setFirstResponder(&m_tableView); +} + +bool ListBookController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) + { + m_readBookController.setBook(m_files[selectedRow()], selectedRow() >= m_txtFilesNumber); + static_cast(parentResponder())->push(&m_readBookController); + Container::activeApp()->setFirstResponder(&m_readBookController); + return true; + } + + return false; +} + + +bool ListBookController::hasBook(const char* filename) const { + for(int i=0;inumberOfRecordsWithExtension("txt"); + for(int i=0; irecordWithExtensionAtIndex("txt", i); + if(!hasBook(r.fullName())) { + r.destroy(); + } + } + + nb = Ion::Storage::sharedStorage()->numberOfRecordsWithExtension("urt"); + for(int i=0; irecordWithExtensionAtIndex("urt", i); + if(!hasBook(r.fullName())) { + r.destroy(); + } + } +} + +bool ListBookController::isEmpty() const { + return m_txtFilesNumber + m_urtFilesNumber == 0; +} + +I18n::Message ListBookController::emptyMessage() { + return I18n::Message::NoFileToDisplay; +} + +Responder * ListBookController::defaultController() { + return parentResponder(); +} + +} \ No newline at end of file diff --git a/apps/reader/list_book_controller.h b/apps/reader/list_book_controller.h new file mode 100644 index 00000000000..3250b173f80 --- /dev/null +++ b/apps/reader/list_book_controller.h @@ -0,0 +1,43 @@ +#ifndef __LIST_BOOK_CONTROLLER_H__ +#define __LIST_BOOK_CONTROLLER_H__ + +#include +#include + +#include "read_book_controller.h" + +namespace Reader +{ + +class ListBookController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource, public AlternateEmptyViewDefaultDelegate +{ +public: + ListBookController(Responder * parentResponder); + View* view() override; + + int numberOfRows() const override; + KDCoordinate cellHeight() override; + HighlightCell * reusableCell(int index) override; + int reusableCellCount() const override; + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + void didBecomeFirstResponder() override; + bool handleEvent(Ion::Events::Event event) override; + bool hasBook(const char* filename) const; + void cleanRemovedBookRecord(); + bool isEmpty() const override; + I18n::Message emptyMessage() override; + Responder * defaultController() override; +private: + SelectableTableView m_tableView; + static const int k_maxFilesNumber = 20; + External::Archive::File m_files[k_maxFilesNumber]; + int m_txtFilesNumber; + int m_urtFilesNumber; + static const int k_cellsNumber = 6; + MessageTableCellWithChevron<> m_cells[k_cellsNumber]; + ReadBookController m_readBookController; +}; + +} + +#endif \ No newline at end of file diff --git a/apps/reader/normalize.py b/apps/reader/normalize.py new file mode 100644 index 00000000000..fff638b333f --- /dev/null +++ b/apps/reader/normalize.py @@ -0,0 +1,18 @@ +import sys +import unicodedata +import argparse +import io +import shutil + +filename = sys.argv[1] + +print("Normalization of "+filename) + +output = open(filename+".tmp", "wb") + +with io.open(filename, "r", encoding='utf-8') as file: + for line in file: + unicodeLine = unicodedata.normalize("NFKD", line) + output.write(unicodeLine.encode("UTF-8")) +output.close() +shutil.move(filename+".tmp",filename) diff --git a/apps/reader/read_book_controller.cpp b/apps/reader/read_book_controller.cpp new file mode 100644 index 00000000000..85ef0b77c23 --- /dev/null +++ b/apps/reader/read_book_controller.cpp @@ -0,0 +1,72 @@ +#include "read_book_controller.h" +#include + +namespace Reader +{ + +ReadBookController::ReadBookController(Responder * parentResponder) : + ViewController(parentResponder), + m_readerView(this) +{ +} + +View * ReadBookController::view() { + return &m_readerView; +} + +void ReadBookController::setBook(const External::Archive::File& file, bool isRichTextFile) { + m_file = &file; + loadPosition(); + m_readerView.setText(reinterpret_cast(file.data), file.dataLength, isRichTextFile); +} + +bool ReadBookController::handleEvent(Ion::Events::Event event) { + if(event == Ion::Events::Down) { + m_readerView.nextPage(); + return true; + } + if(event == Ion::Events::Up) { + m_readerView.previousPage(); + return true; + } + return false; +} + +void ReadBookController::viewDidDisappear() { + savePosition(); +} + +void ReadBookController::throwError() { + static_cast(parentResponder())->pop(); + Container::activeApp()->displayWarning(I18n::Message::SyntaxError); + // As the error is thrown when we are drawing, me must redraw the whole screen + AppsContainer::sharedAppsContainer()->redrawWindow(); +} + +void ReadBookController::savePosition() const { + BookSave save = m_readerView.getBookSave(); + + Ion::Storage::Record::ErrorStatus status = Ion::Storage::sharedStorage()->createRecordWithFullName(m_file->name, &save, sizeof(save)); + if(Ion::Storage::Record::ErrorStatus::NameTaken == status) { + Ion::Storage::Record::Data data; + data.buffer = &save; + data.size = sizeof(save); + status = Ion::Storage::sharedStorage()->recordNamed(m_file->name).setValue(data); + } +} + +void ReadBookController::loadPosition() { + Ion::Storage::Record r = Ion::Storage::sharedStorage()->recordNamed(m_file->name); + if(Ion::Storage::sharedStorage()->hasRecord(r)) { + BookSave save = *(static_cast(r.value().buffer)); + m_readerView.setBookSave(save); + } + else { + m_readerView.setBookSave({ + 0, + Palette::PrimaryText + }); + } +} + +} \ No newline at end of file diff --git a/apps/reader/read_book_controller.h b/apps/reader/read_book_controller.h new file mode 100644 index 00000000000..7fb42b16587 --- /dev/null +++ b/apps/reader/read_book_controller.h @@ -0,0 +1,27 @@ +#ifndef _READ_BOOK_CONTROLLER_H_ +#define _READ_BOOK_CONTROLLER_H_ + +#include +#include "apps/external/archive.h" +#include "word_wrap_view.h" + +namespace Reader { + +class ReadBookController : public ViewController { +public: + ReadBookController(Responder * parentResponder); + View * view() override; + void setBook(const External::Archive::File& file, bool isRichTextFile); + bool handleEvent(Ion::Events::Event event) override; + void viewDidDisappear() override; + void savePosition() const; + void loadPosition(); + void throwError(); +private: + WordWrapTextView m_readerView; + const External::Archive::File* m_file; +}; + +} + +#endif diff --git a/apps/reader/reader_icon.png b/apps/reader/reader_icon.png new file mode 100644 index 00000000000..78e23e13951 Binary files /dev/null and b/apps/reader/reader_icon.png differ diff --git a/apps/reader/tex_parser.cpp b/apps/reader/tex_parser.cpp new file mode 100644 index 00000000000..339ee8022d7 --- /dev/null +++ b/apps/reader/tex_parser.cpp @@ -0,0 +1,368 @@ +#include "tex_parser.h" +#include + +namespace Reader { + + // List of available Symbols + static constexpr char const * k_SymbolsCommands[] = { + "times", "div", "forall", "partial", "exists", "nexists", "pm", "approx", "infty", "neq", "equiv", "leq", "geq", + "cap", "cup", "Cap", "Cup", "subset", "nsubset", "In", "Notin", + "leftarrow", "uparrow", "rightarrow", "downarrow","leftrightarrow", "updownarrow", "Leftarrow", "Uparrow", "Rightarrow", "Downarrow", + "nwarrow", "nearrow", "swarrow", "searrow", "in", "cdot", "cdots", "ldots", + "Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta", "Eta", "Theta", "Iota", "Kappa", "Lambda", + "Mu", "Nu", "Xi", "Omicron", "Pi", "Rho", "Sigma", "Tau", "Upsilon", "Phi", "Chi", "Psi","Omega", + "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", + "mu", "nu", "xi", "omicron", "pi", "rho", "sigma", "tau", "upsilon", "phi", "chi", "psi", "omega", + "sim", "f", "i", + }; + + static constexpr int const k_NumberOfSymbols = sizeof(k_SymbolsCommands) / sizeof(char *); + + // List of the available Symbol's CodePoints in the same order of the Symbol's list + static constexpr uint32_t const k_SymbolsCodePoints[] = { + 0xd7, 0xf7, 0x2200, 0x2202, 0x2203, 0x2204, 0xb1, 0x2248, 0x221e, 0x2260, 0x2261, 0x2264, 0x2265, + 0x2229, 0x222a, 0x22c2, 0x22c3, 0x2282, 0x2284, 0x2208, 0x2209, + 0x2190, 0x2191, 0x2192, 0x2193, 0x2194, 0x2195, 0x21d0, 0x21d1, 0x21d2, 0x21d3, + 0x2196, 0x2197, 0x2198, 0x2199, 0x454, 0xb7, 0x2505, 0x2026, + 0x391, 0x392, 0x393, 0x394, 0x395, 0x396, 0x397, 0x398, 0x399, 0x39a, 0x39b, + 0x39c, 0x39d, 0x39e, 0x39f, 0x3a0, 0x3a1, 0x3a3, 0x3a4, 0x3a5, 0x3a6, 0x3a7, 0x3a8, 0x3a9, + 0x3b1, 0x3b2, 0x3b3, 0x3b4, 0x3b5, 0x3b6, 0x3b7, 0x3b8, 0x3b9, 0x3ba, 0x3bb, + 0x3bc, 0x3bd, 0x3be, 0x3bf, 0x3c0, 0x3c1, 0x3c3, 0x3c4, 0x3c5, 0x3c6, 0x3c7, 0x3c8, 0x3c9, + 0x7e, 0x192, 0x1d422, + }; + + static_assert(sizeof(k_SymbolsCodePoints) / sizeof(uint32_t) == k_NumberOfSymbols); + + // List of available Function Commands that don't require a specific handling + static char const * k_FunctionCommands[] = { + "arccos", "arcsin", "arctan", "arg", "cos", "cosh", "cot", "coth", + "csc", "deg", "det", "dim", "exp", "gcd", "hom", "inf", + "ker", "lg", "lim", "liminf", "limsup", "ln", "log", "max", + "min", "Pr", "sec", "sin", "sinh", "sup", "tan", "tanh" + }; + + static int const k_NumberOfFunctionCommands = sizeof(k_FunctionCommands) / sizeof(char *); + +TexParser::TexParser(const char * text, const char * endOfText) : + m_text(text), + m_endOfText(endOfText), + m_hasError(false) +{ + +} + +Layout TexParser::getLayout() { + Layout layout = popText(0); + + if (m_hasError) { + return CodePointLayout::Builder(CodePoint(0xfffd)); + } + + return layout; +} + +Layout TexParser::popBlock() { + while (*m_text == ' ') { + m_text ++; + } + + if (*m_text == '{') { + m_text ++; + return popText('}'); + } + + if (*m_text == '\\') { + m_text ++; + return popCommand(); + } + + if (m_text >= m_endOfText) { + m_hasError = true; + } + + UTF8Decoder decoder(m_text); + m_text ++; + return CodePointLayout::Builder(decoder.nextCodePoint()); +} + +Layout TexParser::popText(char stop) { + HorizontalLayout layout = HorizontalLayout::Builder(); + const char * start = m_text; + + while (m_text < m_endOfText && *m_text != stop) { + switch (*m_text) { + // TODO: Factorize this code + case '\\': + if (start != m_text) { + layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); + } + m_text ++; + layout.addOrMergeChildAtIndex(popCommand(), layout.numberOfChildren(), false); + start = m_text; + break; + case ' ': + if (start != m_text) { + layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); + } + m_text ++; + start = m_text; + break; + case '^': + if (start != m_text) { + layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); + } + m_text ++; + layout.addOrMergeChildAtIndex(VerticalOffsetLayout::Builder(popBlock(), VerticalOffsetLayoutNode::Position::Superscript), layout.numberOfChildren(), false); + start = m_text; + break; + case '_': + if (start != m_text) { + layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); + } + m_text ++; + layout.addOrMergeChildAtIndex(VerticalOffsetLayout::Builder(popBlock(), VerticalOffsetLayoutNode::Position::Subscript), layout.numberOfChildren(), false); + start = m_text; + break; + default: + m_text ++; + } + } + + if (start != m_text) { + layout.addOrMergeChildAtIndex(LayoutHelper::String(start, m_text - start), layout.numberOfChildren(), false); + } + + m_text ++; + + if (layout.numberOfChildren() == 1) { + return layout.squashUnaryHierarchyInPlace(); + } + + return layout; +} + +Layout TexParser::popCommand() { + // TODO: Factorize this code + if (strncmp(k_binomCommand, m_text, strlen(k_binomCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_binomCommand)))) { + m_text += strlen(k_binomCommand); + return popBinomCommand(); + } + } + if (strncmp(k_ceilCommand, m_text, strlen(k_ceilCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_ceilCommand)))) { + m_text += strlen(k_ceilCommand); + return popCeilCommand(); + } + } + if (strncmp(k_integralCommand, m_text, strlen(k_integralCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_integralCommand)))) { + m_text += strlen(k_integralCommand); + return popIntegralCommand(); + } + } + if (strncmp(k_intsetCommand, m_text, strlen(k_intsetCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_intsetCommand)))) { + m_text += strlen(k_intsetCommand); + return popIntsetCommand(); + } + } + if (strncmp(k_floorCommand, m_text, strlen(k_floorCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_floorCommand)))) { + m_text += strlen(k_floorCommand); + return popFloorCommand(); + } + } + if (strncmp(k_fracCommand, m_text, strlen(k_fracCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_fracCommand)))) { + m_text += strlen(k_fracCommand); + return popFracCommand(); + } + } + if (strncmp(k_leftCommand, m_text, strlen(k_leftCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_leftCommand)))) { + m_text += strlen(k_leftCommand); + return popLeftCommand(); + } + } + if (strncmp(k_overrightArrowCommand, m_text, strlen(k_overrightArrowCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_overrightArrowCommand)))) { + m_text += strlen(k_overrightArrowCommand); + return popOverrightarrowCommand(); + } + } + if (strncmp(k_overlineCommand, m_text, strlen(k_overlineCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_overlineCommand)))) { + m_text += strlen(k_overlineCommand); + return popOverlineCommand(); + } + } + if (strncmp(k_productCommand, m_text, strlen(k_productCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_productCommand)))) { + m_text += strlen(k_productCommand); + return popProductCommand(); + } + } + if (strncmp(k_rightCommand, m_text, strlen(k_rightCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_rightCommand)))) { + m_text += strlen(k_rightCommand); + return popRightCommand(); + } + } + if (strncmp(k_spaceCommand, m_text, strlen(k_spaceCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_spaceCommand)))) { + m_text += strlen(k_spaceCommand); + return popSpaceCommand(); + } + } + if (strncmp(k_sqrtCommand, m_text, strlen(k_sqrtCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_sqrtCommand)))) { + m_text += strlen(k_sqrtCommand); + return popSqrtCommand(); + } + } + if (strncmp(k_sumCommand, m_text, strlen(k_sumCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_sumCommand)))) { + m_text += strlen(k_sumCommand); + return popSumCommand(); + } + } + if (strncmp(k_overlineCommand, m_text, strlen(k_overlineCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_overlineCommand)))) { + m_text += strlen(k_overlineCommand); + return popOverlineCommand(); + } + } + if (strncmp(k_intsetCommand, m_text, strlen(k_intsetCommand)) == 0) { + if (isCommandEnded(*(m_text + strlen(k_intsetCommand)))) { + m_text += strlen(k_intsetCommand); + return popIntsetCommand(); + } + } + for (int i = 0; i < k_NumberOfSymbols; i++) { + if (strncmp(k_SymbolsCommands[i], m_text, strlen(k_SymbolsCommands[i])) == 0) { + if (isCommandEnded(*(m_text + strlen(k_SymbolsCommands[i])))) { + m_text += strlen(k_SymbolsCommands[i]); + return popSymbolCommand(i); + } + } + } + + for (int i = 0; i < k_NumberOfFunctionCommands; i++) { + if (strncmp(k_FunctionCommands[i], m_text, strlen(k_FunctionCommands[i])) == 0) { + if (isCommandEnded(*(m_text + strlen(k_FunctionCommands[i])))) { + m_text += strlen(k_FunctionCommands[i]); + return LayoutHelper::String(k_FunctionCommands[i], strlen(k_FunctionCommands[i])); + } + } + } + + m_hasError = true; + return EmptyLayout::Builder(); +} + +// Expressions +Layout TexParser::popBinomCommand() { + Layout numerator = popBlock(); + Layout denominator = popBlock(); + BinomialCoefficientLayout b = BinomialCoefficientLayout::Builder(numerator, denominator); + return b; +} + +Layout TexParser::popCeilCommand() { + Layout ceil = popBlock(); + return CeilingLayout::Builder(ceil); +} + +Layout TexParser::popFloorCommand() { + Layout floor = popBlock(); + return FloorLayout::Builder(floor); +} + +Layout TexParser::popFracCommand() { + Layout numerator = popBlock(); + Layout denominator = popBlock(); + FractionLayout l = FractionLayout::Builder(numerator, denominator); + return l; +} + +Layout TexParser::popIntegralCommand() { + Layout arg = popBlock(); + Layout var = popBlock(); + Layout start = popBlock(); + Layout end = popBlock(); + return IntegralLayout::Builder(arg, var, start, end); +} + +Layout TexParser::popIntsetCommand() { + HorizontalLayout intset = HorizontalLayout::Builder(); + intset.addOrMergeChildAtIndex(CodePointLayout::Builder(0x27e6), 0, false); + intset.addOrMergeChildAtIndex(popBlock(), intset.numberOfChildren(), false); + intset.addOrMergeChildAtIndex(CodePointLayout::Builder(0x27e7), intset.numberOfChildren(), false); + return intset; +} + +Layout TexParser::popLeftCommand() { + m_text++; + return LeftParenthesisLayout::Builder(); +} + +Layout TexParser::popProductCommand() { + Layout arg = popBlock(); + Layout var = popBlock(); + Layout start = popBlock(); + Layout end = popBlock(); + return ProductLayout::Builder(arg, var, start, end); +} + + +Layout TexParser::popRightCommand() { + m_text++; + return RightParenthesisLayout::Builder(); +} + +Layout TexParser::popSqrtCommand() { + while (*m_text == ' ') { + m_text ++; + } + if (*m_text == '[') { + m_text ++; + Layout rootFactor = popText(']'); + Layout belowRoot = popBlock(); + return NthRootLayout::Builder(belowRoot, rootFactor); + } + else { + return NthRootLayout::Builder(popBlock()); + } +} + +Layout TexParser::popSumCommand() { + Layout arg = popBlock(); + Layout var = popBlock(); + Layout start = popBlock(); + Layout end = popBlock(); + return SumLayout::Builder(arg, var, start, end); +} + +Layout TexParser::popSpaceCommand() { + return LayoutHelper::String(" ", 1); +} + +Layout TexParser::popOverrightarrowCommand() { + return VectorLayout::Builder(popBlock()); +} + +Layout TexParser::popOverlineCommand() { + return ConjugateLayout::Builder(popBlock()); +} + +Layout TexParser::popSymbolCommand(int SymbolIndex) { + uint32_t codePoint = k_SymbolsCodePoints[SymbolIndex]; + return CodePointLayout::Builder(codePoint); +} + +inline bool TexParser::isCommandEnded(char c) const { + return !(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z'); +} + +} diff --git a/apps/reader/tex_parser.h b/apps/reader/tex_parser.h new file mode 100644 index 00000000000..65cb0cc6ab8 --- /dev/null +++ b/apps/reader/tex_parser.h @@ -0,0 +1,66 @@ +#ifndef __TEX_PARSER_H__ +#define __TEX_PARSER_H__ + +#include +#include +#include + +using namespace Poincare; + +namespace Reader +{ +/// @brief Class used in the WordWrapTextView class to parse a Tex expression +class TexParser { +public: + TexParser(const char * text, const char * endOfText); + Layout getLayout(); +private: + Layout popBlock(); + Layout popText(char stop); + Layout popCommand(); + + // Expressions + Layout popBinomCommand(); + Layout popCeilCommand(); + Layout popFloorCommand(); + Layout popFracCommand(); + Layout popIntegralCommand(); + Layout popIntsetCommand(); + Layout popLeftCommand(); + Layout popOverrightarrowCommand(); + Layout popOverlineCommand(); + Layout popProductCommand(); + Layout popRightCommand(); + Layout popSqrtCommand(); + Layout popSumCommand(); + Layout popSpaceCommand(); + + //Symbols + Layout popSymbolCommand(int SymbolIndex); + + const char * m_text; + const char * m_endOfText; + bool m_hasError; + + inline bool isCommandEnded(char c) const; + + // Expressions that require specific handling + static constexpr char const * k_binomCommand = "binom"; + static constexpr char const * k_ceilCommand = "ceil"; + static constexpr char const * k_floorCommand = "floor"; + static constexpr char const * k_fracCommand = "frac"; + static constexpr char const * k_integralCommand = "int"; + static constexpr char const * k_intsetCommand = "intset"; + static constexpr char const * k_leftCommand = "left"; + static constexpr char const * k_overrightArrowCommand = "overrightarrow"; + static constexpr char const * k_overlineCommand = "overline"; + static constexpr char const * k_productCommand = "prod"; + static constexpr char const * k_rightCommand = "right"; + static constexpr char const * k_spaceCommand = "space"; + static constexpr char const * k_sqrtCommand = "sqrt"; + static constexpr char const * k_sumCommand = "sum"; +}; + +} + +#endif diff --git a/apps/reader/utility.cpp b/apps/reader/utility.cpp new file mode 100644 index 00000000000..c29c28f1f29 --- /dev/null +++ b/apps/reader/utility.cpp @@ -0,0 +1,152 @@ +#include "utility.h" + +#include + +#ifndef DEVICE +#include +#include +#include +#endif + +namespace Reader +{ + +bool stringEndsWith(const char* str, const char* pattern) { + int strLength = strlen(str); + int patternLength = strlen(pattern); + if (patternLength > strLength) { + return false; + } + + const char* strIter = str + strlen(str); + const char* patternIter = pattern + strlen(pattern); + + while(*strIter == *patternIter) { + if(patternIter == pattern) { + return true; + } + strIter--; + patternIter--; + } + return false; +} + +void stringNCopy(char* dest, int max, const char* src, int len) { + while(len>0 && max >1 && *src) + { + *dest = *src; + dest++; + src++; + len--; + max--; + } + *dest=0; +} + + +#ifdef DEVICE + +int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize) { + size_t nbTotalFiles = External::Archive::numberOfFiles(); + int nbFiles = 0; + for(size_t i=0; i < nbTotalFiles; ++i) { + External::Archive::File file; + External::Archive::fileAtIndex(i, file); + if(stringEndsWith(file.name, extension)) { + files[nbFiles] = file; + nbFiles++; + if(nbFiles == filesSize) + break; + } + } + return nbFiles; +} +#else + +static void fillFileData(External::Archive::File& file) { + file.data = nullptr; + file.dataLength = 0; + + struct stat info; + if (stat(file.name, &info) != 0) { + return; + } + + unsigned char* content = new unsigned char[info.st_size]; + if (content == NULL) { + return; + } + FILE *fp = fopen(file.name, "rb"); + if (fp == NULL) { + return ; + } + + fread(content, info.st_size, 1, fp); + fclose(fp); + file.data = content; + file.dataLength = info.st_size; +} + +int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize) { + dirent *file; + DIR *d = opendir("."); + int nb = 0; + if (d) { + while ((file = readdir(d)) != NULL) { + if(stringEndsWith(file->d_name, extension)) { + files[nb].name = strdup(file->d_name);//will probably leak + fillFileData(files[nb]); + nb++; + if(nb == filesSize) { + break; + } + } + } + closedir(d); + } + return nb; +} +#endif + +const char * EndOfPrintableWord(const char * word, const char * end) { + if (word >= end) { + return word; + } + UTF8Decoder decoder(word); + CodePoint codePoint = decoder.nextCodePoint(); + const char * result = word; + while (codePoint != '\n' && codePoint != ' ' && codePoint != '%' && codePoint != '$' && codePoint != '\\') { + result = decoder.stringPosition(); + if (result >= end) { + break; + } + codePoint = decoder.nextCodePoint(); + } + return result; +} + +const char * StartOfPrintableWord(const char * word, const char * start) { + if (word <= start) { + return word; + } + // Go to start of code point with some code points dark magic + if (!((*word & 0x80) == 0)) { + word--; + while (!(*word & 0x80 && *word & 0x40)) { + word--; + } + } + UTF8Decoder decoder(start, word); + CodePoint codePoint = decoder.previousCodePoint(); + const char * result = word; + while (codePoint != '\n' && codePoint != ' ' && codePoint != '%' && codePoint != '$') { + result = decoder.stringPosition(); + if (result <= start) { + break; + } + codePoint = decoder.previousCodePoint(); + } + return result; +} + +} \ No newline at end of file diff --git a/apps/reader/utility.h b/apps/reader/utility.h new file mode 100644 index 00000000000..61aabbf2cf6 --- /dev/null +++ b/apps/reader/utility.h @@ -0,0 +1,18 @@ +#ifndef __UTILITY_H__ +#define __UTILITY_H__ + +#include +#include +#include + +namespace Reader +{ + +bool stringEndsWith(const char* str, const char* end); +int filesWithExtension(const char* extension, External::Archive::File* files, int filesSize); +void stringNCopy(char* dest, int max, const char* src, int len); +const char * EndOfPrintableWord(const char * word, const char * end); +const char * StartOfPrintableWord(const char * word, const char * start); + +} +#endif \ No newline at end of file diff --git a/apps/reader/word_wrap_view.cpp b/apps/reader/word_wrap_view.cpp new file mode 100644 index 00000000000..3585e966951 --- /dev/null +++ b/apps/reader/word_wrap_view.cpp @@ -0,0 +1,789 @@ + +#include "word_wrap_view.h" +#include "utility.h" +#include "tex_parser.h" +#include +#include "../shared/poincare_helpers.h" +#include +#include "read_book_controller.h" + +namespace Reader +{ + +WordWrapTextView::WordWrapTextView(ReadBookController * readBookController) : + PointerTextView(GlobalPreferences::sharedGlobalPreferences()->font()), + m_pageOffset(0), + m_nextPageOffset(0), + m_length(0), + m_isRichTextFile(false), // Value isn't important, it will change when the file is loaded + m_lastPagesOffsetsIndex(0), + m_textColor(Palette::PrimaryText), + m_readBookController(readBookController) +{ + for (int i = 0; i < k_lastOffsetsBufferSize; i++) { + m_lastPagesOffsets[i] = -1; // -1 Means : no informations + } +} + +void WordWrapTextView::nextPage() { + if(m_nextPageOffset >= m_length) { + return; + } + m_lastPagesOffsets[m_lastPagesOffsetsIndex] = m_pageOffset; + m_lastPagesOffsetsIndex = (m_lastPagesOffsetsIndex + 1) % k_lastOffsetsBufferSize; + m_pageOffset = m_nextPageOffset; + markRectAsDirty(bounds()); +} + +void WordWrapTextView::setText(const char* text, int length, bool isRichTextFile) { + PointerTextView::setText(text); + m_length = length; + m_isRichTextFile = isRichTextFile; +} + +void WordWrapTextView::previousPage() { + if(m_pageOffset <= 0) { + return; + } + + /* We check if we have available data in our buffer */ + int offsetToCheck = (m_lastPagesOffsetsIndex + k_lastOffsetsBufferSize - 1) % k_lastOffsetsBufferSize; + if (m_lastPagesOffsets[offsetToCheck] != -1) { + m_lastPagesOffsetsIndex = offsetToCheck; + m_pageOffset = m_lastPagesOffsets[offsetToCheck]; + m_lastPagesOffsets[offsetToCheck] = -1; + } else if (m_isRichTextFile) { + richTextPreviousPage(); + } else { + plainTextPreviousPage(); + } + + markRectAsDirty(bounds()); +} + +void WordWrapTextView::richTextPreviousPage() { + const int charWidth = m_font->glyphSize().width(); + const int charHeight = m_font->glyphSize().height(); + + const char * endOfWord = text() + m_pageOffset - 1; + if (*endOfWord == '\n') { + endOfWord --; + } + + KDCoordinate baseline = charHeight / 2; + + KDPoint textBottomEndPosition = KDPoint(m_frame.width() - k_margin, m_frame.height() - k_margin); + KDCoordinate lineHeight = charHeight; + + while(endOfWord >= text()) { + // 1. Skip whitespaces and line jumps + const char * invisiblesCharJumped = endOfWord; // We use this to update endOfWord only if we don't change page + bool changePage = false; + while(invisiblesCharJumped >= text() && (*invisiblesCharJumped == ' ' || *invisiblesCharJumped == '\n')) { + if(*invisiblesCharJumped == '\n') { + textBottomEndPosition = KDPoint(m_frame.width() - k_margin, textBottomEndPosition.y() - lineHeight); + lineHeight = charHeight; + baseline = charHeight / 2; + // We check if we must change page + if (textBottomEndPosition.y() - lineHeight <= k_margin) { + // We don't let text on a new line or a space + endOfWord ++; + changePage = true; + break; + } + } else { + textBottomEndPosition = KDPoint(textBottomEndPosition.x() - charWidth, textBottomEndPosition.y()); + } + invisiblesCharJumped--; + } + + if (changePage) { + break; + } + endOfWord = invisiblesCharJumped; + + // 3. If word is a color change + if (*endOfWord == '%' && *(endOfWord - 1) != '\\') { + const char * startOfWord = endOfWord - 2; + while (*startOfWord != '%') { + startOfWord--; + } + + if (updateTextColorBackward(startOfWord)) { + endOfWord = startOfWord - 1; // Update next endOfWord + continue; + } else { + m_readBookController->throwError(); + return; + } + } + + KDSize textSize = KDSizeZero; + + // 4. If word is a mathematical expression + if (*endOfWord == '$' && *(endOfWord - 1) != '\\') { + // We go to the end of the expression + 1 + const char * expressionStart = --endOfWord; + while (*expressionStart != '$') { + if (expressionStart < text()) { + break; // File isn't rightly formated + } + expressionStart --; + } + + TexParser parser = TexParser(expressionStart, endOfWord); + Layout layout = parser.getLayout(); + + KDCoordinate layoutBaseline = layout.baseline(); + + // We check if we must change baseline + if (layoutBaseline > baseline) { + baseline = layoutBaseline; + } + + KDSize layoutSize = layout.layoutSize(); + textSize = KDSize(layoutSize.width(), layoutSize.height() + baseline - layoutBaseline); + + endOfWord = expressionStart; + } + + // 5. Else it's text + else { + // We go to the start of the word + const char * startOfWord = StartOfPrintableWord(endOfWord, text()); + + textSize = m_font->stringSizeUntil(startOfWord, endOfWord + 1); + + endOfWord = startOfWord; + } + + // 6. We check if we must change line + if (textBottomEndPosition.x() - textSize.width() <= k_margin) { + textBottomEndPosition = KDPoint(m_frame.width() - k_margin, textBottomEndPosition.y() - lineHeight); + lineHeight = 0; + baseline = charHeight; + // We will check if we must change page below + } + textBottomEndPosition = KDPoint(textBottomEndPosition.x() - textSize.width(), textBottomEndPosition.y()); + + // 7. We update height of the line if needed + if (textSize.height() > lineHeight) { + lineHeight = textSize.height(); + // We check if we must change page + if (textBottomEndPosition.y() - lineHeight <= k_margin) { + break; + } + } + + endOfWord -= 1; + } + + if (endOfWord + 1 == text()) { + m_pageOffset = 0; + } else { + m_pageOffset = endOfWord - text(); + } +} + +void WordWrapTextView::plainTextPreviousPage() { + const int charWidth = m_font->glyphSize().width(); + const int charHeight = m_font->glyphSize().height(); + + const char * endOfWord = text() + m_pageOffset - 1; + const char * startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); + + KDPoint textEndPosition(m_frame.width() - k_margin, m_frame.height() - k_margin); + + while(endOfWord>text()) { + KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + KDPoint textStartPosition = KDPoint(textEndPosition.x()-textSize.width(), textEndPosition.y()); + + if (textStartPosition.x() < k_margin) { + // We check if the word is too long to be displayed entirely on just one line + if(textSize.width() > m_frame.width() - 2 * k_margin) { + startOfWord = endOfWord - (textEndPosition.x() - k_margin) / charWidth; + continue; + } + textEndPosition = KDPoint(m_frame.width() - k_margin, textEndPosition.y() - charHeight); + textStartPosition = KDPoint(textEndPosition.x() - textSize.width(), textEndPosition.y()); + } + if (textEndPosition.y() - textSize.height() < k_margin) { + break; + } + + --startOfWord; + while (startOfWord >= text() && (*startOfWord == ' ' || *startOfWord == '\n')) { + if (*startOfWord == ' ') { + textStartPosition = KDPoint(textStartPosition.x() - charWidth, textStartPosition.y()); + } else { + textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y() - charHeight); + } + --startOfWord; + } + + if (textStartPosition.y() < k_margin) { // If out of page, quit + break; + } + + if (textStartPosition.y() != textEndPosition.y()) { // If line changed, x is at start of line + textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y()); + } + if (textStartPosition.x() <= k_margin) { // Go to line if left overflow + textStartPosition = KDPoint(m_frame.width() - k_margin, textStartPosition.y() - charHeight); + } + + textEndPosition = textStartPosition; + endOfWord = startOfWord + 1; + startOfWord = UTF8Helper::BeginningOfWord(text(), endOfWord); + } + + while (endOfWord < text() + m_length && (*endOfWord == ' ' || *endOfWord == '\n')) { + endOfWord++; + } + + m_pageOffset = endOfWord - text(); +} + +void WordWrapTextView::drawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(KDRect(0, 0, bounds().width(), bounds().height()), m_backgroundColor); + + if (m_isRichTextFile) { + richTextDrawRect(ctx, rect); + } else { + plainTextDrawRect(ctx, rect); + } +} + +void WordWrapTextView::richTextDrawRect(KDContext * ctx, KDRect rect) const { + enum class ToDraw { + Text, + Expression + }; + + bool endOfPage = false; + + const char * endOfFile = text() + m_length; + const char * startOfWord = text() + m_pageOffset; + + const int charWidth = m_font->glyphSize().width(); + const int charHeight = m_font->glyphSize().height(); + + const int wordMaxLength = (m_frame.width() - 2*k_margin ) / charWidth; + char word[wordMaxLength]; + + Layout tooBigLayout = EmptyLayout::Builder(); // To store layouts that are too big to be displayed entirely on one line + KDCoordinate tooBigLayoutAlreadyWroteWidth = 0; // We store here the part of the layout that has already been written + + KDPoint textPosition = KDPoint(k_margin, k_margin); + + while (!endOfPage && (startOfWord < endOfFile || !tooBigLayout.isEmpty())) { + // We process line by line + + const char * firstReadIndex = startOfWord; + + // 1. We compute the size of what we are going to draw and the baseline + KDSize lineSize = KDSize(0, charHeight); + KDCoordinate baseline = charHeight / 2; + + while (firstReadIndex < endOfFile) { + // 1.0. We check if we are drawing a too big layout + if (!tooBigLayout.isEmpty()) { + lineSize = tooBigLayout.layoutSize(); + baseline = tooBigLayout.baseline(); + + if (tooBigLayout.layoutSize().width() - tooBigLayoutAlreadyWroteWidth > m_frame.width() - 2 * k_margin) { + // Remaining part of the layout don't fit on the line + break; + } + } + + KDSize textSize = KDSizeZero; + KDCoordinate updatedBaseline = 0; // 0 if it's not a layout + + // 1.1. And we check if we are at the end of the line + if(*firstReadIndex == '\n') { + break; + } + + // 1.2. Check if we are in a color change + if (*firstReadIndex == '%') { // We assume each '%' non-escaped is announcing a color change + // We go to the end of the color change + 1 + const char * startIndex = firstReadIndex; + + do { + firstReadIndex ++; + } while (*firstReadIndex != '%' && firstReadIndex < endOfFile && startIndex - firstReadIndex < 5); + firstReadIndex ++; + + if (firstReadIndex - startIndex > 5) { + m_readBookController->throwError(); + return; + } + + continue; + } + + // 1.3. Check if we are in a math expression + if (*firstReadIndex == '$') { + // We go to the end of the expression + 1 + const char * expressionStart = ++firstReadIndex; + while (*firstReadIndex != '$') { + if (firstReadIndex > endOfFile) { + break; // File isn't rightly formated + } + firstReadIndex ++; + } + + TexParser parser = TexParser(expressionStart, firstReadIndex); + Layout firstReadLayout = parser.getLayout(); + + KDCoordinate layoutBaseline = firstReadLayout.baseline(); + + updatedBaseline = baseline; // We really update baseline after, if the layout fit on the line + if (layoutBaseline > baseline) { + updatedBaseline = layoutBaseline; + } + + KDSize layoutSize = firstReadLayout.layoutSize(); + textSize = KDSize(layoutSize.width(), layoutSize.height() + updatedBaseline - layoutBaseline); + + firstReadIndex ++; + } + + // 1.4. Else it's text + else { + if ((*firstReadIndex == '\\' && *(firstReadIndex + 1) == '$') || (*firstReadIndex == '\\' && *(firstReadIndex + 1) == '%')) { // We escape '$' and '%' if needed + firstReadIndex ++; + } + + const char * endOfWord = EndOfPrintableWord(firstReadIndex + 1, endOfFile); + + textSize = m_font->stringSizeUntil(firstReadIndex, endOfWord); + + firstReadIndex = endOfWord; + } + + // 1.5. We update size + int newWidth = lineSize.width() + textSize.width(); + // We check if the new text fit on the line. + // If not, we look if only the word cannot fit on one line. If so, we do not go to the line + if (newWidth > m_frame.width() - 2 * k_margin && !(textSize.width() > m_frame.width() - 2 * k_margin)) { + break; + } + + if (updatedBaseline) { // Now we update baseline + baseline = updatedBaseline; + } + + int newHeight; + if (lineSize.height() > textSize.height()) { + newHeight = lineSize.height(); + } else { + newHeight = textSize.height(); + // We check if all the content can be displayed + if (textPosition.y() + newHeight > bounds().height() - k_margin) { + endOfPage = true; + break; + } + } + lineSize = KDSize(lineSize.width() + textSize.width(), newHeight); + + // 1.6. We go to the next word + while (*firstReadIndex == ' ') { + lineSize = KDSize(lineSize.width() + charWidth, lineSize.height()); + ++firstReadIndex; + } + } + + if (endOfPage) { + break; + } + + // 2. And now... we read the line again to draw it ! + while (startOfWord < endOfFile || !tooBigLayout.isEmpty()) { + // 2.0 Before all, we check if we were drawing a layout that was too big to fit on one line. + // In this case, we do all the routine here. + if (!tooBigLayout.isEmpty()) { + KDPoint position = KDPoint(textPosition.x() - tooBigLayoutAlreadyWroteWidth, textPosition.y()); + tooBigLayout.draw(ctx, position, m_textColor, m_backgroundColor); + // We fill the left margin + ctx->fillRect(KDRect(0, textPosition.y(), k_margin, tooBigLayout.layoutSize().height()), m_backgroundColor); + + KDCoordinate drawnWidth = tooBigLayout.layoutSize().width() - tooBigLayoutAlreadyWroteWidth; + tooBigLayoutAlreadyWroteWidth += drawnWidth; + + if (drawnWidth > m_frame.width() - 2 * k_margin) { + // We have to fill the margin with the background color + ctx->fillRect(KDRect(textPosition.x() + drawnWidth, textPosition.y(), k_margin, lineSize.height()), m_backgroundColor); + textPosition = KDPoint(k_margin, textPosition.y() + lineSize.height()); + break; + } else { + tooBigLayout = EmptyLayout::Builder(); // We have finished drawing the tooBigLayout + } + continue; + } + + Layout layout; + + //2.1. We check if we are at the end of the line + if (*startOfWord == '\n') { + startOfWord++; + textPosition = KDPoint(k_margin, textPosition.y() + lineSize.height()); + break; + // We aren't supposed to be at the end of the page, else the loop on top would have stopped drawing + } + + const char * endOfWord; + + // 2.2. Check if we are in a color change + if (*startOfWord == '%') { + if (updateTextColorForward(startOfWord)) { + startOfWord += 2; // We can add at least 2 ('%' + the color first char) + while (*startOfWord != '%') { + startOfWord ++; + } + startOfWord ++; + continue; + } + else { + m_readBookController->throwError(); + return; + } + } + + // 2.3. Check what we are going to draw and his size + + KDSize textSize = KDSizeZero; + ToDraw toDraw; + + // 2.3.1. Check if we are in a math expression + if (*startOfWord == '$') { + endOfWord = startOfWord + 1; + while (*endOfWord != '$') { + if (endOfWord > endOfFile) { + break; // File isn't rightly formated + } + endOfWord ++; + } + endOfWord ++; + + TexParser parser = TexParser(startOfWord + 1, endOfWord - 1); + layout = parser.getLayout(); + textSize = layout.layoutSize(); + + toDraw = ToDraw::Expression; + } + + // 2.3.2 Else it's text + else { + if ((*startOfWord == '\\' && *(startOfWord + 1) == '$') || (*startOfWord == '\\' && *(startOfWord + 1) == '%')) { + startOfWord ++; + } + endOfWord = EndOfPrintableWord(startOfWord + 1, endOfFile); + textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); + toDraw = ToDraw::Text; + } + + // 2.4 We decide where to draw and if we must change line + KDPoint endTextPosition = KDPoint(textPosition.x() + textSize.width(), textPosition.y()); + + // 2.4.1. Check if we need to go to the next line + if(endTextPosition.x() > m_frame.width() - k_margin) { + + // We check if the word is too long to be displayed entirely on just one line + if(textSize.width() > m_frame.width() - 2 * k_margin) { + if (toDraw == ToDraw::Text) { + startOfWord = endOfWord - (endTextPosition.x() - k_margin) / charWidth; + continue; + } else { + assert(toDraw == ToDraw::Expression); + tooBigLayout = layout; + tooBigLayoutAlreadyWroteWidth += m_frame.width() - k_margin - textPosition.x(); + endTextPosition = KDPoint(k_margin, textPosition.y() + lineSize.height()); // We jump line now, because this will be the next value of textPosition + } + } else { + textPosition = KDPoint(k_margin, textPosition.y() + lineSize.height()); + // endTextPosition will be updated later + break; + } + } + + // 2.5. Now we draw ! + if (toDraw == ToDraw::Expression) { + KDPoint position = KDPoint(textPosition.x(), textPosition.y() + baseline - layout.baseline()); + layout.draw(ctx, position, m_textColor, m_backgroundColor); + + if (!tooBigLayout.isEmpty()) { + // We fill the margin + ctx->fillRect(KDRect(m_frame.width() - k_margin, textPosition.y(), k_margin, lineSize.height()), m_backgroundColor); + } + } + else { + KDPoint position = KDPoint(textPosition.x(), textPosition.y() + baseline - charHeight / 2); + ctx->drawString(word, position, m_font, m_textColor, m_backgroundColor); + } + + // 2.6. Update the position + textPosition = endTextPosition; + + // 2.7. And we go to the next word + while (*endOfWord == ' ') { + endOfWord++; + textPosition = KDPoint(textPosition.x() + charWidth, textPosition.y()); + } + startOfWord = endOfWord; + + // 2.8. We exit now if we are in the "too big layout" case + if (!tooBigLayout.isEmpty()) { + break; + } + } + } + m_nextPageOffset = startOfWord - text(); +} + + +void WordWrapTextView::plainTextDrawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(KDRect(0, 0, bounds().width(), bounds().height()), m_backgroundColor); + + const char * endOfFile = text() + m_length; + const char * startOfWord = text() + m_pageOffset; + const char * endOfWord = UTF8Helper::EndOfWord(startOfWord, endOfFile); + KDPoint textPosition(k_margin, k_margin); + + const int wordMaxLength = 128; + char word[wordMaxLength]; + + const int charWidth = m_font->glyphSize().width(); + const int charHeight = m_font->glyphSize().height(); + + while(startOfWord < endOfFile) { + KDSize textSize = m_font->stringSizeUntil(startOfWord, endOfWord); + KDPoint nextTextPosition = KDPoint(textPosition.x()+textSize.width(), textPosition.y()); + + if(nextTextPosition.x() > m_frame.width() - k_margin) { // Right overflow + // We check if the word is too long to be displayed entirely on just one line + if(textSize.width() > m_frame.width() - 2 * k_margin) { + endOfWord = startOfWord + (m_frame.width() - k_margin - textPosition.x()) / charWidth; + continue; + } + textPosition = KDPoint(k_margin, textPosition.y() + textSize.height()); + nextTextPosition = KDPoint(k_margin + textSize.width(), textPosition.y()); + } + + if(textPosition.y() + textSize.height() > m_frame.height() - k_margin) { // Bottom overflow + break; + } + + stringNCopy(word, wordMaxLength, startOfWord, endOfWord-startOfWord); + ctx->drawString(word, textPosition, m_font, m_textColor, m_backgroundColor); + + while(*endOfWord == ' ' || *endOfWord == '\n') { + if(*endOfWord == ' ') { + nextTextPosition = KDPoint(nextTextPosition.x() + charWidth, nextTextPosition.y()); + } + else { + nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + charHeight); + } + ++endOfWord; + } + + //We must change value of startOfWord now to avoid having + //two times the same word if the break below is used + startOfWord = endOfWord; + + if(nextTextPosition.y() + textSize.height() > m_frame.height() - k_margin) { // If out of page, quit + break; + } + if(nextTextPosition.y() != textPosition.y()) { // If line changed, x is at start of line + nextTextPosition = KDPoint(k_margin, nextTextPosition.y()); + } + if(nextTextPosition.x() >= m_frame.width() - k_margin) { // Go to line if right overflow + nextTextPosition = KDPoint(k_margin, nextTextPosition.y() + textSize.height()); + } + + textPosition = nextTextPosition; + endOfWord = UTF8Helper::EndOfWord(startOfWord, endOfFile); + } + + m_nextPageOffset = startOfWord - text(); +} + +BookSave WordWrapTextView::getBookSave() const { + return { + m_pageOffset, + m_textColor + }; +} + +void WordWrapTextView::setBookSave(BookSave save) { + m_pageOffset = save.offset; + // TODO: Understand why the color save crash the calculator and fix it + // m_textColor = save.color; + m_lastPagesOffsetsIndex = 0; + + for (int i = 0; i < k_lastOffsetsBufferSize; i++) { + m_lastPagesOffsets[i] = -1; // -1 Means : no informations + } +} + +bool WordWrapTextView::updateTextColorForward(const char * colorStart) const { + + if (*(colorStart + 1) == '\\' && (*(colorStart + 3) == '%' || *(colorStart + 4) == '%')) { + m_textColor = Palette::PrimaryText; + return true; + } + + int keySize = 1; + KDColor lastColor = m_textColor; + + switch (*(colorStart+1)) + { + case 'r': + if (*(colorStart+2) == 'l') { + m_textColor = Palette::RedLight; + keySize = 2; + } + else { + m_textColor = Palette::Red; + } + break; + case 'm': + m_textColor = Palette::Magenta; + break; + case 't': + m_textColor = Palette::Turquoise; + break; + case 'p': + if (*(colorStart+2) == 'k') { + m_textColor = Palette::Pink; + keySize = 2; + } + else if (*(colorStart+2) == 'p') { + m_textColor = Palette::Purple; + keySize = 2; + } + break; + case 'b': + if (*(colorStart+2) == 'r') { + m_textColor = Palette::Brown; + keySize = 2; + } + else if (*(colorStart+2) == 'l') { + m_textColor = Palette::BlueLight; + keySize = 2; + } + else { + m_textColor = Palette::Blue; + } + break; + case 'o': + m_textColor = Palette::Orange; + break; + case 'g': + if (*(colorStart+2) == 'l') { + m_textColor = Palette::GreenLight; + keySize = 2; + } + else { + m_textColor = Palette::Green; + } + break; + case 'c': + m_textColor = Palette::Cyan; + break; + + default: + return false; + } + + if (*(colorStart + keySize + 1) != '%') { + m_textColor = lastColor; + return false; + } + + return true; +} + +bool WordWrapTextView::updateTextColorBackward(const char * colorStart) const { + + if (*(++colorStart) != '\\') { + if (*(colorStart + 1) == '%' || *(colorStart + 2) == '%') { + m_textColor = Palette::PrimaryText; + return true; + } + return false; + } + + int keySize = 1; + KDColor lastColor = m_textColor; + switch (*(colorStart+1)) + { + case 'r': + if (*(colorStart+2) == 'l') { + m_textColor = Palette::RedLight; + keySize = 2; + } + else { + m_textColor = Palette::Red; + } + break; + case 'm': + m_textColor = Palette::Magenta; + break; + case 't': + m_textColor = Palette::Turquoise; + break; + case 'p': + if (*(colorStart+2) == 'k') { + m_textColor = Palette::Pink; + keySize = 2; + } + else if (*(colorStart+2) == 'p') { + m_textColor = Palette::Purple; + keySize = 2; + } + break; + case 'b': + if (*(colorStart+2) == 'r') { + m_textColor = Palette::Brown; + keySize = 2; + } + else if (*(colorStart+2) == 'l') { + m_textColor = Palette::BlueLight; + keySize = 2; + } + else { + m_textColor = Palette::Blue; + } + break; + case 'o': + m_textColor = Palette::Orange; + break; + case 'g': + if (*(colorStart+2) == 'l') { + m_textColor = Palette::GreenLight; + keySize = 2; + } + else { + m_textColor = Palette::Green; + } + break; + case 'c': + m_textColor = Palette::Cyan; + break; + + default: + return false; + } + + if (*(colorStart + keySize + 1) != '%') { + m_textColor = lastColor; + return false; + } + + return true; +} + +} diff --git a/apps/reader/word_wrap_view.h b/apps/reader/word_wrap_view.h new file mode 100644 index 00000000000..c50dba12674 --- /dev/null +++ b/apps/reader/word_wrap_view.h @@ -0,0 +1,52 @@ +#ifndef _WORD_WRAP_VIEW_H_ +#define _WORD_WRAP_VIEW_H_ + +#include +#include + +namespace Reader +{ + +struct BookSave { + int offset; + KDColor color; +}; + +class ReadBookController; + +class WordWrapTextView : public PointerTextView { +public: + WordWrapTextView(ReadBookController * readBookController); + void drawRect(KDContext * ctx, KDRect rect) const override; + void setText(const char*, int length, bool isRichTextFile); + void nextPage(); + void previousPage(); + BookSave getBookSave() const; + void setBookSave(BookSave save); +private: + void richTextPreviousPage(); + void plainTextPreviousPage(); + void richTextDrawRect(KDContext * ctx, KDRect rect) const; + void plainTextDrawRect(KDContext * ctx, KDRect rect) const; + bool updateTextColorForward(const char * colorStart) const; + bool updateTextColorBackward(const char * colorStart) const; + static const int k_margin = 10; + static const int k_lastOffsetsBufferSize = 10; + int m_pageOffset; + mutable int m_nextPageOffset; + int m_length; + bool m_isRichTextFile; + mutable KDColor m_textColor; + /* + * Beacause the text that we draw can be of different sizes, we can't + * exactly know where the last page starts. + * So we store into a buffer (a cyclic stack) the offsets of the last pages. + */ + int m_lastPagesOffsets[k_lastOffsetsBufferSize]; + int m_lastPagesOffsetsIndex; + ReadBookController * m_readBookController; +}; + +} + +#endif diff --git a/apps/regression/go_to_parameter_controller.cpp b/apps/regression/go_to_parameter_controller.cpp index 461f98bfadb..984ebbfcd1e 100644 --- a/apps/regression/go_to_parameter_controller.cpp +++ b/apps/regression/go_to_parameter_controller.cpp @@ -67,7 +67,7 @@ bool GoToParameterController::confirmParameterAtIndex(int parameterIndex, double } else { double yFromX = m_store->modelForSeries(series)->evaluate(m_store->coefficientsForSeries(series, globContext), unknown); /* We here compute y2 = a*((y1-b)/a)+b, which does not always give y1, - * because of computation precision. y2 migth thus be invalid. */ + * because of computation precision. y2 might thus be invalid. */ if (std::isnan(yFromX) || std::isinf(yFromX)) { Container::activeApp()->displayWarning(I18n::Message::ForbiddenValue); return false; diff --git a/apps/regression/graph_options_controller.cpp b/apps/regression/graph_options_controller.cpp index 83d80db4343..70770ae0511 100644 --- a/apps/regression/graph_options_controller.cpp +++ b/apps/regression/graph_options_controller.cpp @@ -118,7 +118,7 @@ void GraphOptionsController::willDisplayCellForIndex(HighlightCell * cell, int i return; } assert(index >=0 && index < k_numberOfParameterCells); - MessageTableCellWithChevron * myCell = (MessageTableCellWithChevron *)cell; + MessageTableCellWithChevron<> * myCell = (MessageTableCellWithChevron<> *)cell; I18n::Message titles[k_numberOfParameterCells] = {I18n::Message::XPrediction, I18n::Message::YPrediction}; myCell->setMessage(titles[index]); } diff --git a/apps/regression/graph_options_controller.h b/apps/regression/graph_options_controller.h index 69ec697bf63..202c4d2f806 100644 --- a/apps/regression/graph_options_controller.h +++ b/apps/regression/graph_options_controller.h @@ -32,7 +32,7 @@ class GraphOptionsController : public ViewController, public ListViewDataSource, constexpr static int k_regressionCellType = 0; constexpr static int k_parameterCelltype = 1; constexpr static int k_numberOfParameterCells = 2; - MessageTableCellWithChevron m_parameterCells[k_numberOfParameterCells]; + MessageTableCellWithChevron<> m_parameterCells[k_numberOfParameterCells]; MessageTableCellWithChevronAndExpression m_changeRegressionCell; SelectableTableView m_selectableTableView; GoToParameterController m_goToParameterController; diff --git a/apps/regression/model/model.h b/apps/regression/model/model.h index 1b36fa6a8a0..149cad00b34 100644 --- a/apps/regression/model/model.h +++ b/apps/regression/model/model.h @@ -45,7 +45,7 @@ class Model { Poincare::Layout m_layout; private: // Model attributes - virtual Poincare::Expression expression(double * modelCoefficients) { return Poincare::Expression(); } // expression is overrided only by Models that do not override levelSet + virtual Poincare::Expression expression(double * modelCoefficients) { return Poincare::Expression(); } // expression is overridden only by Models that do not override levelSet virtual double partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const = 0; // Levenberg-Marquardt diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index eebec477603..4fcb7cdb1ae 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -190,7 +190,7 @@ QUIZ_CASE(power_regression) { // assert_regression_is(x2, y2, 4, Model::Type::Power, coefficients2, r22); } -void assert_trigonomatric_regression_is(double * xi, double * yi, int numberOfPoints, double * trueCoefficients, double trueR2, Poincare::Preferences::AngleUnit trueCoeffcientsUnit) { +void assert_trigonometric_regression_is(double * xi, double * yi, int numberOfPoints, double * trueCoefficients, double trueR2, Poincare::Preferences::AngleUnit trueCoeffcientsUnit) { // Test the trigonometric regression at all angle units const Preferences::AngleUnit previousAngleUnit = Preferences::sharedPreferences()->angleUnit(); const Poincare::Preferences::AngleUnit units[3] = {Poincare::Preferences::AngleUnit::Radian, Poincare::Preferences::AngleUnit::Degree, Poincare::Preferences::AngleUnit::Gradian}; @@ -214,7 +214,7 @@ QUIZ_CASE(trigonometric_regression1) { int numberOfPoints = sizeof(x) / sizeof(double); assert(sizeof(y) == sizeof(double) * numberOfPoints); - assert_trigonomatric_regression_is(x, y, numberOfPoints, coefficients, r2, Poincare::Preferences::AngleUnit::Radian); + assert_trigonometric_regression_is(x, y, numberOfPoints, coefficients, r2, Poincare::Preferences::AngleUnit::Radian); } QUIZ_CASE(trigonometric_regression2) { @@ -225,7 +225,7 @@ QUIZ_CASE(trigonometric_regression2) { int numberOfPoints = sizeof(x) / sizeof(double); assert(sizeof(y) == sizeof(double) * numberOfPoints); - assert_trigonomatric_regression_is(x, y, numberOfPoints, coefficients, r2, Poincare::Preferences::AngleUnit::Radian); + assert_trigonometric_regression_is(x, y, numberOfPoints, coefficients, r2, Poincare::Preferences::AngleUnit::Radian); } diff --git a/apps/rpn b/apps/rpn index 67d66295b0a..908ab20719d 160000 --- a/apps/rpn +++ b/apps/rpn @@ -1 +1 @@ -Subproject commit 67d66295b0a5a9bc5daf52412db9ae2ac87bbf57 +Subproject commit 908ab20719de2d8f30d9fc2da5220f502ef014fb diff --git a/apps/sequence/base.de.i18n b/apps/sequence/base.de.i18n index 88f518ca92b..21da2aeabd6 100644 --- a/apps/sequence/base.de.i18n +++ b/apps/sequence/base.de.i18n @@ -17,6 +17,5 @@ NEnd = "Endwert" TermSum = "Summe der Terme" SelectFirstTerm = "Erster Term " SelectLastTerm = "Letzter Term " -ValueNotReachedBySequence = "Wert wird von Folge nicht erreicht" NColumn = "n-te Spalte" FirstTermIndex = "Anfangsindex" diff --git a/apps/sequence/base.en.i18n b/apps/sequence/base.en.i18n index e9917698c8d..8776d4b7910 100644 --- a/apps/sequence/base.en.i18n +++ b/apps/sequence/base.en.i18n @@ -17,6 +17,5 @@ NEnd = "N end" TermSum = "Sum of terms" SelectFirstTerm = "Select First Term " SelectLastTerm = "Select last term " -ValueNotReachedBySequence = "Value not reached by sequence" NColumn = "n column" FirstTermIndex = "First term index" diff --git a/apps/sequence/base.es.i18n b/apps/sequence/base.es.i18n index 4bf2fe4929b..691b286b249 100644 --- a/apps/sequence/base.es.i18n +++ b/apps/sequence/base.es.i18n @@ -17,6 +17,5 @@ NEnd = "N fin" TermSum = "Suma de términos" SelectFirstTerm = "Seleccionar el primer término " SelectLastTerm = "Seleccionar el último término " -ValueNotReachedBySequence = "No se alcanza este valor" NColumn = "Columna n" FirstTermIndex = "Índice del primer término" diff --git a/apps/sequence/base.fr.i18n b/apps/sequence/base.fr.i18n index 3772efb4713..09d8bfa9da9 100644 --- a/apps/sequence/base.fr.i18n +++ b/apps/sequence/base.fr.i18n @@ -17,6 +17,5 @@ NEnd = "N fin" TermSum = "Somme des termes" SelectFirstTerm = "Sélectionner le premier terme " SelectLastTerm = "Sélectionner le dernier terme " -ValueNotReachedBySequence = "Valeur non atteinte par la suite" NColumn = "Colonne n" FirstTermIndex = "Indice premier terme" diff --git a/apps/sequence/base.hu.i18n b/apps/sequence/base.hu.i18n index f98ed2366d8..b52a13cfaf0 100644 --- a/apps/sequence/base.hu.i18n +++ b/apps/sequence/base.hu.i18n @@ -1,22 +1,21 @@ -SequenceApp = "Szekvenciák" -SequenceAppCapital = "SZEKVENCIÁK" -SequenceTab = "Szekvenciák" -AddSequence = "Szekvencia hozzáadása" -ChooseSequenceType = "Válassza ki a sorozat típusát" -SequenceType = "Szekvencia típusa" -Explicit = "Explicit kifejezés" -SingleRecurrence = "Rekurzív elsö sorrend" -DoubleRecurrence = "Rekurzív második sorrend" -SequenceOptions = "Szekvencia opciók" -SequenceColor = "Szekvencia színe" -DeleteSequence = "Sorozat törlése" -NoSequence = "Nincs sorrend" -NoActivatedSequence = "Nincs szekvencia bekapcsolva" -NStart = "N start" -NEnd = "N vég" -TermSum = "A kifejezés összege" -SelectFirstTerm = "Elsö kifejezés kiválasztása " -SelectLastTerm = "Utolsó kifejezés kiválasztása " -ValueNotReachedBySequence = "Az értéket nem érte el a sorozat" -NColumn = "n oszlop" -FirstTermIndex = "Elsö kifejezés index" +SequenceApp = "Szekvenciák" +SequenceAppCapital = "SZEKVENCIÁK" +SequenceTab = "Szekvenciák" +AddSequence = "Szekvencia hozzáadása" +ChooseSequenceType = "Válassza ki a sorozat típusát" +SequenceType = "Szekvencia típusa" +Explicit = "Explicit kifejezés" +SingleRecurrence = "Rekurzív elsö sorrend" +DoubleRecurrence = "Rekurzív második sorrend" +SequenceOptions = "Szekvencia opciók" +SequenceColor = "Szekvencia színe" +DeleteSequence = "Sorozat törlése" +NoSequence = "Nincs sorrend" +NoActivatedSequence = "Nincs szekvencia bekapcsolva" +NStart = "N start" +NEnd = "N vég" +TermSum = "A kifejezés összege" +SelectFirstTerm = "Elsö kifejezés kiválasztása " +SelectLastTerm = "Utolsó kifejezés kiválasztása " +NColumn = "n oszlop" +FirstTermIndex = "Elsö kifejezés index" diff --git a/apps/sequence/base.it.i18n b/apps/sequence/base.it.i18n index d9ddac4a1bf..3d714acfeac 100644 --- a/apps/sequence/base.it.i18n +++ b/apps/sequence/base.it.i18n @@ -17,6 +17,5 @@ NEnd = "N finale" TermSum = "Somma dei termini" SelectFirstTerm = "Selezionare il primo termine " SelectLastTerm = "Selezionare l'ultimo termine " -ValueNotReachedBySequence = "Valore non raggiunto dalla successione" NColumn = "Colonna n" FirstTermIndex = "Indice del primo termine" diff --git a/apps/sequence/base.nl.i18n b/apps/sequence/base.nl.i18n index 34a63d53d43..178761f1d40 100644 --- a/apps/sequence/base.nl.i18n +++ b/apps/sequence/base.nl.i18n @@ -17,6 +17,5 @@ NEnd = "N einde" TermSum = "Som van termen" SelectFirstTerm = "Selecteer eerste term " SelectLastTerm = "Selecteer laatste term " -ValueNotReachedBySequence = "Waarde niet bereikt door de rij" NColumn = "n-kolom" FirstTermIndex = "Eerste termindex" diff --git a/apps/sequence/base.pt.i18n b/apps/sequence/base.pt.i18n index 97f6580f8dd..3cfbb3338a7 100644 --- a/apps/sequence/base.pt.i18n +++ b/apps/sequence/base.pt.i18n @@ -17,6 +17,5 @@ NEnd = "N fim" TermSum = "Soma dos termos" SelectFirstTerm = "Selecionar primeiro termo " SelectLastTerm = "Selecionar último termo " -ValueNotReachedBySequence = "O valor não é alcançado pela sequência" NColumn = "Coluna n" FirstTermIndex = "Índice do primeiro termo" diff --git a/apps/sequence/graph/curve_parameter_controller.h b/apps/sequence/graph/curve_parameter_controller.h index 7d47f1f6d11..c47af5a3dad 100644 --- a/apps/sequence/graph/curve_parameter_controller.h +++ b/apps/sequence/graph/curve_parameter_controller.h @@ -20,7 +20,7 @@ class CurveParameterController : public Shared::FunctionCurveParameterController constexpr static int k_totalNumberOfCells = 2; GoToParameterController * goToParameterController() override; GoToParameterController m_goToParameterController; - MessageTableCell m_sumCell; + MessageTableCell<> m_sumCell; GraphController * m_graphController; }; diff --git a/apps/sequence/list/list_controller.h b/apps/sequence/list/list_controller.h index d16af18d9cd..9aef7c4adf0 100644 --- a/apps/sequence/list/list_controller.h +++ b/apps/sequence/list/list_controller.h @@ -25,6 +25,8 @@ class ListController : public Shared::FunctionListController, public Shared::Inp Toolbox * toolboxForInputEventHandler(InputEventHandler * handler) override; void selectPreviousNewSequenceCell(); void editExpression(int sequenceDefinitionIndex, Ion::Events::Event event); +protected: + virtual const char * recordExtension() const override { return Ion::Storage::seqExtension; } private: static constexpr KDCoordinate k_expressionCellVerticalMargin = 3; bool editInitialConditionOfSelectedRecordWithText(const char * text, bool firstInitialCondition); diff --git a/apps/sequence/list/list_parameter_controller.cpp b/apps/sequence/list/list_parameter_controller.cpp index 9058083e3c9..2ac1397cd62 100644 --- a/apps/sequence/list/list_parameter_controller.cpp +++ b/apps/sequence/list/list_parameter_controller.cpp @@ -24,38 +24,18 @@ const char * ListParameterController::title() { bool ListParameterController::handleEvent(Ion::Events::Event event) { bool hasAdditionalRow = hasInitialRankRow(); -#if FUNCTION_COLOR_CHOICE - if (event == Ion::Events::OK || event == Ion::Events::EXE || (event == Ion::Events::Right && selectedRow() == 1)) { -#else if (event == Ion::Events::OK || event == Ion::Events::EXE || (event == Ion::Events::Right && selectedRow() == 0)) { -#endif int selectedRowIndex = selectedRow(); -#if FUNCTION_COLOR_CHOICE if (selectedRowIndex == 0) { - return handleEnterOnRow(selectedRowIndex); - } - if (selectedRowIndex == 1) { -#else - if (selectedRowIndex == 0) { -#endif StackViewController * stack = (StackViewController *)(parentResponder()); m_typeParameterController.setRecord(m_record); stack->push(&m_typeParameterController); return true; } -#if FUNCTION_COLOR_CHOICE - if (selectedRowIndex == 2+hasAdditionalRow) { - -#else - if (selectedRowIndex == 1+hasAdditionalRow) { -#endif + if (selectedRowIndex == 1+hasAdditionalRow || selectedRowIndex == 2+hasAdditionalRow) { return handleEnterOnRow(selectedRowIndex-hasAdditionalRow-1); } -#if FUNCTION_COLOR_CHOICE if (selectedRowIndex == 3+hasAdditionalRow) { -#else - if (selectedRowIndex == 2+hasAdditionalRow) { -#endif App::app()->localContext()->resetCache(); return handleEnterOnRow(selectedRowIndex-hasAdditionalRow-1); } diff --git a/apps/sequence/list/list_parameter_controller.h b/apps/sequence/list/list_parameter_controller.h index 911630a0c8e..06166e8b0b7 100644 --- a/apps/sequence/list/list_parameter_controller.h +++ b/apps/sequence/list/list_parameter_controller.h @@ -25,11 +25,7 @@ class ListParameterController : public Shared::ListParameterController, public S HighlightCell * reusableCell(int index, int type) override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; private: -#if FUNCTION_COLOR_CHOICE constexpr static int k_totalNumberOfCell = 5; -#else - constexpr static int k_totalNumberOfCell = 4; -#endif int totalNumberOfCells() const override; Shared::Sequence * sequence() { return static_cast(function().pointer()); } bool hasInitialRankRow() const; diff --git a/apps/sequence/list/type_parameter_controller.cpp b/apps/sequence/list/type_parameter_controller.cpp index e7ad0c56e9d..90cee0d7ccc 100644 --- a/apps/sequence/list/type_parameter_controller.cpp +++ b/apps/sequence/list/type_parameter_controller.cpp @@ -17,7 +17,7 @@ TypeParameterController::TypeParameterController(Responder * parentResponder, Li ViewController(parentResponder), m_explicitCell(&m_selectableTableView, I18n::Message::Explicit, cellLayout), m_singleRecurrenceCell(&m_selectableTableView, I18n::Message::SingleRecurrence, cellLayout), - m_doubleRecurenceCell(&m_selectableTableView, I18n::Message::DoubleRecurrence, cellLayout), + m_doubleRecurrenceCell(&m_selectableTableView, I18n::Message::DoubleRecurrence, cellLayout), m_layouts{}, m_selectableTableView(this), m_record(), @@ -100,7 +100,7 @@ int TypeParameterController::numberOfRows() const { HighlightCell * TypeParameterController::reusableCell(int index) { assert(index >= 0); assert(index < k_totalNumberOfCell); - HighlightCell * cells[] = {&m_explicitCell, &m_singleRecurrenceCell, &m_doubleRecurenceCell}; + HighlightCell * cells[] = {&m_explicitCell, &m_singleRecurrenceCell, &m_doubleRecurrenceCell}; return cells[index]; } diff --git a/apps/sequence/list/type_parameter_controller.h b/apps/sequence/list/type_parameter_controller.h index f6bf68862fc..f4d18d3ac26 100644 --- a/apps/sequence/list/type_parameter_controller.h +++ b/apps/sequence/list/type_parameter_controller.h @@ -36,7 +36,7 @@ class TypeParameterController : public ViewController, public SimpleListViewData constexpr static int k_totalNumberOfCell = 3; ExpressionTableCellWithPointer m_explicitCell; ExpressionTableCellWithPointer m_singleRecurrenceCell; - ExpressionTableCellWithPointer m_doubleRecurenceCell; + ExpressionTableCellWithPointer m_doubleRecurrenceCell; Poincare::Layout m_layouts[k_totalNumberOfCell]; SelectableTableView m_selectableTableView; Ion::Storage::Record m_record; diff --git a/apps/sequence/test/sequence.cpp b/apps/sequence/test/sequence.cpp index fb729ab14c1..3a390d6a651 100644 --- a/apps/sequence/test/sequence.cpp +++ b/apps/sequence/test/sequence.cpp @@ -47,7 +47,7 @@ void check_sequences_defined_by(double result[MaxNumberOfSequences][10], Sequenc } } store->removeAll(); - /* The store is a global variable that has been contructed through + /* The store is a global variable that has been constructed through * GlobalContext::sequenceStore singleton. It won't be destructed. However, * we need to make sure that the pool is empty between quiz_cases. */ store->tidy(); diff --git a/apps/settings/Makefile b/apps/settings/Makefile index 588b95c2f1c..5380cec810c 100644 --- a/apps/settings/Makefile +++ b/apps/settings/Makefile @@ -11,6 +11,7 @@ app_settings_src = $(addprefix apps/settings/,\ main_controller_prompt_update.cpp:+update \ sub_menu/about_controller.cpp \ sub_menu/accessibility_controller.cpp \ + sub_menu/code_options_controller.cpp \ sub_menu/about_controller_official.cpp:+official \ sub_menu/about_controller_non_official.cpp:-official \ sub_menu/exam_mode_controller_official.cpp:+official \ @@ -24,6 +25,8 @@ app_settings_src = $(addprefix apps/settings/,\ sub_menu/contributors_controller.cpp \ sub_menu/math_options_controller.cpp \ sub_menu/selectable_view_with_messages.cpp \ + sub_menu/external_controller.cpp \ + sub_menu/brightness_controller.cpp\ ) SFLAGS += -DOMEGA_STATE="$(OMEGA_STATE)" @@ -31,6 +34,6 @@ SFLAGS += -DOMEGA_STATE="$(OMEGA_STATE)" app_settings_src += $(app_settings_test_src) apps_src += $(app_settings_src) -i18n_files += $(call i18n_without_universal_for,settings/base) +i18n_files += $(call i18n_with_universal_for,settings/base) $(eval $(call depends_on_image,apps/settings/app.cpp,apps/settings/settings_icon.png)) diff --git a/apps/settings/base.de.i18n b/apps/settings/base.de.i18n index 5c00526fc3e..8f8cd5898b2 100644 --- a/apps/settings/base.de.i18n +++ b/apps/settings/base.de.i18n @@ -32,7 +32,9 @@ Real = "Reell " Cartesian = "Kartesisch " Polar = "Polar " Brightness = "Helligkeit" +BrightnessSettings = "Helligkeitseinstellungen" SoftwareVersion = "Epsilon version" +UpsilonVersion = "Upsilon version" OmegaVersion = "Omega version" Username = "Name" MicroPythonVersion = "µPythonversion" @@ -43,6 +45,7 @@ SerialNumber = "Seriennummer" UpdatePopUp = "Erinnerung: Aktualisierung" BetaPopUp = "Erinnerung: Vorabversion" Contributors = "Mitwirkende" +Battery = "Batteriestatus" Accessibility = "Barrierefreiheit" AccessibilityInvertColors = "Farbumkehrung" AccessibilityMagnify = "Lupe" @@ -60,11 +63,21 @@ SymbolFunction = "Ausdrucksformat " SymbolDefaultFunction = "Standardl " SymbolArgFunction = "Leer " SymbolArgDefaultFunction = "Argument " -PythonFont = "Python Schriftart" MemUse = "Speicher" DateTime = "Datum/Uhrzeit" +ExternalApps = "Externe Apps" ActivateClock = "Uhr aktivieren" Date = "Datum" Time = "Uhrzeit" RTCWarning1 = "Das Aktivieren der Uhr verkürzt die" RTCWarning2 = "Akkulaufzeit im Bereitschaftsmodus." +SyntaxHighlighting = "Syntaxhervorhebung" +Normal = "Normal" +IdleTimeBeforeDimming = "Abdunkeln nach (s)" +IdleTimeBeforeSuspend = "Anhalten nach (s)" +BrightnessShortcut = "Tastenkombinationsschritte" +ExtAppWrite = "Schreiben aktiviert" +ExtAppWriteExplanation1 = "Standardmäßig externe Anwendungen" +ExtAppWriteExplanation2 = "kann nicht in den Speicher schreiben" +ExtAppWriteExplanation3 = "Flash (dauerhaft) Ihres Rechners." +ExtAppEnabled = "Aufstecken" diff --git a/apps/settings/base.en.i18n b/apps/settings/base.en.i18n index b5d07690485..56f15205480 100644 --- a/apps/settings/base.en.i18n +++ b/apps/settings/base.en.i18n @@ -32,7 +32,9 @@ Real = "Real " Cartesian = "Cartesian " Polar = "Polar " Brightness = "Brightness" +BrightnessSettings = "Brightness settings" SoftwareVersion = "Epsilon version" +UpsilonVersion = "Upsilon version" OmegaVersion = "Omega version" Username = "Name" MicroPythonVersion = "µPython version" @@ -42,6 +44,7 @@ SmallFont = "Small " SerialNumber = "Serial number" UpdatePopUp = "Update pop-up" BetaPopUp = "Beta pop-up" +Battery = "Battery" Contributors = "Contributors" Accessibility = "Accessibility" AccessibilityInvertColors = "Invert colors" @@ -60,11 +63,21 @@ SymbolFunction = "Expression format " SymbolDefaultFunction = "Default " SymbolArgFunction = "Empty " SymbolArgDefaultFunction = "Argument " -PythonFont = "Python Font" MemUse = "Memory" DateTime = "Date/time" +ExternalApps = "External Apps" ActivateClock = "Activate clock" Date = "Date" Time = "Time" RTCWarning1 = "Enabling the clock drains the battery faster" RTCWarning2 = "when the calculator is powered off." +SyntaxHighlighting = "Syntax Highlighting" +Normal = "Normal" +IdleTimeBeforeDimming = "Dim after (s)" +IdleTimeBeforeSuspend = "Suspend after (s)" +BrightnessShortcut = "Shortcut steps" +ExtAppWrite = "Write allowed" +ExtAppWriteExplanation1 = "By default, external applications" +ExtAppWriteExplanation2 = "cannot write to memory" +ExtAppWriteExplanation3 = "flash (persistent) of your calculator." +ExtAppEnabled = "Pin up" diff --git a/apps/settings/base.es.i18n b/apps/settings/base.es.i18n index 944709570f0..42ce777f24e 100644 --- a/apps/settings/base.es.i18n +++ b/apps/settings/base.es.i18n @@ -32,7 +32,9 @@ Real = "Real " Cartesian = "Binómica " Polar = "Polar " Brightness = "Brillo" +BrightnessSettings = "Configuración de brillo" SoftwareVersion = "Versión de Epsilon" +UpsilonVersion = "Versión de Upsilon" OmegaVersion = "Versión de Omega" Username = "Apellido" MicroPythonVersion = "Version de µPython" @@ -43,6 +45,7 @@ SerialNumber = "Número serie" UpdatePopUp = "Pop-up de actualización" BetaPopUp = "Beta pop-up" Contributors = "Contribuyentes" +Battery = "Batería" Accessibility = "Accesibilidad" AccessibilityInvertColors = "Colores invertidos" AccessibilityMagnify = "Lupa" @@ -60,11 +63,21 @@ SymbolFunction = "Formato expresión " SymbolDefaultFunction = "Defecto " SymbolArgFunction = "Vacío " SymbolArgDefaultFunction = "Argumento " -PythonFont = "Fuente Python" MemUse = "Memoria" DateTime = "Fecha/Hora" +ExternalApps = "Aplicaciones externas" ActivateClock = "Activar el reloj" Date = "Fecha" Time = "Hora" RTCWarning1 = "Activar el reloj gasta la batería más rápido" RTCWarning2 = "cuando la calculadora está apagada." +SyntaxHighlighting = "Resaltado de sintaxis" +Normal = "Normal" +IdleTimeBeforeDimming = "Oscurecer después de (s)" +IdleTimeBeforeSuspend = "Suspender después de (s)" +BrightnessShortcut = "Pasos de acceso directo" +ExtAppWrite = "Escritura habilitada" +ExtAppWriteExplanation1 = "Por defecto, las aplicaciones externas" +ExtAppWriteExplanation2 = "no se puede escribir en la memoria" +ExtAppWriteExplanation3 = "flash (persistente) de su calculadora." +ExtAppEnabled = "Fijar" diff --git a/apps/settings/base.fr.i18n b/apps/settings/base.fr.i18n index ac76c7dca0d..676e64687ca 100644 --- a/apps/settings/base.fr.i18n +++ b/apps/settings/base.fr.i18n @@ -32,7 +32,9 @@ Real = "Réel " Cartesian = "Algébrique " Polar = "Exponentielle " Brightness = "Luminosité" +BrightnessSettings = "Paramètres de luminosité" SoftwareVersion = "Version d'Epsilon" +UpsilonVersion = "Version d'Upsilon" OmegaVersion = "Version d'Omega" Username = "Nom" MicroPythonVersion = "Version de µPython" @@ -43,6 +45,7 @@ SerialNumber = "Numéro de série" UpdatePopUp = "Rappel de mise à jour" BetaPopUp = "Rappel de version bêta" Contributors = "Contributeurs" +Battery = "Batterie" Accessibility = "Accessibilité" AccessibilityInvertColors = "Inverser couleurs" AccessibilityMagnify = "Loupe" @@ -60,11 +63,21 @@ SymbolFunction = "Format expression " SymbolDefaultFunction = "Défaut " SymbolArgFunction = "Vide " SymbolArgDefaultFunction = "Arguments " -PythonFont = "Police Python" MemUse = "Mémoire" DateTime = "Date/heure" +ExternalApps = "Applications externes" ActivateClock = "Activer horloge" Date = "Date" Time = "Heure" RTCWarning1 = "Activer l'horloge décharge la batterie plus" RTCWarning2 = "vite quand la calculatrice est éteinte." +SyntaxHighlighting = "Coloration syntaxique" +Normal = "Normale" +IdleTimeBeforeDimming = "Assombrir après (s)" +IdleTimeBeforeSuspend = "Éteindre après (s)" +BrightnessShortcut = "Étapes du raccourci" +ExtAppWrite = "Écriture autorisée" +ExtAppWriteExplanation1 = "Par défaut, les applications externes" +ExtAppWriteExplanation2 = "ne peuvent écrire dans la mémoire" +ExtAppWriteExplanation3 = "flash (persistante) de votre calculatrice." +ExtAppEnabled = "Afficher" diff --git a/apps/settings/base.hu.i18n b/apps/settings/base.hu.i18n index 19ed3a67f63..a46e8cddaa0 100644 --- a/apps/settings/base.hu.i18n +++ b/apps/settings/base.hu.i18n @@ -1,70 +1,83 @@ -SettingsApp = "Beállítások" -SettingsAppCapital = "BEÁLLÍTÁSOK" -AngleUnit = "Szögmérö" -DisplayMode = "Eredmény formátuma" -EditionMode = "Írás formátuma" -EditionLinear = "Lineáris" -Edition2D = "Természetes" -ComplexFormat = "Komplex formátum" -ExamMode = "Vizsga mód" -ExamModeActive = "A vizsgamód újraaktiválása" -ToDeactivateExamMode1 = "a vizsga mód kikapcsoláshoz" -ToDeactivateExamMode2 = "csatlakoztassa a számológépet a számítógéphez" -ToDeactivateExamMode3 = "vagy egy konnektorhoz." -# --------------------- Please do not edit these messages --------------------- -ExamModeWarning1 = "Vigyázat: a használt szoftver nem" -ExamModeWarning2 = "hivatalos, Numworks nem garantálja" -ExamModeWarning3 = "a vizsgálati mód megfelelőségét." -AboutWarning1 = "Vigyázat: a használt szoftver" -AboutWarning2 = "nem hivatalos. A NumWorks nem" -AboutWarning3 = "vállal felelösséget az" -AboutWarning4 = "esetleges károkért." -# ----------------------------------------------------------------------------- -About = "Apropó" -Degrees = "Fokok " -Gradians = "Gradiens " -Radian = "Radián " -Decimal = "Tizedes " -Scientific = "Tudományos " -Engineering = "Mérnökség " -SignificantFigures = "Tizedes számok " -Real = "Valódi " -Cartesian = "Kartéziánus " -Polar = "Poláris " -Brightness = "Fényerö" -SoftwareVersion = "Epsilon verzió" -OmegaVersion = "Omega verzió" -Username = "Felhasználónév" -MicroPythonVersion = "µPython verzió" -FontSizes = "Python betü méret" -LargeFont = "Nagy " -SmallFont = "Kicsi " -SerialNumber = "Sorozatszám" -UpdatePopUp = "Frissítés figyelmeztetés" -BetaPopUp = "Béta figyelmeztetés" -Contributors = "Közremüködök" -Accessibility = "Több vizuális beállitások" -AccessibilityInvertColors = "Invertált színek" -AccessibilityMagnify = "Nagyító" -AccessibilityGamma = "Gamma korrekció" -AccessibilityGammaRed = "Piros gamma" -AccessibilityGammaGreen = "Zöld gamma" -AccessibilityGammaBlue = "Kék gamma" -MathOptions = "Matematikai beállítások" -SymbolMultiplication = "Szorzás" -SymbolMultiplicationCross = "Kereszt " -SymbolMultiplicationMiddleDot = "Pont " -SymbolMultiplicationStar = "Csillag " -SymbolMultiplicationAutoSymbol = "Automatitus " -SymbolFunction = "Kifejezés " -SymbolDefaultFunction = "Alap " -SymbolArgFunction = "Üres " -SymbolArgDefaultFunction = "Argumentummal " -PythonFont = "Python Betütipus" -MemUse = "Memória" -DateTime = "Dátum/óra" -ActivateClock = "Óra bekapcsolása" -Date = "Datum" -Time = "Óra" -RTCWarning1 = "Amikor a számológép alvómódban van, az óra" -RTCWarning2 = "használása az elemet gyorsabban meríti ki." +SettingsApp = "Beállítások" +SettingsAppCapital = "BEÁLLÍTÁSOK" +AngleUnit = "Szögmérö" +DisplayMode = "Eredmény formátuma" +EditionMode = "Írás formátuma" +EditionLinear = "Lineáris" +Edition2D = "Természetes" +ComplexFormat = "Komplex formátum" +ExamMode = "Vizsga mód" +ExamModeActive = "A vizsgamód újraaktiválása" +ToDeactivateExamMode1 = "a vizsga mód kikapcsoláshoz" +ToDeactivateExamMode2 = "csatlakoztassa a számológépet a számítógéphez" +ToDeactivateExamMode3 = "vagy egy konnektorhoz." +# --------------------- Please do not edit these messages --------------------- +ExamModeWarning1 = "Vigyázat: a használt szoftver nem" +ExamModeWarning2 = "hivatalos, Numworks nem garantálja" +ExamModeWarning3 = "a vizsgálati mód megfelelőségét." +AboutWarning1 = "Vigyázat: a használt szoftver" +AboutWarning2 = "nem hivatalos. A NumWorks nem" +AboutWarning3 = "vállal felelösséget az" +AboutWarning4 = "esetleges károkért." +# ----------------------------------------------------------------------------- +About = "Apropó" +Degrees = "Fokok " +Gradians = "Gradiens " +Radian = "Radián " +Decimal = "Tizedes " +Scientific = "Tudományos " +Engineering = "Mérnökség " +SignificantFigures = "Tizedes számok " +Real = "Valódi " +Cartesian = "Kartéziánus " +Polar = "Poláris " +Brightness = "Fényerö" +BrightnessSettings = "Fényerő beállításai" +SoftwareVersion = "Epsilon verzió" +UpsilonVersion = "Upsilon verzió" +OmegaVersion = "Omega verzió" +Username = "Felhasználónév" +MicroPythonVersion = "µPython verzió" +FontSizes = "Python betü méret" +LargeFont = "Nagy " +SmallFont = "Kicsi " +SerialNumber = "Sorozatszám" +UpdatePopUp = "Frissítés figyelmeztetés" +BetaPopUp = "Béta figyelmeztetés" +Contributors = "Közremüködök" +Battery = "Akkumulátor" +Accessibility = "Több vizuális beállitások" +AccessibilityInvertColors = "Invertált színek" +AccessibilityMagnify = "Nagyító" +AccessibilityGamma = "Gamma korrekció" +AccessibilityGammaRed = "Piros gamma" +AccessibilityGammaGreen = "Zöld gamma" +AccessibilityGammaBlue = "Kék gamma" +MathOptions = "Matematikai beállítások" +SymbolMultiplication = "Szorzás" +SymbolMultiplicationCross = "Kereszt " +SymbolMultiplicationMiddleDot = "Pont " +SymbolMultiplicationStar = "Csillag " +SymbolMultiplicationAutoSymbol = "Automatitus " +SymbolFunction = "Kifejezés " +SymbolDefaultFunction = "Alap " +SymbolArgFunction = "Üres " +SymbolArgDefaultFunction = "Argumentummal " +MemUse = "Memória" +DateTime = "Dátum/óra" +ExternalApps = "Külső alkalmazások" +ActivateClock = "Óra bekapcsolása" +Date = "Datum" +Time = "Óra" +RTCWarning1 = "Amikor a számológép alvómódban van, az óra" +RTCWarning2 = "használása az elemet gyorsabban meríti ki." +SyntaxHighlighting = "Szintaxis kiemelés" +Normal = "Normale" +IdleTimeBeforeDimming = "Assombrir après (s)" +IdleTimeBeforeSuspend = "Éteindre après (s)" +BrightnessShortcut = "Parancsikon lépések" +ExtAppWrite = "Írás engedélyezve" +ExtAppWriteExplanation1 = "Alapértelmezés szerint külső alkalmazások" +ExtAppWriteExplanation2 = "nem tud a memóriába írni" +ExtAppWriteExplanation3 = "villog (tartósan) a számológép." +ExtAppEnabled = "Feltűz" diff --git a/apps/settings/base.it.i18n b/apps/settings/base.it.i18n index 5c4dcc0a47c..76b7090850f 100644 --- a/apps/settings/base.it.i18n +++ b/apps/settings/base.it.i18n @@ -32,39 +32,52 @@ Real = "Reale " Cartesian = "Algebrico " Polar = "Esponenziale " Brightness = "Luminosità" -SoftwareVersion = "Epsilon version" -OmegaVersion = "Omega version" -Username = "Name" -MicroPythonVersion = "µPython version" +BrightnessSettings = "Impostazioni luminosità" +SoftwareVersion = "Versione di Epsilon" +UpsilonVersion = "Versione di Upsilon" +OmegaVersion = "Versione di Omega" +Username = "Nome" +MicroPythonVersion = "Versione µPython" FontSizes = "Carattere Python" LargeFont = "Grande " SmallFont = "Piccolo " SerialNumber = "Numero di serie" UpdatePopUp = "Promemoria aggiornamento" BetaPopUp = "Promemoria beta" -Contributors = "Contributors" -Accessibility = "Accessibility" -AccessibilityInvertColors = "Invert colors" -AccessibilityMagnify = "Magnify" -AccessibilityGamma = "Gamma correction" -AccessibilityGammaRed = "Red gamma" -AccessibilityGammaGreen = "Green gamma" -AccessibilityGammaBlue = "Blue gamma" -MathOptions = "Math options" -SymbolMultiplication = "Multiply" -SymbolMultiplicationCross = "Cross " -SymbolMultiplicationMiddleDot = "Dot " -SymbolMultiplicationStar = "Star " -SymbolMultiplicationAutoSymbol = "Auto " -SymbolFunction = "Expression format " -SymbolDefaultFunction = "Default " -SymbolArgFunction = "Empty " -SymbolArgDefaultFunction = "Argument " -PythonFont = "Python Font" -MemUse = "Memory" -DateTime = "Date/time" -ActivateClock = "Activate clock" -Date = "Date" -Time = "Time" -RTCWarning1 = "Enabling the clock drains the battery faster" -RTCWarning2 = "when the calculator is powered off." +Contributors = "Contributori" +Battery = "Batteria" +Accessibility = "Accessibilità" +AccessibilityInvertColors = "Inversione colori" +AccessibilityMagnify = "Ingrandimento" +AccessibilityGamma = "Correzione gamma" +AccessibilityGammaRed = "Gamma del rosso" +AccessibilityGammaGreen = "Gamma del verde" +AccessibilityGammaBlue = "Gamma del blu" +MathOptions = "Opzioni matematica" +SymbolMultiplication = "Simbolo moltiplicaz." +SymbolMultiplicationCross = "Croce " +SymbolMultiplicationMiddleDot = "Punto " +SymbolMultiplicationStar = "Stella " +SymbolMultiplicationAutoSymbol = "Automatico " +SymbolFunction = "Formato espressioni " +SymbolDefaultFunction = "Predefinito " +SymbolArgFunction = "Vuoto " +SymbolArgDefaultFunction = "Argomento " +MemUse = "Memoria" +DateTime = "Data/ora" +ExternalApps = "App esterne" +ActivateClock = "Attiva orologio" +Date = "Data" +Time = "Ora" +RTCWarning1 = "Attivare l'orologio drena la batteria più" +RTCWarning2 = "velocemente quando la calcolatrice è spenta." +SyntaxHighlighting = "Evidenz. sintassi" +Normal = "Normale" +IdleTimeBeforeDimming = "Scurisci dopo (s)" +IdleTimeBeforeSuspend = "Sospendi dopo (s)" +BrightnessShortcut = "Livelli scelta rapida" +ExtAppWrite = "Scrittura abilitata" +ExtAppWriteExplanation1 = "Per impostazione predefinita, le app esterne" +ExtAppWriteExplanation2 = "non possono scrivere in memoria flash" +ExtAppWriteExplanation3 = "(persistente) della calcolatrice." +ExtAppEnabled = "Affiggere" diff --git a/apps/settings/base.nl.i18n b/apps/settings/base.nl.i18n index 75939d6a877..f26e546c402 100644 --- a/apps/settings/base.nl.i18n +++ b/apps/settings/base.nl.i18n @@ -32,7 +32,9 @@ Real = "Reëel " Cartesian = "Cartesisch " Polar = "Polair " Brightness = "Helderheid" +BrightnessSettings = "Helderheidsinstellingen" SoftwareVersion = "Epsilon version" +UpsilonVersion = "Upsilon version" OmegaVersion = "Omega version" Username = "Name" MicroPythonVersion = "µPython version" @@ -43,6 +45,7 @@ SerialNumber = "Serienummer" UpdatePopUp = "Update pop-up" BetaPopUp = "Bèta pop-up" Contributors = "Contributors" +Battery = "Batterij" Accessibility = "Accessibility" AccessibilityInvertColors = "Invert colors" AccessibilityMagnify = "Magnify" @@ -60,11 +63,21 @@ SymbolFunction = "Expression format " SymbolDefaultFunction = "Default " SymbolArgFunction = "Empty " SymbolArgDefaultFunction = "Argument " -PythonFont = "Python Font" MemUse = "Memory" DateTime = "Date/time" +ExternalApps = "Externe apps" ActivateClock = "Activate clock" Date = "Date" Time = "Time" RTCWarning1 = "Enabling the clock drains the battery faster" RTCWarning2 = "when the calculator is powered off." +SyntaxHighlighting = "Syntax Highlighting" +Normal = "Normaal" +IdleTimeBeforeDimming = "Donkerder maken na (s)" +IdleTimeBeforeSuspend = "Suspend after (s)" +BrightnessShortcut = "Snelkoppelingsstappen" +ExtAppWrite = "Schrijven ingeschakeld" +ExtAppWriteExplanation1 = "Standaard zijn externe toepassingen" +ExtAppWriteExplanation2 = "kan niet naar het geheugen schrijven" +ExtAppWriteExplanation3 = "flash (aanhoudend) van uw rekenmachine." +ExtAppEnabled = "Vastpinnen" diff --git a/apps/settings/base.pt.i18n b/apps/settings/base.pt.i18n index f33006f4ad6..950bc992718 100644 --- a/apps/settings/base.pt.i18n +++ b/apps/settings/base.pt.i18n @@ -32,7 +32,9 @@ Real = "Real " Cartesian = "Cartesiana " Polar = "Polar " Brightness = "Brilho" +BrightnessSettings = "Configurações de brilho" SoftwareVersion = "Versão do Epsilon" +UpsilonVersion = "Versão do Upsilon" OmegaVersion = "Versão do Omega" Username = "Nome" MicroPythonVersion = "Versao do µPython" @@ -43,6 +45,7 @@ SerialNumber = "Número serie" UpdatePopUp = "Alertas de atualização" BetaPopUp = "Beta pop-up" Contributors = "Contribuidores" +Battery = "Bateria" Accessibility = "Acessibilidade" AccessibilityInvertColors = "Cores invertidas" AccessibilityMagnify = "Lupa" @@ -60,11 +63,21 @@ SymbolFunction = "Formato expressão " SymbolDefaultFunction = "Padrão " SymbolArgFunction = "Vazio " SymbolArgDefaultFunction = "Argumento " -PythonFont = "Fonte Python" MemUse = "Memória" DateTime = "Date/time" +ExternalApps = "Aplicativos externos" ActivateClock = "Activate clock" Date = "Date" Time = "Time" RTCWarning1 = "Enabling the clock drains the battery faster" RTCWarning2 = "when the calculator is powered off." +SyntaxHighlighting = "Destaque da sintaxe" +Normal = "Normal" +IdleTimeBeforeDimming = "Diminuir depois (s)" +IdleTimeBeforeSuspend = "Suspender depois (s)" +BrightnessShortcut = "Passos de atalho" +ExtAppWrite = "Gravação ativada" +ExtAppWriteExplanation1 = "Por padrão, aplicativos externos" +ExtAppWriteExplanation2 = "não pode gravar na memória" +ExtAppWriteExplanation3 = "flash (persistente) de sua calculadora." +ExtAppEnabled = "Pôster" diff --git a/apps/settings/base.universal.i18n b/apps/settings/base.universal.i18n new file mode 100644 index 00000000000..210e673fbaf --- /dev/null +++ b/apps/settings/base.universal.i18n @@ -0,0 +1 @@ +UsbSetting = "USB" diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index 6b65f15d3ea..7e71771f93a 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -15,25 +15,35 @@ constexpr SettingsMessageTree s_modelFloatDisplayModeChildren[4] = {SettingsMess constexpr SettingsMessageTree s_modelComplexFormatChildren[3] = {SettingsMessageTree(I18n::Message::Real), SettingsMessageTree(I18n::Message::Cartesian), SettingsMessageTree(I18n::Message::Polar)}; constexpr SettingsMessageTree s_modelDateTimeChildren[3] = {SettingsMessageTree(I18n::Message::ActivateClock), SettingsMessageTree(I18n::Message::Date), SettingsMessageTree(I18n::Message::Time)}; constexpr SettingsMessageTree s_symbolChildren[4] = {SettingsMessageTree(I18n::Message::SymbolMultiplicationCross),SettingsMessageTree(I18n::Message::SymbolMultiplicationMiddleDot),SettingsMessageTree(I18n::Message::SymbolMultiplicationStar),SettingsMessageTree(I18n::Message::SymbolMultiplicationAutoSymbol)}; +constexpr SettingsMessageTree s_externalChildren[2] = {SettingsMessageTree(I18n::Message::ExtAppWrite), SettingsMessageTree(I18n::Message::ExtAppEnabled)}; constexpr SettingsMessageTree s_symbolFunctionChildren[3] = {SettingsMessageTree(I18n::Message::SymbolDefaultFunction), SettingsMessageTree(I18n::Message::SymbolArgDefaultFunction), SettingsMessageTree(I18n::Message::SymbolArgFunction)}; constexpr SettingsMessageTree s_modelMathOptionsChildren[6] = {SettingsMessageTree(I18n::Message::AngleUnit, s_modelAngleChildren), SettingsMessageTree(I18n::Message::DisplayMode, s_modelFloatDisplayModeChildren), SettingsMessageTree(I18n::Message::EditionMode, s_modelEditionModeChildren), SettingsMessageTree(I18n::Message::SymbolFunction, s_symbolFunctionChildren), SettingsMessageTree(I18n::Message::ComplexFormat, s_modelComplexFormatChildren), SettingsMessageTree(I18n::Message::SymbolMultiplication, s_symbolChildren)}; -constexpr SettingsMessageTree s_modelFontChildren[2] = {SettingsMessageTree(I18n::Message::LargeFont), SettingsMessageTree(I18n::Message::SmallFont)}; +constexpr SettingsMessageTree s_brightnessChildren[4] = {SettingsMessageTree(I18n::Message::Brightness), SettingsMessageTree(I18n::Message::IdleTimeBeforeDimming), SettingsMessageTree(I18n::Message::IdleTimeBeforeSuspend), SettingsMessageTree(I18n::Message::BrightnessShortcut)}; constexpr SettingsMessageTree s_accessibilityChildren[6] = {SettingsMessageTree(I18n::Message::AccessibilityInvertColors), SettingsMessageTree(I18n::Message::AccessibilityMagnify),SettingsMessageTree(I18n::Message::AccessibilityGamma),SettingsMessageTree(I18n::Message::AccessibilityGammaRed),SettingsMessageTree(I18n::Message::AccessibilityGammaGreen),SettingsMessageTree(I18n::Message::AccessibilityGammaBlue)}; -constexpr SettingsMessageTree s_contributorsChildren[23] = {SettingsMessageTree(I18n::Message::Developers), SettingsMessageTree(I18n::Message::QuentinGuidee), SettingsMessageTree(I18n::Message::JoachimLeFournis), SettingsMessageTree(I18n::Message::MaximeFriess), SettingsMessageTree(I18n::Message::JeanBaptisteBoric), SettingsMessageTree(I18n::Message::SandraSimmons), SettingsMessageTree(I18n::Message::David), SettingsMessageTree(I18n::Message::DamienNicolet), SettingsMessageTree(I18n::Message::EvannDreumont), SettingsMessageTree(I18n::Message::SzaboLevente), SettingsMessageTree(I18n::Message::VenceslasDuet), SettingsMessageTree(I18n::Message::CharlotteThomas), SettingsMessageTree(I18n::Message::AntoninLoubiere), SettingsMessageTree(I18n::Message::CyprienMejat), SettingsMessageTree(I18n::Message::BetaTesters), SettingsMessageTree(I18n::Message::TimeoArnouts), SettingsMessageTree(I18n::Message::JulieC), SettingsMessageTree(I18n::Message::LelahelHideux), SettingsMessageTree(I18n::Message::Madil), SettingsMessageTree(I18n::Message::HilaireLeRoux), SettingsMessageTree(I18n::Message::HectorNussbaumer), SettingsMessageTree(I18n::Message::RaphaelDyda), SettingsMessageTree(I18n::Message::ThibautC)}; -constexpr SettingsMessageTree s_modelAboutChildren[8] = {SettingsMessageTree(I18n::Message::Username), SettingsMessageTree(I18n::Message::SoftwareVersion), SettingsMessageTree(I18n::Message::OmegaVersion), SettingsMessageTree(I18n::Message::MicroPythonVersion), SettingsMessageTree(I18n::Message::MemUse), SettingsMessageTree(I18n::Message::SerialNumber), SettingsMessageTree(I18n::Message::FccId), SettingsMessageTree(I18n::Message::Contributors, s_contributorsChildren)}; +constexpr SettingsMessageTree s_contributorsChildren[18] = {SettingsMessageTree(I18n::Message::LaurianFournier), SettingsMessageTree(I18n::Message::YannCouturier), SettingsMessageTree(I18n::Message::DavidLuca), SettingsMessageTree(I18n::Message::LoicE), SettingsMessageTree(I18n::Message::VictorKretz), SettingsMessageTree(I18n::Message::QuentinGuidee), SettingsMessageTree(I18n::Message::JoachimLeFournis), SettingsMessageTree(I18n::Message::MaximeFriess), SettingsMessageTree(I18n::Message::JeanBaptisteBoric), SettingsMessageTree(I18n::Message::SandraSimmons), SettingsMessageTree(I18n::Message::David), SettingsMessageTree(I18n::Message::DamienNicolet), SettingsMessageTree(I18n::Message::EvannDreumont), SettingsMessageTree(I18n::Message::SzaboLevente), SettingsMessageTree(I18n::Message::VenceslasDuet), SettingsMessageTree(I18n::Message::CharlotteThomas), SettingsMessageTree(I18n::Message::AntoninLoubiere), SettingsMessageTree(I18n::Message::CyprienMejat)}; + +// Code Settings +#ifdef HAS_CODE +constexpr SettingsMessageTree s_codeChildren[3] = {SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Autocomplete), SettingsMessageTree(I18n::Message::SyntaxHighlighting)}; +#endif +constexpr SettingsMessageTree s_modelFontChildren[2] = {SettingsMessageTree(I18n::Message::LargeFont), SettingsMessageTree(I18n::Message::SmallFont)}; + +constexpr SettingsMessageTree s_modelAboutChildren[10] = {SettingsMessageTree(I18n::Message::Username), SettingsMessageTree(I18n::Message::UpsilonVersion), SettingsMessageTree(I18n::Message::OmegaVersion), SettingsMessageTree(I18n::Message::SoftwareVersion), SettingsMessageTree(I18n::Message::MicroPythonVersion), SettingsMessageTree(I18n::Message::Battery), SettingsMessageTree(I18n::Message::MemUse), SettingsMessageTree(I18n::Message::SerialNumber), SettingsMessageTree(I18n::Message::FccId), SettingsMessageTree(I18n::Message::Contributors, s_contributorsChildren)}; MainController::MainController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) : ViewController(parentResponder), - m_brightnessCell(I18n::Message::Default, KDFont::LargeFont), m_popUpCell(I18n::Message::Default, KDFont::LargeFont), m_selectableTableView(this), m_mathOptionsController(this, inputEventHandlerDelegate), + m_brightnessController(this, inputEventHandlerDelegate), m_localizationController(this, Metric::CommonTopMargin, LocalizationController::Mode::Language), m_accessibilityController(this), m_dateTimeController(this), + m_codeOptionsController(this), m_examModeController(this), m_aboutController(this), - m_preferencesController(this) + m_preferencesController(this), + m_externalController(this) { for (int i = 0; i < k_numberOfSimpleChevronCells; i++) { m_cells[i].setMessageFont(KDFont::LargeFont); @@ -53,32 +63,15 @@ void MainController::didBecomeFirstResponder() { bool MainController::handleEvent(Ion::Events::Event event) { GlobalPreferences * globalPreferences = GlobalPreferences::sharedGlobalPreferences(); - if (event == Ion::Events::BrightnessPlus || event == Ion::Events::BrightnessMinus){ - int delta = Ion::Backlight::MaxBrightness/GlobalPreferences::NumberOfBrightnessStates; - int direction = (event == Ion::Events::BrightnessPlus) ? Ion::Backlight::NumberOfStepsPerShortcut*delta : -delta*Ion::Backlight::NumberOfStepsPerShortcut; - GlobalPreferences::sharedGlobalPreferences()->setBrightnessLevel(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()+direction); - m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), 1); - return true; - } if (model()->childAtIndex(selectedRow())->numberOfChildren() == 0) { if (model()->childAtIndex(selectedRow())->label() == promptMessage()) { - if (event == Ion::Events::OK || event == Ion::Events::EXE) { + if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { globalPreferences->setShowPopUp(!globalPreferences->showPopUp()); m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); return true; } return false; } - if (model()->childAtIndex(selectedRow())->label() == I18n::Message::Brightness) { - if (event == Ion::Events::Right || event == Ion::Events::Left || event == Ion::Events::Plus || event == Ion::Events::Minus) { - int delta = Ion::Backlight::MaxBrightness/GlobalPreferences::NumberOfBrightnessStates; - int direction = (event == Ion::Events::Right || event == Ion::Events::Plus) ? delta : -delta; - globalPreferences->setBrightnessLevel(globalPreferences->brightnessLevel()+direction); - m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); - return true; - } - return false; - } if (model()->childAtIndex(selectedRow())->label() == I18n::Message::Language || model()->childAtIndex(selectedRow())->label() == I18n::Message::Country) { if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { m_localizationController.setMode(model()->childAtIndex(selectedRow())->label() == I18n::Message::Language ? LocalizationController::Mode::Language : LocalizationController::Mode::Country); @@ -97,12 +90,18 @@ bool MainController::handleEvent(Ion::Events::Event event) { subController = &m_examModeController; } else if (title == I18n::Message::About) { subController = &m_aboutController; + } else if (title == I18n::Message::BrightnessSettings) { + subController = &m_brightnessController; } else if (title == I18n::Message::Accessibility) { subController = &m_accessibilityController; } else if (title == I18n::Message::DateTime) { subController = &m_dateTimeController; } else if (title == I18n::Message::MathOptions) { subController = &m_mathOptionsController; + } else if (title == I18n::Message::CodeApp) { + subController = &m_codeOptionsController; + } else if (title == I18n::Message::ExternalApps) { + subController = &m_externalController; } else { subController = &m_preferencesController; } @@ -126,11 +125,7 @@ KDCoordinate MainController::rowHeight(int j) { } KDCoordinate MainController::cumulatedHeightFromIndex(int j) { - KDCoordinate height = j * rowHeight(0); - if (j > k_indexOfBrightnessCell) { - height += CellWithSeparator::k_margin; - } - return height; + return j * rowHeight(0); } int MainController::indexFromCumulatedHeight(KDCoordinate offsetY) { @@ -147,11 +142,8 @@ HighlightCell * MainController::reusableCell(int index, int type) { return &m_cells[index]; } assert(index == 0); - if (type == 2) { - return &m_popUpCell; - } - assert(type == 1); - return &m_brightnessCell; + assert(type == 2); + return &m_popUpCell; } int MainController::reusableCellCount(int type) { @@ -181,7 +173,7 @@ void MainController::willDisplayCellForIndex(HighlightCell * cell, int index) { myGauge->setLevel((float)globalPreferences->brightnessLevel()/(float)Ion::Backlight::MaxBrightness); return; } - MessageTableCell * myCell = (MessageTableCell *)cell; + MessageTableCell<> * myCell = (MessageTableCell<> *)cell; myCell->setMessage(title); if (model()->childAtIndex(index)->label() == I18n::Message::Language) { int index = (int)(globalPreferences->language()); @@ -202,9 +194,6 @@ void MainController::willDisplayCellForIndex(HighlightCell * cell, int index) { MessageTableCellWithChevronAndMessage * myTextCell = (MessageTableCellWithChevronAndMessage *)cell; int childIndex = -1; switch (model()->childAtIndex(index)->label()) { - case I18n::Message::FontSizes: - childIndex = GlobalPreferences::sharedGlobalPreferences()->font() == KDFont::LargeFont ? 0 : 1; - break; default: break; } diff --git a/apps/settings/main_controller.h b/apps/settings/main_controller.h index fd638a0cd9e..e78aff9a243 100644 --- a/apps/settings/main_controller.h +++ b/apps/settings/main_controller.h @@ -8,9 +8,12 @@ #include "sub_menu/accessibility_controller.h" #include "sub_menu/datetime_controller.h" #include "sub_menu/exam_mode_controller.h" +#include "sub_menu/code_options_controller.h" #include "sub_menu/localization_controller.h" #include "sub_menu/math_options_controller.h" #include "sub_menu/preferences_controller.h" +#include "sub_menu/external_controller.h" +#include "sub_menu/brightness_controller.h" namespace Settings { @@ -22,10 +25,15 @@ extern const Shared::SettingsMessageTree s_symbolChildren[4]; extern const Shared::SettingsMessageTree s_symbolFunctionChildren[3]; extern const Shared::SettingsMessageTree s_modelMathOptionsChildren[6]; extern const Shared::SettingsMessageTree s_modelFontChildren[2]; +extern const Shared::SettingsMessageTree s_codeChildren[3]; extern const Shared::SettingsMessageTree s_modelDateTimeChildren[3]; extern const Shared::SettingsMessageTree s_accessibilityChildren[6]; -extern const Shared::SettingsMessageTree s_contributorsChildren[23]; -extern const Shared::SettingsMessageTree s_modelAboutChildren[8]; +extern const Shared::SettingsMessageTree s_contributorsChildren[18]; +extern const Shared::SettingsMessageTree s_modelAboutChildren[10]; +extern const Shared::SettingsMessageTree s_usbProtectionChildren[2]; +extern const Shared::SettingsMessageTree s_usbProtectionLevelChildren[3]; +extern const Shared::SettingsMessageTree s_externalChildren[2]; +extern const Shared::SettingsMessageTree s_brightnessChildren[4]; extern const Shared::SettingsMessageTree s_model; class MainController : public ViewController, public ListViewDataSource, public SelectableTableViewDataSource { @@ -61,18 +69,20 @@ class MainController : public ViewController, public ListViewDataSource, public StackViewController * stackController() const; I18n::Message promptMessage() const; bool hasPrompt() const { return promptMessage() != I18n::Message::Default; } - constexpr static int k_numberOfSimpleChevronCells = 9; + constexpr static int k_numberOfSimpleChevronCells = 10; MessageTableCellWithChevronAndMessage m_cells[k_numberOfSimpleChevronCells]; - MessageTableCellWithGaugeWithSeparator m_brightnessCell; MessageTableCellWithSwitch m_popUpCell; SelectableTableView m_selectableTableView; MathOptionsController m_mathOptionsController; + BrightnessController m_brightnessController; LocalizationController m_localizationController; AccessibilityController m_accessibilityController; DateTimeController m_dateTimeController; + CodeOptionsController m_codeOptionsController; ExamModeController m_examModeController; AboutController m_aboutController; PreferencesController m_preferencesController; + ExternalController m_externalController; }; } diff --git a/apps/settings/main_controller_prompt_beta.cpp b/apps/settings/main_controller_prompt_beta.cpp index b462cc14b3a..0f48e7940ed 100644 --- a/apps/settings/main_controller_prompt_beta.cpp +++ b/apps/settings/main_controller_prompt_beta.cpp @@ -8,15 +8,18 @@ namespace Settings { constexpr SettingsMessageTree s_modelMenu[] = {SettingsMessageTree(I18n::Message::MathOptions, s_modelMathOptionsChildren), - SettingsMessageTree(I18n::Message::Brightness), + SettingsMessageTree(I18n::Message::BrightnessSettings, s_brightnessChildren), SettingsMessageTree(I18n::Message::DateTime, s_modelDateTimeChildren), - SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Language), SettingsMessageTree(I18n::Message::Country), SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), +#ifdef HAS_CODE + SettingsMessageTree(I18n::Message::CodeApp, s_codeChildren), +#endif SettingsMessageTree(I18n::Message::BetaPopUp), - SettingsMessageTree(I18n::Message::About, s_modelAboutChildren), - SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren)}; + SettingsMessageTree(I18n::Message::ExternalApps, s_externalChildren), + SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), + SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; constexpr SettingsMessageTree s_model = SettingsMessageTree(I18n::Message::SettingsApp, s_modelMenu); diff --git a/apps/settings/main_controller_prompt_none.cpp b/apps/settings/main_controller_prompt_none.cpp index 965fffe0706..46511e36091 100644 --- a/apps/settings/main_controller_prompt_none.cpp +++ b/apps/settings/main_controller_prompt_none.cpp @@ -1,21 +1,26 @@ -#include "main_controller.h" -#include "../exam_mode_configuration.h" #include +#include "../exam_mode_configuration.h" +#include "main_controller.h" + using namespace Shared; namespace Settings { constexpr SettingsMessageTree s_modelMenu[] = {SettingsMessageTree(I18n::Message::MathOptions, s_modelMathOptionsChildren), - SettingsMessageTree(I18n::Message::Brightness), - SettingsMessageTree(I18n::Message::DateTime, s_modelDateTimeChildren), - SettingsMessageTree(I18n::Message::Language), - SettingsMessageTree(I18n::Message::Country), - SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), - SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), - SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), - SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; + SettingsMessageTree(I18n::Message::BrightnessSettings, s_brightnessChildren), + SettingsMessageTree(I18n::Message::DateTime, s_modelDateTimeChildren), + SettingsMessageTree(I18n::Message::Language), + SettingsMessageTree(I18n::Message::Country), + SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), +#ifdef HAS_CODE + SettingsMessageTree(I18n::Message::CodeApp, s_codeChildren), +#endif + //SettingsMessageTree(I18n::Message::UsbSetting, s_usbProtectionChildren), + SettingsMessageTree(I18n::Message::ExternalApps, s_externalChildren), + SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), + SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; constexpr SettingsMessageTree s_model = SettingsMessageTree(I18n::Message::SettingsApp, s_modelMenu); diff --git a/apps/settings/main_controller_prompt_update.cpp b/apps/settings/main_controller_prompt_update.cpp index 3c74faffc1a..2deed3c3e8c 100644 --- a/apps/settings/main_controller_prompt_update.cpp +++ b/apps/settings/main_controller_prompt_update.cpp @@ -8,14 +8,17 @@ using namespace Shared; constexpr SettingsMessageTree s_modelMenu[] = {SettingsMessageTree(I18n::Message::MathOptions, s_modelMathOptionsChildren), - SettingsMessageTree(I18n::Message::Brightness), + SettingsMessageTree(I18n::Message::BrightnessSettings, s_brightnessChildren), SettingsMessageTree(I18n::Message::DateTime, s_modelDateTimeChildren), - SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Language), SettingsMessageTree(I18n::Message::Country), SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), +#ifdef HAS_CODE + SettingsMessageTree(I18n::Message::CodeApp, s_codeChildren), +#endif SettingsMessageTree(I18n::Message::UpdatePopUp), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren), + SettingsMessageTree(I18n::Message::ExternalApps, s_externalChildren), SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; constexpr SettingsMessageTree s_model = SettingsMessageTree(I18n::Message::SettingsApp, s_modelMenu); diff --git a/apps/settings/sub_menu/about_controller.cpp b/apps/settings/sub_menu/about_controller.cpp index 220a2c39647..dcae3211a61 100644 --- a/apps/settings/sub_menu/about_controller.cpp +++ b/apps/settings/sub_menu/about_controller.cpp @@ -1,11 +1,15 @@ #include "about_controller.h" #include "../../../python/src/py/mpconfig.h" +#include "poincare/division.h" #include #include #include #include +#include #include +#include + #define MP_STRINGIFY_HELPER(x) #x #define MP_STRINGIFY(x) MP_STRINGIFY_HELPER(x) @@ -13,6 +17,8 @@ #error This file expects OMEGA_STATE to be defined #endif + +using namespace Shared; namespace Settings { AboutController::AboutController(Responder * parentResponder) : @@ -21,7 +27,7 @@ AboutController::AboutController(Responder * parentResponder) : m_contributorsCell(KDFont::LargeFont, KDFont::SmallFont) //m_view(&m_selectableTableView) { - for (int i = 0; i < k_totalNumberOfCell; i++) { + for (int i = 0; i < k_totalNumberOfCell - 1; i++) { m_cells[i].setMessageFont(KDFont::LargeFont); m_cells[i].setAccessoryFont(KDFont::SmallFont); m_cells[i].setAccessoryTextColor(Palette::SecondaryText); @@ -32,7 +38,7 @@ bool AboutController::handleEvent(Ion::Events::Event event) { I18n::Message childLabel = m_messageTreeModel->childAtIndex(selectedRow()+(!hasUsernameCell()))->label(); /* We hide here the activation hardware test app: in the menu "about", by * clicking on '6' on the last row. */ - if ((event == Ion::Events::Six || event == Ion::Events::LowerT || event == Ion::Events::UpperT) && childLabel == I18n::Message::FccId) { + if ((event == Ion::Events::Six || event == Ion::Events::LowerT || event == Ion::Events::UpperT) && childLabel == I18n::Message::FccId && !GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { Container::activeApp()->displayModalViewController(&m_hardwareTestPopUpController, 0.f, 0.f, Metric::ExamPopUpTopMargin, Metric::PopUpRightMargin, Metric::ExamPopUpBottomMargin, Metric::PopUpLeftMargin); return true; } @@ -59,6 +65,15 @@ bool AboutController::handleEvent(Ion::Events::Event event) { } return true; } + if (childLabel == I18n::Message::UpsilonVersion) { + MessageTableCellWithBuffer * myCell = (MessageTableCellWithBuffer *)m_selectableTableView.selectedCell(); + if (strcmp(myCell->accessoryText(), Ion::upsilonVersion()) == 0) { + myCell->setAccessoryText(MP_STRINGIFY(OMEGA_STATE)); //Change for public/dev + return true; + } + myCell->setAccessoryText(Ion::upsilonVersion()); + return true; + } if (childLabel == I18n::Message::OmegaVersion) { MessageTableCellWithBuffer * myCell = (MessageTableCellWithBuffer *)m_selectableTableView.selectedCell(); if (strcmp(myCell->accessoryText(), Ion::omegaVersion()) == 0) { @@ -95,6 +110,32 @@ bool AboutController::handleEvent(Ion::Events::Event event) { return true; } + if(childLabel == I18n::Message::Battery){ + MessageTableCellWithBuffer * myCell = (MessageTableCellWithBuffer *)m_selectableTableView.selectedCell(); + char batteryLevel[5]; + if(strchr(myCell->accessoryText(), '%') == NULL) { + float voltage = (Ion::Battery::voltage() - 3.6) * 166; + if(voltage < 0.0) { + myCell->setAccessoryText("1%"); // We cheat... + return true; + } else if (voltage >= 100.0) { + myCell->setAccessoryText("100%"); + return true; + } else { + int batteryLen = Poincare::Integer((int) voltage).serialize(batteryLevel, 5); + batteryLevel[batteryLen] = '%'; + batteryLevel[batteryLen+1] = '\0'; + } + } + else { + int batteryLen = Poincare::Number::FloatNumber(Ion::Battery::voltage()).serialize(batteryLevel, 5, Poincare::Preferences::PrintFloatMode::Decimal, 3); + batteryLevel[batteryLen] = 'V'; + batteryLevel[batteryLen+1] = '\0'; + } + + myCell->setAccessoryText(batteryLevel); + return true; + } } return false; } @@ -108,8 +149,8 @@ int AboutController::numberOfRows() const { HighlightCell * AboutController::reusableCell(int index, int type) { assert(index >= 0); if (type == 0) { - assert(index < k_totalNumberOfCell-1-(!hasUsernameCell())); - return &m_cells[index+(!hasUsernameCell())]; + assert(index < k_totalNumberOfCell-1); + return &m_cells[index]; } assert(index == 0); return &m_contributorsCell; @@ -122,7 +163,7 @@ int AboutController::typeAtLocation(int i, int j) { int AboutController::reusableCellCount(int type) { switch (type) { case 0: - return k_totalNumberOfCell-1-(!hasUsernameCell()); + return k_totalNumberOfCell-1; case 1: return 1; default: @@ -138,7 +179,6 @@ bool AboutController::hasUsernameCell() const { void AboutController::willDisplayCellForIndex(HighlightCell * cell, int index) { int i = index + (!hasUsernameCell()); GenericSubController::willDisplayCellForIndex(cell, i); - assert(index >= 0 && index < k_totalNumberOfCell); if (m_messageTreeModel->childAtIndex(i)->label() == I18n::Message::Contributors) { MessageTableCellWithChevronAndMessage * myTextCell = (MessageTableCellWithChevronAndMessage *)cell; myTextCell->setSubtitle(I18n::Message::Default); @@ -148,22 +188,30 @@ void AboutController::willDisplayCellForIndex(HighlightCell * cell, int index) { memUseBuffer[len] = 'k'; memUseBuffer[len+1] = 'B'; memUseBuffer[len+2] = '/'; - + len = Poincare::Integer((int)((float) Ion::Storage::k_storageSize / 1024.f)).serialize(memUseBuffer + len + 3, 4) + len + 3; memUseBuffer[len] = 'k'; memUseBuffer[len+1] = 'B'; memUseBuffer[len+2] = '\0'; - + MessageTableCellWithBuffer * myCell = (MessageTableCellWithBuffer *)cell; myCell->setAccessoryText(memUseBuffer); } else { MessageTableCellWithBuffer * myCell = (MessageTableCellWithBuffer *)cell; static const char * mpVersion = MICROPY_VERSION_STRING; + + static char batteryLevel[5]; + int batteryLen = Poincare::Number::FloatNumber(Ion::Battery::voltage()).serialize(batteryLevel, 5, Poincare::Preferences::PrintFloatMode::Decimal, 3); + batteryLevel[batteryLen] = 'V'; + batteryLevel[batteryLen + 1] = '\0'; + static const char * messages[] = { (const char*) Ion::username(), - Ion::softwareVersion(), + Ion::upsilonVersion(), Ion::omegaVersion(), + Ion::softwareVersion(), mpVersion, + batteryLevel, "", Ion::serialNumber(), Ion::fccId(), diff --git a/apps/settings/sub_menu/about_controller.h b/apps/settings/sub_menu/about_controller.h index beef15d95cb..b661ac9e399 100644 --- a/apps/settings/sub_menu/about_controller.h +++ b/apps/settings/sub_menu/about_controller.h @@ -11,7 +11,6 @@ namespace Settings { class AboutController : public GenericSubController { public: AboutController(Responder * parentResponder); - //View * view() override { return &m_view; } View * view() override { return &m_selectableTableView; } void viewWillAppear() override; TELEMETRY_ID("About"); @@ -26,8 +25,7 @@ class AboutController : public GenericSubController { bool hasUsernameCell() const; ContributorsController m_contributorsController; MessageTableCellWithChevronAndMessage m_contributorsCell; - //SelectableViewWithMessages m_view; - MessageTableCellWithBuffer m_cells[k_totalNumberOfCell]; + MessageTableCellWithBuffer m_cells[k_totalNumberOfCell - 1]; HardwareTest::PopUpController m_hardwareTestPopUpController; }; diff --git a/apps/settings/sub_menu/about_controller_non_official.cpp b/apps/settings/sub_menu/about_controller_non_official.cpp index 52e111eb513..cedd20636a5 100644 --- a/apps/settings/sub_menu/about_controller_non_official.cpp +++ b/apps/settings/sub_menu/about_controller_non_official.cpp @@ -5,7 +5,7 @@ namespace Settings { void AboutController::viewWillAppear() { GenericSubController::viewWillAppear(); - // IN OMEGA, THE FOLLOWING LINES ARE ADDED IN A SUBMENU "LEGAL INFORMATION", BECAUSE MESSAGES DELETE THE SCROLLBAR. + // IN UPSILON, THE FOLLOWING LINES ARE ADDED IN A SUBMENU "LEGAL INFORMATION", BECAUSE MESSAGES DELETE THE SCROLLBAR. // --------------------- Please don't edit these lines ---------------------- I18n::Message cautionMessages[] = {I18n::Message::AboutWarning1, I18n::Message::AboutWarning2, I18n::Message::AboutWarning3, I18n::Message::AboutWarning4}; // m_view.setMessages(cautionMessages, sizeof(cautionMessages)/sizeof(I18n::Message)); diff --git a/apps/settings/sub_menu/accessibility_controller.cpp b/apps/settings/sub_menu/accessibility_controller.cpp index 1df981f379d..ca6b5b4a0da 100644 --- a/apps/settings/sub_menu/accessibility_controller.cpp +++ b/apps/settings/sub_menu/accessibility_controller.cpp @@ -25,7 +25,7 @@ bool AccessibilityController::handleEvent(Ion::Events::Event event) { int redGamma, greenGamma, blueGamma; KDIonContext::sharedContext()->gamma.gamma(redGamma, greenGamma, blueGamma); - if (event == Ion::Events::OK || event == Ion::Events::EXE) { + if ((event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) && (selectedRow() <= 2)) { if (selectedRow() == 0) { invertEnabled = !invertEnabled; } diff --git a/apps/settings/sub_menu/brightness_controller.cpp b/apps/settings/sub_menu/brightness_controller.cpp new file mode 100644 index 00000000000..9af1f42155a --- /dev/null +++ b/apps/settings/sub_menu/brightness_controller.cpp @@ -0,0 +1,142 @@ +#include "brightness_controller.h" +#include "../../global_preferences.h" +#include "../../apps_container.h" +#include "../../shared/poincare_helpers.h" +#include +#include +#include "../app.h" +#include +#include + +using namespace Poincare; +using namespace Shared; + +namespace Settings { + +BrightnessController::BrightnessController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) : + GenericSubController(parentResponder), + m_brightnessCell(I18n::Message::Brightness, KDFont::LargeFont), + m_editableCellIdleBeforeDimmingSeconds(&m_selectableTableView, inputEventHandlerDelegate, this), + m_editableCellIdleBeforeSuspendSeconds(&m_selectableTableView, inputEventHandlerDelegate, this), + m_BrightnessShortcutCell(&m_selectableTableView, inputEventHandlerDelegate, this) +{ + m_editableCellIdleBeforeDimmingSeconds.setMessage(I18n::Message::IdleTimeBeforeDimming); + m_editableCellIdleBeforeDimmingSeconds.setMessageFont(KDFont::LargeFont); + m_editableCellIdleBeforeSuspendSeconds.setMessage(I18n::Message::IdleTimeBeforeSuspend); + m_editableCellIdleBeforeSuspendSeconds.setMessageFont(KDFont::LargeFont); + m_BrightnessShortcutCell.setMessage(I18n::Message::BrightnessShortcut); + m_BrightnessShortcutCell.setMessageFont(KDFont::LargeFont); +} + +HighlightCell * BrightnessController::reusableCell(int index, int type) { + HighlightCell * editableCell[] = { + &m_brightnessCell, + &m_editableCellIdleBeforeDimmingSeconds, + &m_editableCellIdleBeforeSuspendSeconds, + &m_BrightnessShortcutCell + }; + return editableCell[index]; +} + +int BrightnessController::reusableCellCount(int type) { + switch(type) { + case 0: + case 1: + return k_totalNumberOfCell; + default: + assert(false); + return 0; + } +} + +void BrightnessController::willDisplayCellForIndex(HighlightCell * cell, int index) { + if(index == 0){ + MessageTableCellWithGauge * myGaugeCell = (MessageTableCellWithGauge *)cell; + GaugeView * myGauge = (GaugeView *)myGaugeCell->accessoryView(); + myGauge->setLevel((float)GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()/(float)Ion::Backlight::MaxBrightness); + return; + } else { + MessageTableCellWithEditableText * myCell = (MessageTableCellWithEditableText *)cell; + GenericSubController::willDisplayCellForIndex(myCell, index); + char buffer[5]; + int val; + switch(index){ + case 1:{ + val = GlobalPreferences::sharedGlobalPreferences()->idleBeforeDimmingSeconds(); + break; + } + case 2:{ + val = GlobalPreferences::sharedGlobalPreferences()->idleBeforeSuspendSeconds(); + break; + } + case 3:{ + val = GlobalPreferences::sharedGlobalPreferences()->brightnessShortcut(); + break; + } + default: + assert(false); + } + Poincare::Integer(val).serialize(buffer, 5); + myCell->setAccessoryText(buffer); + } +} + +bool BrightnessController::handleEvent(Ion::Events::Event event) { + if ((selectedRow() == 0) && (event == Ion::Events::Right || event == Ion::Events::Left || event == Ion::Events::Plus || event == Ion::Events::Minus)) { + int delta = Ion::Backlight::MaxBrightness/GlobalPreferences::NumberOfBrightnessStates; + int direction = (event == Ion::Events::Right || event == Ion::Events::Plus) ? delta : -delta; + GlobalPreferences::sharedGlobalPreferences()->setBrightnessLevel(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()+direction); + m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); + return true; + } + if (event == Ion::Events::BrightnessPlus || event == Ion::Events::BrightnessMinus){ + int delta = Ion::Backlight::MaxBrightness/GlobalPreferences::NumberOfBrightnessStates; + int NumberOfStepsPerShortcut = GlobalPreferences::sharedGlobalPreferences()->brightnessShortcut(); + int direction = (event == Ion::Events::BrightnessPlus) ? NumberOfStepsPerShortcut*delta : -delta*NumberOfStepsPerShortcut; + GlobalPreferences::sharedGlobalPreferences()->setBrightnessLevel(GlobalPreferences::sharedGlobalPreferences()->brightnessLevel()+direction); + m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), 0); + return true; + } + return false; +} + +bool BrightnessController::textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) { + return ((event == Ion::Events::Up && selectedRow() > 0) || (event == Ion::Events::Down && selectedRow() < k_totalNumberOfCell - 1)) + || TextFieldDelegate::textFieldShouldFinishEditing(textField, event); +} + +bool BrightnessController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { + double floatBody; + if (textFieldDelegateApp()->hasUndefinedValue(text, floatBody)) { + return false; + } + int val = std::round(floatBody); + switch (selectedRow()) { + case 1: + GlobalPreferences::sharedGlobalPreferences()->setIdleBeforeDimmingSeconds(val); + m_editableCellIdleBeforeDimmingSeconds.setAccessoryText(text); + break; + case 2: + GlobalPreferences::sharedGlobalPreferences()->setIdleBeforeSuspendSeconds(val); + m_editableCellIdleBeforeSuspendSeconds.setAccessoryText(text); + break; + case 3:{ + if(val > GlobalPreferences::NumberOfBrightnessStates){ val = GlobalPreferences::NumberOfBrightnessStates; + } else if (val < 0){val = 0;} + GlobalPreferences::sharedGlobalPreferences()->setBrightnessShortcut(val); + m_BrightnessShortcutCell.setAccessoryText(text); + break; + } + default: + assert(false); + } + AppsContainer * myContainer = AppsContainer::sharedAppsContainer(); + myContainer->refreshPreferences(); + m_selectableTableView.reloadCellAtLocation(0, selectedRow()); + if (event == Ion::Events::Up || event == Ion::Events::Down || event == Ion::Events::OK) { + m_selectableTableView.handleEvent(event); + } + return true; +} + +} diff --git a/apps/settings/sub_menu/brightness_controller.h b/apps/settings/sub_menu/brightness_controller.h new file mode 100644 index 00000000000..a36a39a3246 --- /dev/null +++ b/apps/settings/sub_menu/brightness_controller.h @@ -0,0 +1,33 @@ +#ifndef SETTINGS_BRIGHTNESS_CONTROLLER_H +#define SETTINGS_BRIGHTNESS_CONTROLLER_H + +#include "generic_sub_controller.h" +#include "preferences_controller.h" +#include "../message_table_cell_with_editable_text_with_separator.h" +#include "../../shared/parameter_text_field_delegate.h" +#include "../message_table_cell_with_gauge_with_separator.h" + +namespace Settings { + +class BrightnessController : public GenericSubController, public Shared::ParameterTextFieldDelegate { +public: + BrightnessController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate); + TELEMETRY_ID("BrightnessSettings"); + HighlightCell * reusableCell(int index, int type) override; + int reusableCellCount(int type) override; + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + bool handleEvent(Ion::Events::Event event) override; + bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; + bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; +protected: + constexpr static int k_totalNumberOfCell = 4; +private: + MessageTableCellWithGauge m_brightnessCell; + MessageTableCellWithEditableText m_editableCellIdleBeforeDimmingSeconds; + MessageTableCellWithEditableText m_editableCellIdleBeforeSuspendSeconds; + MessageTableCellWithEditableText m_BrightnessShortcutCell; +}; + +} + +#endif diff --git a/apps/settings/sub_menu/code_options_controller.cpp b/apps/settings/sub_menu/code_options_controller.cpp new file mode 100644 index 00000000000..ae7d5bbc2a2 --- /dev/null +++ b/apps/settings/sub_menu/code_options_controller.cpp @@ -0,0 +1,85 @@ +#include "code_options_controller.h" +#include +#include "../../global_preferences.h" + +using namespace Shared; + +namespace Settings { + +CodeOptionsController::CodeOptionsController(Responder * parentResponder) : + GenericSubController(parentResponder), + m_preferencesController(this) +{ + m_chevronCellFontSize.setMessageFont(KDFont::LargeFont); + m_switchCellAutoCompletion.setMessageFont(KDFont::LargeFont); + m_switchCellSyntaxHighlighting.setMessageFont(KDFont::LargeFont); +} + +bool CodeOptionsController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { + switch (selectedRow()){ + case 1: + GlobalPreferences::sharedGlobalPreferences()->setAutocomplete(!GlobalPreferences::sharedGlobalPreferences()->autocomplete()); + m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); + break; + case 2: + GlobalPreferences::sharedGlobalPreferences()->setSyntaxhighlighting(!GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()); + m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); + break; + default: + GenericSubController * subController = nullptr; + subController = &m_preferencesController; + subController->setMessageTreeModel(m_messageTreeModel->childAtIndex(selectedRow())); + StackViewController * stack = stackController(); + m_lastSelect = selectedRow(); + stack->push(subController); + break; + } + return true; + } + return GenericSubController::handleEvent(event); +} + +HighlightCell * CodeOptionsController::reusableCell(int index, int type) { + assert(type == 0); + assert(index >= 0 && index < k_totalNumberOfCell); + if (index == 0) { + return &m_chevronCellFontSize; + } + else if (index == 1) { + return &m_switchCellAutoCompletion; + } + return &m_switchCellSyntaxHighlighting; +} + +int CodeOptionsController::reusableCellCount(int type) { + assert(type == 0); + return k_totalNumberOfCell; +} + +void CodeOptionsController::willDisplayCellForIndex(HighlightCell * cell, int index) { + GenericSubController::willDisplayCellForIndex(cell, index); + I18n::Message thisLabel = m_messageTreeModel->childAtIndex(index)->label(); + + if (thisLabel == I18n::Message::FontSizes){ + MessageTableCellWithChevronAndMessage * myTextCell = (MessageTableCellWithChevronAndMessage *)cell; + myTextCell->setMessage(thisLabel); + GlobalPreferences::sharedGlobalPreferences()->font() == KDFont::LargeFont + ? myTextCell->setSubtitle(I18n::Message::LargeFont) + : myTextCell->setSubtitle(I18n::Message::SmallFont); + } +#ifdef HAS_CODE + else if (thisLabel == I18n::Message::Autocomplete) { + MessageTableCellWithSwitch * mySwitchCell = (MessageTableCellWithSwitch *)cell; + SwitchView * mySwitch = (SwitchView *)mySwitchCell->accessoryView(); + mySwitch->setState(GlobalPreferences::sharedGlobalPreferences()->autocomplete()); + } + else if (thisLabel == I18n::Message::SyntaxHighlighting) { + MessageTableCellWithSwitch * mySwitchCell = (MessageTableCellWithSwitch *)cell; + SwitchView * mySwitch = (SwitchView *)mySwitchCell->accessoryView(); + mySwitch->setState(GlobalPreferences::sharedGlobalPreferences()->syntaxhighlighting()); + } +#endif +} + +} diff --git a/apps/settings/sub_menu/code_options_controller.h b/apps/settings/sub_menu/code_options_controller.h new file mode 100644 index 00000000000..5f9b6acea22 --- /dev/null +++ b/apps/settings/sub_menu/code_options_controller.h @@ -0,0 +1,26 @@ +#ifndef SETTINGS_CODE_OPTIONS_CONTROLLER_H +#define SETTINGS_CODE_OPTIONS_CONTROLLER_H + +#include "generic_sub_controller.h" +#include "preferences_controller.h" + +namespace Settings { + +class CodeOptionsController : public GenericSubController { +public: + CodeOptionsController(Responder * parentResponder); + bool handleEvent(Ion::Events::Event event) override; + HighlightCell * reusableCell(int index, int type) override; + int reusableCellCount(int type) override; + void willDisplayCellForIndex(HighlightCell * cell, int index) override; +private: + constexpr static int k_totalNumberOfCell = 4; + PreferencesController m_preferencesController; + MessageTableCellWithChevronAndMessage m_chevronCellFontSize; + MessageTableCellWithSwitch m_switchCellAutoCompletion; + MessageTableCellWithSwitch m_switchCellSyntaxHighlighting; +}; + +} + +#endif diff --git a/apps/settings/sub_menu/contributors_controller.cpp b/apps/settings/sub_menu/contributors_controller.cpp index 26bab3326a5..378bfc3f6c1 100644 --- a/apps/settings/sub_menu/contributors_controller.cpp +++ b/apps/settings/sub_menu/contributors_controller.cpp @@ -28,8 +28,14 @@ int ContributorsController::reusableCellCount(int type) { return k_totalNumberOfCell; } -constexpr static int s_numberOfDevelopers = 13; +constexpr static int s_numberOfDevelopers = 18; +constexpr static int s_numberOfUpsilonDevelopers = 5; constexpr static I18n::Message s_developersUsernames[s_numberOfDevelopers] = { + I18n::Message::PLaurianFournier, + I18n::Message::PYannCouturier, + I18n::Message::PDavidLuca, + I18n::Message::PLoicE, + I18n::Message::PVictorKretz, I18n::Message::PQuentinGuidee, I18n::Message::PJoachimLeFournis, I18n::Message::PMaximeFriess, @@ -45,32 +51,13 @@ constexpr static I18n::Message s_developersUsernames[s_numberOfDevelopers] = { I18n::Message::PCyprienMejat, }; -constexpr static int s_numberOfBetaTesters = 8; -constexpr static I18n::Message s_betaTestersUsernames[s_numberOfBetaTesters] = { - I18n::Message::PTimeoArnouts, - I18n::Message::PJulieC, - I18n::Message::PLelahelHideux, - I18n::Message::PMadil, - I18n::Message::PHilaireLeRoux, - I18n::Message::PHectorNussbaumer, - I18n::Message::PRaphaelDyda, - I18n::Message::PThibautC, -}; - void ContributorsController::willDisplayCellForIndex(HighlightCell * cell, int index) { MessageTableCellWithBuffer * myTextCell = (MessageTableCellWithBuffer *)cell; - if (index == 0) { - myTextCell->setAccessoryText(""); - myTextCell->setTextColor(KDColor::RGB24(0xC03535)); - } else if (index > 0 && index <= s_numberOfDevelopers) { - myTextCell->setAccessoryText(I18n::translate(s_developersUsernames[index - 1])); - myTextCell->setTextColor(Palette::PrimaryText); - } else if (index == s_numberOfDevelopers + 1) { - myTextCell->setAccessoryText(""); - myTextCell->setTextColor(KDColor::RGB24(0x1ABC9A)); + myTextCell->setAccessoryText(I18n::translate(s_developersUsernames[index])); + if (index < s_numberOfUpsilonDevelopers) { + myTextCell->setTextColor(KDColor::RGB24(0x5e81ac)); } else { - myTextCell->setAccessoryText(I18n::translate(s_betaTestersUsernames[index - 2 - s_numberOfDevelopers])); - myTextCell->setTextColor(Palette::PrimaryText); + myTextCell->setTextColor(KDColor::RGB24(0xc53431)); } myTextCell->setAccessoryTextColor(Palette::SecondaryText); GenericSubController::willDisplayCellForIndex(cell, index); diff --git a/apps/settings/sub_menu/datetime_controller.cpp b/apps/settings/sub_menu/datetime_controller.cpp index f3ca0576049..35fb8c99455 100644 --- a/apps/settings/sub_menu/datetime_controller.cpp +++ b/apps/settings/sub_menu/datetime_controller.cpp @@ -26,11 +26,14 @@ DateTimeController::DateTimeController(Responder * parentResponder) : bool DateTimeController::handleEvent(Ion::Events::Event event) { bool clockEnabled = Ion::RTC::mode() != Ion::RTC::Mode::Disabled; - if (event == Ion::Events::OK || event == Ion::Events::EXE) { + if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { if (selectedRow() == 0) { clockEnabled = !clockEnabled; if (clockEnabled) { + #ifndef _FXCG + // This doesn't apply on Casio calculators Container::activeApp()->displayWarning(I18n::Message::RTCWarning1, I18n::Message::RTCWarning2); + #endif } Ion::RTC::setMode(clockEnabled ? Ion::RTC::Mode::HSE : Ion::RTC::Mode::Disabled); } diff --git a/apps/settings/sub_menu/exam_mode_controller.cpp b/apps/settings/sub_menu/exam_mode_controller.cpp index 33a853eaab1..9fd322321a7 100644 --- a/apps/settings/sub_menu/exam_mode_controller.cpp +++ b/apps/settings/sub_menu/exam_mode_controller.cpp @@ -88,7 +88,7 @@ void ExamModeController::willDisplayCellForIndex(HighlightCell * cell, int index I18n::Message thisLabel = m_messageTreeModel->childAtIndex(index)->label(); if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode() && (thisLabel == I18n::Message::ActivateExamMode || thisLabel == I18n::Message::ExamModeActive)) { - MessageTableCell * myCell = (MessageTableCell *)cell; + MessageTableCell<> * myCell = (MessageTableCell<> *)cell; myCell->setMessage(I18n::Message::ExamModeActive); } if (thisLabel == I18n::Message::ExamModeMode) { diff --git a/apps/settings/sub_menu/exam_mode_controller.h b/apps/settings/sub_menu/exam_mode_controller.h index e7abb1b1298..ebdfb219536 100644 --- a/apps/settings/sub_menu/exam_mode_controller.h +++ b/apps/settings/sub_menu/exam_mode_controller.h @@ -27,7 +27,7 @@ class ExamModeController : public GenericSubController { GlobalPreferences::ExamMode examMode(); static constexpr int k_maxNumberOfCells = 4; SelectableViewWithMessages m_contentView; - MessageTableCell m_cell[k_maxNumberOfCells]; + MessageTableCell<> m_cell[k_maxNumberOfCells]; PreferencesController m_ledController; PreferencesController m_examModeModeController; MessageTableCellWithChevronAndMessage m_examModeCell; diff --git a/apps/settings/sub_menu/external_controller.cpp b/apps/settings/sub_menu/external_controller.cpp new file mode 100644 index 00000000000..4ba28901f70 --- /dev/null +++ b/apps/settings/sub_menu/external_controller.cpp @@ -0,0 +1,72 @@ +#include "external_controller.h" + +#include +#include +#include +#include +#include +#include "../../apps_container.h" +#include "../../global_preferences.h" + +using namespace Poincare; +using namespace Shared; + +namespace Settings { + +ExternalController::ExternalController(Responder *parentResponder): + GenericSubController(parentResponder), + m_contentView(&m_selectableTableView) +{ + m_writeSwitchCell.setMessageFont(KDFont::LargeFont); + m_enabledSwitchCell.setMessageFont(KDFont::LargeFont); +} + +bool ExternalController::handleEvent(Ion::Events::Event event) { + if ((Ion::Events::OK == event || Ion::Events::EXE == event || Ion::Events::Right == event) && selectedRow() == 0) { + bool externalWasUnlocked = GlobalPreferences::sharedGlobalPreferences()->externalAppWritePermission(); + GlobalPreferences::sharedGlobalPreferences()->setExternalAppWritePermission(!externalWasUnlocked); + m_selectableTableView.reloadCellAtLocation(0, selectedRow()); + return true; + } else if ((Ion::Events::OK == event || Ion::Events::EXE == event || Ion::Events::Right == event) && selectedRow() == 1) { + bool extappWasShowed = GlobalPreferences::sharedGlobalPreferences()->externalAppShown(); + GlobalPreferences::sharedGlobalPreferences()->setExternalAppShown(!extappWasShowed); + m_selectableTableView.reloadCellAtLocation(0, selectedRow()); + return true; + } + + return GenericSubController::handleEvent(event); +} + +HighlightCell *ExternalController::reusableCell(int index, int type) { + if (index == 0) { + return &m_writeSwitchCell; + } + assert(index == 1); + return &m_enabledSwitchCell; +} + +int ExternalController::reusableCellCount(int type) { + assert(type == 0); + return 2; +} + +void ExternalController::willDisplayCellForIndex(HighlightCell *cell, int index) { + GenericSubController::willDisplayCellForIndex(cell, index); + + if (index == 0) { + MessageTableCellWithSwitch *myCell = (MessageTableCellWithSwitch *)cell; + SwitchView *mySwitch = (SwitchView *)myCell->accessoryView(); + mySwitch->setState(GlobalPreferences::sharedGlobalPreferences()->externalAppWritePermission()); + } else if (index == 1) { + MessageTableCellWithSwitch *myCell = (MessageTableCellWithSwitch *)cell; + SwitchView *mySwitch = (SwitchView *)myCell->accessoryView(); + mySwitch->setState(GlobalPreferences::sharedGlobalPreferences()->externalAppShown()); + } +} + +void ExternalController::didEnterResponderChain(Responder *previousFirstResponder) { + m_contentView.reload(); + I18n::Message infoMessages[] = {I18n::Message::ExtAppWriteExplanation1, I18n::Message::ExtAppWriteExplanation2, I18n::Message::ExtAppWriteExplanation3}; + m_contentView.setMessages(infoMessages, k_numberOfExplanationMessages); +} +} diff --git a/apps/settings/sub_menu/external_controller.h b/apps/settings/sub_menu/external_controller.h new file mode 100644 index 00000000000..11e5ac1212e --- /dev/null +++ b/apps/settings/sub_menu/external_controller.h @@ -0,0 +1,29 @@ +#ifndef SETTINGS_EXTERNAL_CONTROLLER_H +#define SETTINGS_EXTERNAL_CONTROLLER_H + +#include "generic_sub_controller.h" +#include "preferences_controller.h" +#include "selectable_view_with_messages.h" + +namespace Settings { + +class ExternalController : public GenericSubController { +public: + ExternalController(Responder* parentResponder); + View* view() override { return &m_contentView; } + bool handleEvent(Ion::Events::Event event) override; + TELEMETRY_ID("ExternalSettings"); + void didEnterResponderChain(Responder* previousFirstResponder) override; + HighlightCell* reusableCell(int index, int type) override; + int reusableCellCount(int type) override; + void willDisplayCellForIndex(HighlightCell* cell, int index) override; +private: + static constexpr int k_numberOfExplanationMessages = 3; + SelectableViewWithMessages m_contentView; + MessageTableCellWithSwitch m_writeSwitchCell; + MessageTableCellWithSwitch m_enabledSwitchCell; +}; + +} + +#endif diff --git a/apps/settings/sub_menu/generic_sub_controller.cpp b/apps/settings/sub_menu/generic_sub_controller.cpp index 5286743d1d1..25c5d15fade 100644 --- a/apps/settings/sub_menu/generic_sub_controller.cpp +++ b/apps/settings/sub_menu/generic_sub_controller.cpp @@ -75,7 +75,7 @@ int GenericSubController::typeAtLocation(int i, int j) { } void GenericSubController::willDisplayCellForIndex(HighlightCell * cell, int index) { - MessageTableCell * myCell = (MessageTableCell *)cell; + MessageTableCell<> * myCell = (MessageTableCell<> *)cell; myCell->setMessage(m_messageTreeModel->childAtIndex(index)->label()); } diff --git a/apps/settings/sub_menu/preferences_controller.cpp b/apps/settings/sub_menu/preferences_controller.cpp index 2aba6a8691f..dbf254966cb 100644 --- a/apps/settings/sub_menu/preferences_controller.cpp +++ b/apps/settings/sub_menu/preferences_controller.cpp @@ -27,7 +27,7 @@ void PreferencesController::didBecomeFirstResponder() { } bool PreferencesController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::OK || event == Ion::Events::EXE) { + if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { /* Generic behaviour of preference menu*/ assert(m_messageTreeModel->label() != I18n::Message::DisplayMode || selectedRow() != numberOfRows()-1); // In that case, events OK and EXE are handled by the cell setPreferenceWithValueIndex(m_messageTreeModel->label(), selectedRow()); diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index 7219416911b..f4fb2d05f80 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -1,12 +1,8 @@ ActivateDeactivate = "Aktivieren/Deaktivieren" -ActivateDutchExamMode = "Prüfungsmodus starten NL" ActivateExamMode = "Prüfungsmodus starten" ActiveExamModeMessage1 = "Alle Ihre Daten werden " ActiveExamModeMessage2 = "gelöscht, wenn Sie den " ActiveExamModeMessage3 = "Prüfungsmodus einschalten." -ActiveDutchExamModeMessage1 = "Alle Daten werden gelöscht, wenn" -ActiveDutchExamModeMessage2 = "Sie den Prüfungsmodus einschalten. " -ActiveDutchExamModeMessage3 = "Python wird nicht verfügbar sein." Axis = "Achse" Cancel = "Abbrechen" ClearColumn = "Spalte löschen" @@ -39,7 +35,6 @@ ExitExamMode2 = "Prüfungsmodus verlassen?" Exponential = "Exponentielle" FillWithFormula = "Mit einer Formel füllen" ForbiddenValue = "Verbotener Wert" -FunctionColumn = "0(0) Spalte" FunctionOptions = "Funktionsoptionen" Goto = "Gehe zu" GraphTab = "Graph" @@ -59,7 +54,6 @@ Navigate = "Navigieren" NEnd = "N Endwert" Next = "Nächste" NoDataToPlot = "Keine Daten zum Zeichnen" -NoFunctionToDelete = "Keine Funktion zum Löschen" NoValueToCompute = "Keine Größe zum Berechnen" NStart = "N Startwert" Ok = "Bestätigen" @@ -78,7 +72,6 @@ StandardDeviation = "Standardabweichung" Step = "Schrittwert" StorageMemoryFull1 = "Der Speicher ist voll. Löschen Sie" StorageMemoryFull2 = "einige Daten, dann erneut versuchen." -StoreExpressionNotAllowed = "'store' ist verboten" SyntaxError = "Syntaxfehler" Sym = "sym" TEnd = "T Endwert" @@ -91,11 +84,40 @@ ValuesTab = "Tabelle" Warning = "Achtung" XEnd = "X Endwert" XStart = "X Startwert" -Zoom = "Zoom" -Developers = "Entwickler" -BetaTesters = "Beta-Tester" ExamModeMode = "Modus" ExamModeModeStandard = "Standard " ExamModeModeNoSym = "Kein Symbol " ExamModeModeNoSymNoText = "Kein Symbol kein Text " ExamModeModeDutch = "Niederländisch " +ColorRed = "Rot " +ColorBlue = "Blau " +ColorGreen = "Grün " +ColorYellow = "Gelb " +ColorPurple = "Violett " +ColorCyan = "Cyan " +ColorPink = "Rosa " +ColorOrange = "Orange " +TimeDimension = "Zeit" +DistanceDimension = "Distanz" +MassDimension = "Masse" +CurrentDimension = "Betrieb" +TemperatureDimension = "Temperatur" +AmountOfSubstanceDimension = "Quantität der Materie" +LuminousIntensityDimension = "Lichtintensität" +FrequencyDimension = "Frequenz" +ForceDimension = "Stärke" +PressureDimension = "Druck" +EnergyDimension = "Energie" +PowerDimension = "Mächtig" +ElectricChargeDimension = "Elektrische Ladung" +ElectricPotentialDimension = "Elektrisches Potenzial" +ElectricCapacitanceDimension = "Elektrische Kapazität" +ElectricResistanceDimension = "Elektrischer Wiederstand" +ElectricConductanceDimension = "elektrische Leitfähigkeit" +MagneticFluxDimension = "magnetischer Fluss" +MagneticFieldDimension = "Magnetfeld" +InductanceDimension = "Induktivität" +CatalyticActivityDimension = "Katalytische Aktivität" +SurfaceDimension = "Auftauchen" +VolumeDimension = "Volumen" +SpeedDimension = "Geschwindigkeit" diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index d61df44e108..a5f65b59495 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -1,12 +1,8 @@ ActivateDeactivate = "Turn on/off" ActivateExamMode = "Activate exam mode" -ActivateDutchExamMode = "Activate Dutch exam mode" ActiveExamModeMessage1 = "All your data will be " ActiveExamModeMessage2 = "deleted when you activate " ActiveExamModeMessage3 = "the exam mode." -ActiveDutchExamModeMessage1 = "All your data will be deleted when" -ActiveDutchExamModeMessage2 = "you activate the exam mode. Python" -ActiveDutchExamModeMessage3 = "application will be unavailable." Axis = "Axes" Cancel = "Cancel" ClearColumn = "Clear column" @@ -39,7 +35,6 @@ ExitExamMode2 = "mode?" Exponential = "Exponential" FillWithFormula = "Fill with a formula" ForbiddenValue = "Forbidden value" -FunctionColumn = "0(0) column" FunctionOptions = "Function options" Goto = "Go to" GraphTab = "Graph" @@ -58,7 +53,6 @@ NameTooLong = "This name is too long" Navigate = "Navigate" Next = "Next" NoDataToPlot = "No data to draw" -NoFunctionToDelete = "No function to delete" NoValueToCompute = "No values to calculate" NEnd = "N end" NStart = "N start" @@ -74,7 +68,6 @@ SortValues = "Sort by increasing values" SortSizes = "Sort by increasing frequencies" SquareSum = "Sum of squares" StandardDeviation = "Standard deviation" -StoreExpressionNotAllowed = "'store' is not allowed" StatTab = "Stats" Step = "Step" StorageMemoryFull1 = "The memory is full." @@ -91,11 +84,40 @@ ValuesTab = "Table" Warning = "Warning" XEnd = "X end" XStart = "X start" -Zoom = "Zoom" -Developers = "Developers" -BetaTesters = "Beta testers" ExamModeMode = "Mode" ExamModeModeStandard = "Standard " ExamModeModeNoSym = "No sym " ExamModeModeNoSymNoText = "No sym no text " ExamModeModeDutch = "Dutch " +ColorRed = "Red " +ColorBlue = "Blue " +ColorGreen = "Green " +ColorYellow = "Yellow " +ColorPurple = "Purple " +ColorCyan = "Cyan " +ColorPink = "Pink " +ColorOrange = "Orange " +TimeDimension = "Time" +DistanceDimension = "Distance" +MassDimension = "Mass" +CurrentDimension = "Running" +TemperatureDimension = "Temperature" +AmountOfSubstanceDimension = "Quantity of matter" +LuminousIntensityDimension = "Light intensity" +FrequencyDimension = "Frequency" +ForceDimension = "Strength" +PressureDimension = "Pressure" +EnergyDimension = "Energy" +PowerDimension = "Powerful" +ElectricChargeDimension = "Electrical charge" +ElectricPotentialDimension = "Electric potential" +ElectricCapacitanceDimension = "Electrical capacity" +ElectricResistanceDimension = "Electrical resistance" +ElectricConductanceDimension = "electrical conductance" +MagneticFluxDimension = "magnetic flux" +MagneticFieldDimension = "Magnetic field" +InductanceDimension = "Inductance" +CatalyticActivityDimension = "Catalytic activity" +SurfaceDimension = "Surface" +VolumeDimension = "Volume" +SpeedDimension = "Speed" diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index e542571edab..cc76ce7e0c1 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -1,12 +1,8 @@ ActivateDeactivate = "Activar/Desactivar" ActivateExamMode = "Activar el modo examen" -ActivateDutchExamMode = "Activar el modo examen NL" ActiveExamModeMessage1 = "Todos sus datos se " ActiveExamModeMessage2 = "eliminaran al activar " ActiveExamModeMessage3 = "el modo examen." -ActiveDutchExamModeMessage1 = "Todos sus datos se eliminaran al" -ActiveDutchExamModeMessage2 = "activar el modo examen. La aplicación" -ActiveDutchExamModeMessage3 = "Python ya no estará disponible." Axis = "Ejes" Cancel = "Cancelar" ClearColumn = "Borrar la columna" @@ -39,7 +35,6 @@ ExitExamMode2 = "examen ?" Exponential = "Exponencial" FillWithFormula = "Rellenar con una fórmula" ForbiddenValue = "Valor prohibido" -FunctionColumn = "Columna 0(0)" FunctionOptions = "Opciones de la función" Goto = "Ir a" GraphTab = "Gráfico" @@ -59,7 +54,6 @@ Navigate = "Navegar" NEnd = "N fin" Next = "Siguiente" NoDataToPlot = "Ningunos datos que dibujar" -NoFunctionToDelete = "Ninguna función que eliminar" NoValueToCompute = "Ninguna medida que calcular" NStart = "N inicio" Ok = "Confirmar" @@ -78,7 +72,6 @@ StatTab = "Medidas" Step = "Incremento" StorageMemoryFull1 = "La memoria está llena." StorageMemoryFull2 = "Borre datos e intente de nuevo." -StoreExpressionNotAllowed = "'store' no está permitido" SyntaxError = "Error sintáctico" Sym = "sim" TEnd = "T fin" @@ -91,11 +84,40 @@ ValuesTab = "Tabla" Warning = "Cuidado" XEnd = "X fin" XStart = "X inicio" -Zoom = "Zoom" -Developers = "Desarrolladores" -BetaTesters = "Probadores beta" ExamModeMode = "Modo" ExamModeModeStandard = "Estándar " ExamModeModeNoSym = "Sin simbólico " ExamModeModeNoSymNoText = "Sin simbólico sin texto " ExamModeModeDutch = "Holandés " +ColorRed = "Rojo " +ColorBlue = "Azul " +ColorGreen = "Verde " +ColorYellow = "Amarillo " +ColorPurple = "Púrpura " +ColorCyan = "Cian " +ColorPink = "Rosa " +ColorOrange = "Naranja " +TimeDimension = "Tiempo" +DistanceDimension = "Distancia" +MassDimension = "Masa" +CurrentDimension = "Correr" +TemperatureDimension = "La temperatura" +AmountOfSubstanceDimension = "cantidad de materia" +LuminousIntensityDimension = "Intensidad de luz" +FrequencyDimension = "Frecuencia" +ForceDimension = "Fuerza" +PressureDimension = "Presión" +EnergyDimension = "Energía" +PowerDimension = "Potencia" +ElectricChargeDimension = "Carga eléctrica" +ElectricPotentialDimension = "Potencial eléctrico" +ElectricCapacitanceDimension = "Capacidad eléctrica" +ElectricResistanceDimension = "Resistencia eléctrica" +ElectricConductanceDimension = "conductancia eléctrica" +MagneticFluxDimension = "flujo magnético" +MagneticFieldDimension = "Campo magnético" +InductanceDimension = "Inductancia" +CatalyticActivityDimension = "Actividad catalítica" +SurfaceDimension = "Superficie" +VolumeDimension = "Volumen" +SpeedDimension = "Velocidad" \ No newline at end of file diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index 46b76202d59..2428b309b4e 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -1,12 +1,8 @@ ActivateDeactivate = "Activer/Désactiver" ActivateExamMode = "Activer le mode examen" -ActivateDutchExamMode = "Activer le mode examen NL" ActiveExamModeMessage1 = "Toutes vos données seront " ActiveExamModeMessage2 = "supprimées si vous activez " ActiveExamModeMessage3 = "le mode examen." -ActiveDutchExamModeMessage1 = "Toutes vos données seront supprimées " -ActiveDutchExamModeMessage2 = "si vous activez le mode examen." -ActiveDutchExamModeMessage3 = "Python sera inaccessible." Axis = "Axes" Cancel = "Annuler" ClearColumn = "Effacer la colonne" @@ -39,7 +35,6 @@ ExitExamMode2 = "du mode examen ?" Exponential = "Exponentielle" FillWithFormula = "Remplir avec une formule" ForbiddenValue = "Valeur interdite" -FunctionColumn = "Colonne 0(0)" FunctionOptions = "Options de la fonction" Goto = "Aller à" GraphTab = "Graphique" @@ -59,7 +54,6 @@ Navigate = "Naviguer" Next = "Suivant" NEnd = "N fin" NoDataToPlot = "Aucune donnée à tracer" -NoFunctionToDelete = "Pas de fonction à supprimer" NoValueToCompute = "Aucune grandeur à calculer" NStart = "N début" Ok = "Valider" @@ -78,7 +72,6 @@ StatTab = "Stats" Step = "Pas" StorageMemoryFull1 = "La mémoire est pleine." StorageMemoryFull2 = "Effacez des données et réessayez." -StoreExpressionNotAllowed = "'store' n'est pas autorisé" SyntaxError = "Attention à la syntaxe" Sym = "sym" TEnd = "T fin" @@ -91,11 +84,40 @@ ValuesTab = "Tableau" Warning = "Attention" XEnd = "X fin" XStart = "X début" -Zoom = "Zoom" -Developers = "Développeurs" -BetaTesters = "Beta testeurs" ExamModeMode = "Mode" ExamModeModeStandard = "Standard " ExamModeModeNoSym = "Sans symbolique " ExamModeModeNoSymNoText = "Sans symbolique ni texte " ExamModeModeDutch = "Dutch " +ColorRed = "Rouge " +ColorBlue = "Bleu " +ColorGreen = "Vert " +ColorYellow = "Jaune " +ColorPurple = "Violet " +ColorCyan = "Cyan " +ColorPink = "Rose " +ColorOrange = "Orange " +TimeDimension = "Temps" +DistanceDimension = "Distance" +MassDimension = "Masse" +CurrentDimension = "Courant" +TemperatureDimension = "Température" +AmountOfSubstanceDimension = "Quantité de matière" +LuminousIntensityDimension = "Intensité lumineuse" +FrequencyDimension = "Fréquence" +ForceDimension = "Force" +PressureDimension = "Pression" +EnergyDimension = "Énergie" +PowerDimension = "Puissance" +ElectricChargeDimension = "Charge électrique" +ElectricPotentialDimension = "Potentiel électrique" +ElectricCapacitanceDimension = "Capacité électrique" +ElectricResistanceDimension = "Résistance électrique" +ElectricConductanceDimension = "Conductance électrique" +MagneticFluxDimension = "Flux magnétique" +MagneticFieldDimension = "Champ magnétique" +InductanceDimension = "Inductance" +CatalyticActivityDimension = "Activité catalytique" +SurfaceDimension = "Surface" +VolumeDimension = "Volume" +SpeedDimension = "Vitesse" diff --git a/apps/shared.hu.i18n b/apps/shared.hu.i18n index 60ba0754b34..91e45f3d154 100644 --- a/apps/shared.hu.i18n +++ b/apps/shared.hu.i18n @@ -1,101 +1,124 @@ -ActivateDeactivate = "Ki/Be kapcsolás" -ActivateExamMode = "A vizsgálati mód aktiválása" -ActivateDutchExamMode = "A holland vizsga mód aktiválása" -ActiveExamModeMessage1 = "Az összes adatod" -ActiveExamModeMessage2 = "törölve lesz ha" -ActiveExamModeMessage3 = "a vizsga módot aktiválja." -ActiveDutchExamModeMessage1 = "Az összes adatod törölve lesz" -ActiveDutchExamModeMessage2 = "ha a vizsga módot aktiválja. A" -ActiveDutchExamModeMessage3 = "Python alkalmazás használhatatlan lesz." -Axis = "Tengelyek" -Cancel = "Mégse" -ClearColumn = "Oszlop törlése" -ColumnOptions = "Oszlop opciók" -ConfirmDiscard1 = "Minden változtatást elvetünk" -ConfirmDiscard2 = "" -CopyColumnInList = "Az oszlopot egy listába másolni" -Country = "Ország" -CountryCA = "Kanada " -CountryDE = "Németország " -CountryES = "Spanyolország " -CountryFR = "Franciaország " -CountryGB = "Egyesült Királyság " -CountryIT = "Olaszország " -CountryNL = "Hollandia " -CountryPT = "Portugália " -CountryUS = "Egyesült Államok " -CountryWW = "Nemzetközi " -CountryWarning1 = "Ez a beállítás meghatározza az" -CountryWarning2 = "alkalmazott tematikus konvenciókat." -DataNotSuitable = "Az adatok nem felelnek meg" -DataTab = "Adatok" -Deg = "deg" -Deviation = "Varianca" -DisplayValues = "Értékek mutatása" -Empty = "Üres" -Eng = "eng" -ExitExamMode1 = "Kilépni a vizsga " -ExitExamMode2 = "módból?" -Exponential = "Exponenciális" -FillWithFormula = "Töltse ki egy képlettel" -ForbiddenValue = "Tiltott érték" -FunctionColumn = "0(0) oszlop" -FunctionOptions = "Funkció opciók" -Goto = "Menj ..." -GraphTab = "Grafikon" -HardwareTestLaunch1 = "A hardverteszt indítása :" -HardwareTestLaunch2 = "Nyomjon a reset gombra a" -HardwareTestLaunch3 = "teszt megállításához (ez" -HardwareTestLaunch4 = "az adatokat törölni fogja)" -IntervalSet = "Állítsa be az intervallumot" -Language = "Nyelv" -LowBattery = "Majdnem kimerült az elem" -Mean = "középérték" -Move = " Odébb rakni: " -NameCannotStartWithNumber = "Egy név nem kezdöthet számmal" -NameTaken = "Ez a név foglalt" -NameTooLong = "Ez a név túl hosszú" -Navigate = "Hajózik" -Next = "következö" -NEnd = "N vége" -NoDataToPlot = "Nincs rajzolható adat" -NoFunctionToDelete = "Nincs törölhetö függvény" -NoValueToCompute = "Nincs számítható érték" -NStart = "N kezdete" -Ok = "Érvényesítés" -Or = " vagy " -Orthonormal = "Ortonormált" -Plot = "Grafikon rajzolása" -PoolMemoryFull1 = "A memória megtelt." -PoolMemoryFull2 = "Kérem próbálja újra." -Rename = "Átnevezés" -Sci = "sci" -SortValues = "Rendezés értékek növelésével" -SortSizes = "Rendezés növekvő frekvenciák szerint" -SquareSum = "Négyzetek összege" -StandardDeviation = "Alap eltérés" -StatTab = "Statisztikák" -Step = "Lépés" -StorageMemoryFull1 = "A memória megtelt." -StorageMemoryFull2 = "Törlöljön adatokat és próbálkozzon újra." -StoreExpressionNotAllowed = "'szore' nem engedélyezett" -SyntaxError = "Szintaxis hiba" -Sym = "sym" -TEnd = "T vég" -ThetaEnd = "θ vége" -ThetaStart = "θ kezdete" -TStart = "T kezdete" -ToZoom = "Nagyítani : " -UndefinedValue = "Meghatározatlan adat" -ValuesTab = "Táblázat" -Warning = "Figyelem" -XEnd = "X vége" -XStart = "X kezdete" -Zoom = "Nagyítás" -Developers = "Kifejlesztök" -BetaTesters = "Béta tesztelök" -ExamModeMode = "Üzemmód" -ExamModeModeStandard = "Normál " -ExamModeModeNoSym = "Szimbólikus nélkül " -ExamModeModeNoSymNoText = "Szimbólikus és szöveg nélkül " -ExamModeModeDutch = "Holland " +ActivateDeactivate = "Ki/Be kapcsolás" +ActivateExamMode = "A vizsgálati mód aktiválása" +ActiveExamModeMessage1 = "Az összes adatod" +ActiveExamModeMessage2 = "törölve lesz ha" +ActiveExamModeMessage3 = "a vizsga módot aktiválja." +Axis = "Tengelyek" +Cancel = "Mégse" +ClearColumn = "Oszlop törlése" +ColumnOptions = "Oszlop opciók" +ConfirmDiscard1 = "Minden változtatást elvetünk" +ConfirmDiscard2 = "" +CopyColumnInList = "Az oszlopot egy listába másolni" +Country = "Ország" +CountryCA = "Kanada " +CountryDE = "Németország " +CountryES = "Spanyolország " +CountryFR = "Franciaország " +CountryGB = "Egyesült Királyság " +CountryIT = "Olaszország " +CountryNL = "Hollandia " +CountryPT = "Portugália " +CountryUS = "Egyesült Államok " +CountryWW = "Nemzetközi " +CountryWarning1 = "Ez a beállítás meghatározza az" +CountryWarning2 = "alkalmazott tematikus konvenciókat." +DataNotSuitable = "Az adatok nem felelnek meg" +DataTab = "Adatok" +Deg = "deg" +Deviation = "Varianca" +DisplayValues = "Értékek mutatása" +Empty = "Üres" +Eng = "eng" +ExitExamMode1 = "Kilépni a vizsga " +ExitExamMode2 = "módból?" +Exponential = "Exponenciális" +FillWithFormula = "Töltse ki egy képlettel" +ForbiddenValue = "Tiltott érték" +FunctionOptions = "Funkció opciók" +Goto = "Menj ..." +GraphTab = "Grafikon" +HardwareTestLaunch1 = "A hardverteszt indítása :" +HardwareTestLaunch2 = "Nyomjon a reset gombra a" +HardwareTestLaunch3 = "teszt megállításához (ez" +HardwareTestLaunch4 = "az adatokat törölni fogja)" +IntervalSet = "Állítsa be az intervallumot" +Language = "Nyelv" +LowBattery = "Majdnem kimerült az elem" +Mean = "középérték" +Move = " Odébb rakni: " +NameCannotStartWithNumber = "Egy név nem kezdöthet számmal" +NameTaken = "Ez a név foglalt" +NameTooLong = "Ez a név túl hosszú" +Navigate = "Hajózik" +Next = "következö" +NEnd = "N vége" +NoDataToPlot = "Nincs rajzolható adat" +NoValueToCompute = "Nincs számítható érték" +NStart = "N kezdete" +Ok = "Érvényesítés" +Or = " vagy " +Orthonormal = "Ortonormált" +Plot = "Grafikon rajzolása" +PoolMemoryFull1 = "A memória megtelt." +PoolMemoryFull2 = "Kérem próbálja újra." +Rename = "Átnevezés" +Sci = "sci" +SortValues = "Rendezés értékek növelésével" +SortSizes = "Rendezés növekvő frekvenciák szerint" +SquareSum = "Négyzetek összege" +StandardDeviation = "Alap eltérés" +StatTab = "Statisztikák" +Step = "Lépés" +StorageMemoryFull1 = "A memória megtelt." +StorageMemoryFull2 = "Törlöljön adatokat és próbálkozzon újra." +SyntaxError = "Szintaxis hiba" +Sym = "sym" +TEnd = "T vég" +ThetaEnd = "θ vége" +ThetaStart = "θ kezdete" +TStart = "T kezdete" +ToZoom = "Nagyítani : " +UndefinedValue = "Meghatározatlan adat" +ValuesTab = "Táblázat" +Warning = "Figyelem" +XEnd = "X vége" +XStart = "X kezdete" +ExamModeMode = "Üzemmód" +ExamModeModeStandard = "Normál " +ExamModeModeNoSym = "Szimbólikus nélkül " +ExamModeModeNoSymNoText = "Szimbólikus és szöveg nélkül " +ExamModeModeDutch = "Holland " +ColorRed = "Piros " +ColorBlue = "Kék " +ColorGreen = "Zöld " +ColorYellow = "Sárga " +ColorPurple = "Lila " +ColorCyan = "Cián " +ColorPink = "Rózsaszín " +ColorOrange = "Narancssárga " +TimeDimension = "Idő" +DistanceDimension = "Távolság" +MassDimension = "Tömeg" +CurrentDimension = "Futó" +TemperatureDimension = "Hőfok" +AmountOfSubstanceDimension = "Az anyag mennyisége" +LuminousIntensityDimension = "Fény intenzitása" +FrequencyDimension = "Frekvencia" +ForceDimension = "Erő" +PressureDimension = "Nyomás" +EnergyDimension = "Energia" +PowerDimension = "Erős" +ElectricChargeDimension = "Elektromos töltő" +ElectricPotentialDimension = "Elektromos potenciál" +ElectricCapacitanceDimension = "Elektromos kapacitás" +ElectricResistanceDimension = "Elektromos ellenállás" +ElectricConductanceDimension = "elektromos vezetőképesség" +MagneticFluxDimension = "mágneses fluxus" +MagneticFieldDimension = "Mágneses mező" +InductanceDimension = "Induktivitás" +CatalyticActivityDimension = "Katalitikus aktivitás" +SurfaceDimension = "Felület" +VolumeDimension = "Hangerő" +SpeedDimension = "Sebesség" +Factorial = "Faktorál" diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n index 3322800c8c5..d1932fbd34b 100644 --- a/apps/shared.it.i18n +++ b/apps/shared.it.i18n @@ -1,12 +1,8 @@ ActivateDeactivate = "Attivare/Disattivare" ActivateExamMode = "Attiva modalità d'esame" -ActivateDutchExamMode = "Attiva modalità d'esame NL" ActiveExamModeMessage1 = "Tutti i tuoi dati saranno " ActiveExamModeMessage2 = "cancellati se attivi " ActiveExamModeMessage3 = "la modalità d'esame." -ActiveDutchExamModeMessage1 = "Tutti i tuoi dati saranno cancellati" -ActiveDutchExamModeMessage2 = "se attivi la modalità d'esame." -ActiveDutchExamModeMessage3 = "L'app Python sarà inaccessibile." Axis = "Assi" Cancel = "Annullare" ClearColumn = "Cancella la colonna" @@ -39,7 +35,6 @@ ExitExamMode2 = "dalla modalità d'esame ?" Exponential = "Esponenziale" FillWithFormula = "Compilare con una formula" ForbiddenValue = "Valore non consentito" -FunctionColumn = "Colonna 0(0)" FunctionOptions = "Opzioni della funzione" Goto = "Andare a" GraphTab = "Grafico" @@ -59,7 +54,6 @@ Navigate = "Navigare" Next = "Successivo" NEnd = "N finale" NoDataToPlot = "Nessun dato da tracciare" -NoFunctionToDelete = "Nessuna funzione da cancellare" NoValueToCompute = "Nessun valore da calcolare" NStart = "N iniziale" Ok = "Conferma" @@ -78,7 +72,6 @@ StatTab = "Stats" Step = "Passo" StorageMemoryFull1 = "La memoria è piena." StorageMemoryFull2 = "Cancellate i dati e riprovate." -StoreExpressionNotAllowed = "'store' non è consentito" SyntaxError = "Sintassi errata" Sym = "sym" TEnd = "T finale" @@ -91,11 +84,41 @@ ValuesTab = "Tabella" Warning = "Attenzione" XEnd = "X finale" XStart = "X iniziale" -Zoom = "Zoom" -Developers = "Developers" -BetaTesters = "Beta testers" ExamModeMode = "Modalità" ExamModeModeStandard = "Standard " ExamModeModeNoSym = "Nessun simbolo " ExamModeModeNoSymNoText = "Nessun simbolo nessun testo " ExamModeModeDutch = "Olandese " +ColorRed = "Rosso " +ColorBlue = "Blu " +ColorGreen = "Verde " +ColorYellow = "Giallo " +ColorPurple = "Viola " +ColorCyan = "Ciano" +ColorPink = "Rosa " +ColorOrange = "Arancia " +TimeDimension = "Volta" +DistanceDimension = "Distanza" +MassDimension = "Messa" +CurrentDimension = "In esecuzione" +TemperatureDimension = "Temperatura" +AmountOfSubstanceDimension = "Quantità di materia" +LuminousIntensityDimension = "Intensità luminosa" +FrequencyDimension = "Frequenza" +ForceDimension = "Forza" +PressureDimension = "Pressione" +EnergyDimension = "Energia" +PowerDimension = "Potere" +ElectricChargeDimension = "Carica elettrica" +ElectricPotentialDimension = "Potenziale elettrico" +ElectricCapacitanceDimension = "Capacità elettrica" +ElectricResistanceDimension = "Resistenza elettrica" +ElectricConductanceDimension = "conduttanza elettrica" +MagneticFluxDimension = "flusso magnetico" +MagneticFieldDimension = "Campo magnetico" +InductanceDimension = "Induttanza" +CatalyticActivityDimension = "Attività catalitica" +SurfaceDimension = "Superficie" +VolumeDimension = "Volume" +SpeedDimension = "Velocità" +Factorial = "Fattoriale" diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n index b3971aa6cfe..e63e98605c5 100644 --- a/apps/shared.nl.i18n +++ b/apps/shared.nl.i18n @@ -1,12 +1,8 @@ ActivateDeactivate = "Zet aan/uit" ActivateExamMode = "Internationale examenst." -ActivateDutchExamMode = "Nederlandse examenstand" ActiveExamModeMessage1 = "Al je gegevens worden " ActiveExamModeMessage2 = "gewist wanneer je de " ActiveExamModeMessage3 = "examenstand activeert." -ActiveDutchExamModeMessage1 = "Al je gegevens worden gewist wanneer" -ActiveDutchExamModeMessage2 = "je de examenstand activeert. De Python" -ActiveDutchExamModeMessage3 = "applicatie wordt uitgeschakeld." Axis = "Assen" Cancel = "Annuleer" ClearColumn = "Wis kolom" @@ -39,7 +35,6 @@ ExitExamMode2 = "examenstand?" Exponential = "Exponentieel" FillWithFormula = "Vul met een formule" ForbiddenValue = "Verboden waarde" -FunctionColumn = "0(0) kolom" FunctionOptions = "Functie-opties" Goto = "Ga naar" GraphTab = "Grafiek" @@ -58,7 +53,6 @@ NameTooLong = "Deze naam is te lang" Navigate = "Navigeren" Next = "Volgende" NoDataToPlot = "Geen gegevens om te plotten" -NoFunctionToDelete = "Geen functie om te verwijderen" NoValueToCompute = "Geen waarden om te berekenen" NEnd = "N einde" NStart = "N begin" @@ -74,7 +68,6 @@ SortValues = "Sorteer waarden oplopend" SortSizes = "Sorteer frequenties oplopend" SquareSum = "Som van kwadraten" StandardDeviation = "Standaardafwijking" -StoreExpressionNotAllowed = "'opslaan' is niet toegestaan" StatTab = "Stats" Step = "Stap" StorageMemoryFull1 = "Het geheugen is vol." @@ -91,11 +84,41 @@ ValuesTab = "Tabel" Warning = "Waarschuwing" XEnd = "X einde" XStart = "X begin" -Zoom = "Zoom" -Developers = "Developers" -BetaTesters = "Beta testers" ExamModeMode = "Mode" ExamModeModeStandard = "Standaard " ExamModeModeNoSym = "Geen sym " ExamModeModeNoSymNoText = "Geen sym geen tekst " ExamModeModeDutch = "Nederlands " +ColorRed = "rood" +ColorBlue = "Blauw" +ColorGreen = "Groente" +ColorYellow = "Geel" +ColorPurple = "Purper" +ColorCyan = "Cyaan " +ColorPink = "Roze" +ColorOrange = "Oranje" +TimeDimension = "Tijd" +DistanceDimension = "Afstand" +MassDimension = "Massa" +CurrentDimension = "Rennen" +TemperatureDimension = "Temperatuur" +AmountOfSubstanceDimension = "Hoeveelheid materie" +LuminousIntensityDimension = "Lichtsterkte" +FrequencyDimension = "Frequentie" +ForceDimension = "Kracht" +PressureDimension = "Druk" +EnergyDimension = "Energie" +PowerDimension = "Kracht" +ElectricChargeDimension = "Elektrische lading" +ElectricPotentialDimension = "elektrische potentiaal" +ElectricCapacitanceDimension = "elektrische capaciteit:" +ElectricResistanceDimension = "Elektrische weerstand" +ElectricConductanceDimension = "elektrische geleiding:" +MagneticFluxDimension = "magnetische flux" +MagneticFieldDimension = "Magnetisch veld" +InductanceDimension = "Inductie" +CatalyticActivityDimension = "Katalytische activiteit" +SurfaceDimension = "Oppervlak" +VolumeDimension = "Volume" +SpeedDimension = "Snelheid" +Factorial = "Faculteit" diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index 39fa24c1261..de0852119a3 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -1,12 +1,8 @@ ActivateDeactivate = "Ativar/Desativar" ActivateExamMode = "Ativar o modo de exame" -ActivateDutchExamMode = "Ativar o modo de exame NL" ActiveExamModeMessage1 = "Todos os seus dados serão " ActiveExamModeMessage2 = "apagados se ativar " ActiveExamModeMessage3 = "o modo de exame." -ActiveDutchExamModeMessage1 = "Todos os seus dados serão apagados " -ActiveDutchExamModeMessage2 = "se ativar o modo de exame. A" -ActiveDutchExamModeMessage3 = "aplicação Python estará indisponível." Axis = "Eixos" Cancel = "Cancelar" ClearColumn = "Excluir coluna" @@ -39,7 +35,6 @@ ExitExamMode2 = "exame ?" Exponential = "Exponencial" FillWithFormula = "Preencher com uma fórmula" ForbiddenValue = "Valor proibido" -FunctionColumn = "Coluna 0(0)" FunctionOptions = "Opções de função" Goto = "Ir para" GraphTab = "Gráfico" @@ -59,7 +54,6 @@ Navigate = "Navegar" NEnd = "N fim" Next = "Seguinte" NoDataToPlot = "Não há dados para desenhar" -NoFunctionToDelete = "Sem função para eliminar" NoValueToCompute = "Não há dados para calcular" NStart = "N início" Ok = "Confirmar" @@ -78,7 +72,6 @@ StatTab = "Estat" Step = "Passo" StorageMemoryFull1 = "A memória esta cheia." StorageMemoryFull2 = "Apage dados e tente novamente." -StoreExpressionNotAllowed = "'store' não está permitido" SyntaxError = "Erro de sintaxe" Sym = "sim" TEnd = "T fim" @@ -91,11 +84,40 @@ ValuesTab = "Tabela" Warning = "Atenção" XEnd = "X fim" XStart = "X início" -Zoom = "Zoom" -Developers = "Desenvolvedores" -BetaTesters = "Testadores beta" ExamModeMode = "Modo" ExamModeModeStandard = "Padrão " ExamModeModeNoSym = "Sem sym " ExamModeModeNoSymNoText = "Sem sym sem texto " ExamModeModeDutch = "holandês " +ColorRed = "Vermelho " +ColorBlue = "Azul " +ColorGreen = "Verde " +ColorYellow = "Amarelo " +ColorPurple = "Roxa " +ColorCyan = "Ciano" +ColorPink = "Cor de rosa " +ColorOrange = "Laranja " +TimeDimension = "Tempo" +DistanceDimension = "Distância" +MassDimension = "Massa" +CurrentDimension = "Corrida" +TemperatureDimension = "Temperatura" +AmountOfSubstanceDimension = "Quantidade de matéria" +LuminousIntensityDimension = "Intensidade da luz" +FrequencyDimension = "Frequência" +ForceDimension = "Força" +PressureDimension = "Pressão" +EnergyDimension = "Energia" +PowerDimension = "Poderoso" +ElectricChargeDimension = "Carga elétrica" +ElectricPotentialDimension = "Potencial elétrico" +ElectricCapacitanceDimension = "Capacidade elétrica" +ElectricResistanceDimension = "Resistência elétrica" +ElectricConductanceDimension = "condutância elétrica" +MagneticFluxDimension = "fluxo magnético" +MagneticFieldDimension = "Campo magnético" +InductanceDimension = "Indutância" +CatalyticActivityDimension = "Atividade catalítica" +SurfaceDimension = "Superfície" +VolumeDimension = "Volume" +SpeedDimension = "Velocidade" diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index fb927a65962..f38ad608540 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -25,7 +25,6 @@ UnitMassGramKiloSymbol = "_kg" UnitMassGramSymbol = "_g" UnitMassGramMilliSymbol = "_mg" UnitMassGramMicroSymbol = "_μg" -UnitMassGramNanoSymbol = "_ng" UnitMassTonneSymbol = "_t" UnitMassOunceSymbol = "_oz" UnitMassPoundSymbol = "_lb" @@ -122,11 +121,12 @@ DeterminantCommandWithArg = "det(M)" DiffCommandWithArg = "diff(f(x),x,a)" DiffCommand = "diff(\x11,x,\x11)" DimensionCommandWithArg = "dim(M)" -DiscriminantFormulaDegree2 = "Δ=b^2-4ac" DotCommandWithArg = "dot(u,v)" E = "e" Equal = "=" FactorCommandWithArg = "factor(n)" +FactorialCommand = "!" +FactorialCommandWithArg = "n!" FccId = "FCC ID" FloorCommandWithArg = "floor(x)" FracCommandWithArg = "frac(x)" @@ -315,6 +315,16 @@ ElementTsMass = "294" ElementOgMass = "294" ElementUueMass = "295" ElementUbnMass = "297" +LaurianFournier = "Laurian Fournier" +PLaurianFournier = "@Lauryy06" +YannCouturier = "Yann Couturier" +PYannCouturier = "@Yaya-Cout" +LoicE = "Loïc E." +PLoicE = "@lolocomotive" +DavidLuca = "David Luca" +PDavidLuca = "@dl11" +VictorKretz = "Victor Kretz" +PVictorKretz = "@Mino" QuentinGuidee = "Quentin Guidée" PQuentinGuidee = "@quentinguidee" SandraSimmons = "Sandra Simmons" @@ -341,24 +351,7 @@ VenceslasDuet = "Venceslas Duet" PVenceslasDuet = "@Citorva" CyprienMejat = "Cyprien Méjat" PCyprienMejat = "@A2drien" -TimeoArnouts = "Timéo Arnouts" -PTimeoArnouts = "@Dogm" -JulieC = "Julie C." -PJulieC = "@windows9x95" -LelahelHideux = "Lélahel Hideux" -PLelahelHideux = "@Lelahelry" -Madil = "Madil" -PMadil = "@le-grand-mannitout" -HilaireLeRoux = "Hilaire Le Roux" -PHilaireLeRoux = "@0Babass2" -HectorNussbaumer = "Hector Nussbaumer" -PHectorNussbaumer = "@Sycorax" -RaphaelDyda = "Raphaël Dyda" -PRaphaelDyda = "@Trixciel" -ThibautC = "Thibaut C." -PThibautC = "@Tibo_C" SpeedOfLight = "2.99792458·10^8_m_s^-1" -YearLight = "9.461·10^15_m" Boltzmann = "1.380649·10^-23_J_K^-1" StefanBoltzmann = "5.670374419·10^-8_W_m^-2_K^-4" VacuumImpedance = "376.730313668_Ω" @@ -458,3 +451,39 @@ HartreeConstant = "4.3597447222071·10^-18_J" MagneticFluxQuantum = "2.067833848·10^-15_Wb" ConductanceQuantum = "7.748091729·10^-5_S" CirculationQuantum = "3.6369475516·10^-4_m^2_s^-1" +Cndcvt_Silver = "6.30·10^7_S_m^-1" +Cndcvt_Copper = "5.96·10^7_S_m^-1" +Cndcvt_Gold = "4.11·10^7_S_m^-1" +Cndcvt_Aluminium = "3.77·10^7_S_m^-1" +Cndcvt_Calcium = "2.98·10^7_S_m^-1" +Cndcvt_Tungsten = "1.79·10^7_S_m^-1" +Cndcvt_Zinc = "1.69·10^7_S_m^-1" +Cndcvt_Cobalt = "1.60·10^7_S_m^-1" +Cndcvt_Nickel = "1.43·10^7_S_m^-1" +Cndcvt_Lithium = "1.08·10^7_S_m^-1" +Cndcvt_Iron = "1.00·10^7_S_m^-1" +Cndcvt_Platinum = "9.43·10^6_S_m^-1" +Cndcvt_Tin = "9.17·10^6_S_m^-1" +Cndcvt_Sea_water = "4.80_S_m^-1" +Cndcvt_Water = "5.00·10^-3_S_m^-1" +Cndcvt_Air = "1.00·10^-13_S_m^-1" +Cndcvt_Glass = "1.00·10^-13_S_m^-1" +Cndcvt_Wood = "1.00·10^-3_S_m^-1" +Rstvt_Silver = "1.59·10^-8_Ω_m" +Rstvt_Copper = "1.68·10^-8_Ω_m" +Rstvt_Gold = "2.44·10^-8_Ω_m" +Rstvt_Aluminium = "2.65·10^-8_Ω_m" +Rstvt_Calcium = "3.36·10^-8_Ω_m" +Rstvt_Tungsten = "5.60·10^-8_Ω_m" +Rstvt_Zinc = "5.90·10^-8_Ω_m" +Rstvt_Cobalt = "6.24·10^-8_Ω_m" +Rstvt_Nickel = "6.99·10^-8_Ω_m" +Rstvt_Lithium = "9.28·10^-8_Ω_m" +Rstvt_Iron = "9.70·10^-8_Ω_m" +Rstvt_Platinum = "1.06·10^-7_Ω_m" +Rstvt_Tin = "1.09·10^-7_Ω_m" +Rstvt_Sea_water = "2.1·10^-1_Ω_m" +Rstvt_Water = "1.01·10^3_Ω_m" +Rstvt_Air = "1.00·10^9_Ω_m" +Rstvt_Glass = "5.00·10^14_Ω_m" +Rstvt_Wood = "1.00·10^3_Ω_m" diff --git a/apps/shared/Makefile b/apps/shared/Makefile index bd421f508a6..1d0530e7383 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -29,6 +29,8 @@ app_shared_src = $(addprefix apps/shared/,\ buffer_function_title_cell.cpp \ buffer_text_view_with_text_field.cpp \ button_with_separator.cpp \ + color_cell.cpp \ + color_parameter_controller.cpp \ cursor_view.cpp \ editable_cell_table_view_controller.cpp \ expression_field_delegate_app.cpp \ diff --git a/apps/shared/banner_view.h b/apps/shared/banner_view.h index b1ad6e5fc5d..23424dec4e9 100644 --- a/apps/shared/banner_view.h +++ b/apps/shared/banner_view.h @@ -12,7 +12,7 @@ class BannerView : public View { KDSize minimalSizeForOptimalDisplay() const override; KDCoordinate minimalHeightForOptimalDisplayGivenWidth(KDCoordinate width) const; void reload() { layoutSubviews(); } - static constexpr const KDFont * Font() { return KDFont::SmallFont; } + static constexpr const KDFont * Font() { return KDFont::ItalicSmallFont; } static constexpr KDColor TextColor() { return Palette::PrimaryText; } static constexpr KDColor BackgroundColor() { return Palette::SubMenuBackground; } private: diff --git a/apps/shared/color_cell.cpp b/apps/shared/color_cell.cpp new file mode 100644 index 00000000000..a43e3f3ea5b --- /dev/null +++ b/apps/shared/color_cell.cpp @@ -0,0 +1,51 @@ +#include "color_cell.h" + +namespace Shared { + +constexpr const I18n::Message MessageTableCellWithColor::k_textForIndex[Palette::numberOfDataColors()]; + +constexpr const uint8_t colorMask[MessageTableCellWithColor::ColorView::k_colorSize][MessageTableCellWithColor::ColorView::k_colorSize] = { // FIXME Can't link with constexpr static + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xE1, 0x0C, 0x00, 0x00, 0x0C, 0xE1, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xFF, 0xFF}, + {0xFF, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE1, 0xFF}, + {0xFF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF}, + {0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, + {0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, + {0xFF, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF}, + {0xFF, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE1, 0xFF}, + {0xFF, 0xFF, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xE1, 0x0C, 0x00, 0x00, 0x0C, 0xE1, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, +}; + +MessageTableCellWithColor::MessageTableCellWithColor() : + MessageTableCell(), + m_accessoryView() + {} + +View * MessageTableCellWithColor::accessoryView() const { + return (View *)&m_accessoryView; +} + +void MessageTableCellWithColor::setColor(int i) { + m_accessoryView.setColor(i); + MessageTextView * label = (MessageTextView*)(labelView()); + return label->setMessage(k_textForIndex[i]); +} + +MessageTableCellWithColor::ColorView::ColorView() : + m_index(0) + {} + +void MessageTableCellWithColor::ColorView::drawRect(KDContext * ctx, KDRect rect) const { + KDColor Buffer[MessageTableCellWithColor::ColorView::k_colorSize*MessageTableCellWithColor::ColorView::k_colorSize]; + KDRect Frame(bounds().x(), bounds().y() + bounds().height()/2 - k_colorSize/2, k_colorSize, k_colorSize); + ctx->blendRectWithMask(Frame, Palette::DataColor[m_index], (const uint8_t *)colorMask, Buffer); +} + +KDSize MessageTableCellWithColor::ColorView::minimalSizeForOptimalDisplay() const { + return KDSize(k_colorSize, k_colorSize); +} + +} diff --git a/apps/shared/color_cell.h b/apps/shared/color_cell.h new file mode 100644 index 00000000000..1951cd68015 --- /dev/null +++ b/apps/shared/color_cell.h @@ -0,0 +1,42 @@ +#ifndef SHARED_COLOR_CELL_CONTROLLER_H +#define SHARED_COLOR_CELL_CONTROLLER_H + +#include +#include + +namespace Shared { + +class MessageTableCellWithColor : public MessageTableCell<> { +public: + MessageTableCellWithColor(); + View * accessoryView() const override; + void setColor(int i); + int color() { return m_accessoryView.color(); } + constexpr static I18n::Message k_textForIndex[Palette::numberOfDataColors()] = { + I18n::Message::ColorRed, + I18n::Message::ColorBlue, + I18n::Message::ColorGreen, + I18n::Message::ColorYellow, + I18n::Message::ColorPurple, + I18n::Message::ColorCyan, + I18n::Message::ColorPink, + I18n::Message::ColorOrange + }; + class ColorView : public TransparentView { + public: + ColorView(); + void setColor(int i) { m_index = i; } + int color() { return m_index; } + void drawRect(KDContext * ctx, KDRect rect) const override; + KDSize minimalSizeForOptimalDisplay() const override; + constexpr static KDCoordinate k_colorSize = 12; + private: + int m_index; + }; +private: + ColorView m_accessoryView; +}; + +} + +#endif diff --git a/apps/shared/color_parameter_controller.cpp b/apps/shared/color_parameter_controller.cpp new file mode 100644 index 00000000000..2b2e4ef8d81 --- /dev/null +++ b/apps/shared/color_parameter_controller.cpp @@ -0,0 +1,86 @@ +#include "color_parameter_controller.h" + +#include "function_app.h" +#include "../apps_container.h" +#include + +namespace Shared { + +ColorParameterController::ColorParameterController(Responder * parentResponder, I18n::Message title) : + ViewController(parentResponder), + m_selectableTableView(this), + m_record(), + m_title(title) +{} + +void ColorParameterController::viewWillAppear() { + ViewController::viewWillAppear(); + // Restore the selected color + KDColor FunctionColor = function()->color(); + uint8_t cellXPosition = 0; + // TODO: Improve this if possible + for (uint8_t i = 0; i < sizeof(Palette::DataColor)/sizeof(Palette::DataColor[0]); i++) { + if (Palette::DataColor[i] == FunctionColor) { + cellXPosition = i; + break; + } + } + assert(Palette::DataColor[cellXPosition] == FunctionColor); + selectCellAtLocation(0, cellXPosition); + m_selectableTableView.reloadData(); +} + +void ColorParameterController::didBecomeFirstResponder() { + Container::activeApp()->setFirstResponder(&m_selectableTableView); +} + +bool ColorParameterController::handleEvent(Ion::Events::Event event) { + StackViewController * stack = (StackViewController *)(parentResponder()); + if (event == Ion::Events::Left) { + stack->pop(); + return true; + } + if (event == Ion::Events::OK || event == Ion::Events::EXE) { + handleEnterOnRow(selectedRow()); + stack->pop(); + return true; + } + return false; +} + +KDCoordinate ColorParameterController::cellHeight() { + return Metric::ParameterCellHeight; +} + +HighlightCell * ColorParameterController::reusableCell(int index) { + assert(index < numberOfRows()); + return &m_cells[index]; +} + +void ColorParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { + MessageTableCellWithColor * myCell = (MessageTableCellWithColor *)cell; + myCell->setColor(index); + myCell->setMessageFont(KDFont::LargeFont); + cell->reloadCell(); +} + +bool ColorParameterController::handleEnterOnRow(int rowIndex) { + function()->setColor(Palette::DataColor[rowIndex]); + return true; +} + +void ColorParameterController::setRecord(Ion::Storage::Record record) { + m_record = record; + selectCellAtLocation(0, 0); +} + +ExpiringPointer ColorParameterController::function() { + return functionStore()->modelForRecord(m_record); +} + +FunctionStore * ColorParameterController::functionStore() { + return FunctionApp::app()->functionStore(); +} + + +} diff --git a/apps/shared/color_parameter_controller.h b/apps/shared/color_parameter_controller.h new file mode 100644 index 00000000000..416e9401924 --- /dev/null +++ b/apps/shared/color_parameter_controller.h @@ -0,0 +1,44 @@ +#ifndef SHARED_COLOR_PARAM_CONTROLLER_H +#define SHARED_COLOR_PARAM_CONTROLLER_H + +#include +#include "function_store.h" +#include "color_cell.h" +#include + +namespace Shared { + +class ColorParameterController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { +public: + ColorParameterController(Responder * parentResponder, I18n::Message title); + + View * view() override { return &m_selectableTableView; } + void viewWillAppear() override; + void didBecomeFirstResponder() override; + + const char * title() override { return I18n::translate(m_title); } + + bool handleEvent(Ion::Events::Event event) override; + + TELEMETRY_ID("ColorParameter"); + + void setRecord(Ion::Storage::Record record); + + int numberOfRows() const override { return Palette::numberOfDataColors(); } + KDCoordinate cellHeight() override; + HighlightCell * reusableCell(int index) override; + int reusableCellCount() const override { return Palette::numberOfDataColors(); } // FIXME Display issue + void willDisplayCellForIndex(HighlightCell * cell, int index); +private: + bool handleEnterOnRow(int rowIndex); + FunctionStore * functionStore(); + ExpiringPointer function(); + SelectableTableView m_selectableTableView; + Ion::Storage::Record m_record; + I18n::Message m_title; + MessageTableCellWithColor m_cells[Palette::numberOfDataColors()]; +}; + +} + +#endif diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index e127feed520..39c705ecd08 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -270,7 +270,7 @@ void ContinuousFunction::rangeForDisplay(float * xMin, float * xMax, float * yMi } if (!basedOnCostlyAlgorithms(context)) { - Zoom::ValueAtAbscissa evaluation = [](float x, Context * context, const void * auxiliary) { + Zoom::ValueAtAbscissa evaluation = [](float x, Context * context, const void * auxiliary) -> float { /* When evaluating sin(x)/x close to zero using the standard sine function, * one can detect small variations, while the cardinal sine is supposed to be * locally monotonous. To smooth our such variations, we round the result of diff --git a/apps/shared/continuous_function_cache.cpp b/apps/shared/continuous_function_cache.cpp index 00a8cd687d3..5ff498052b8 100644 --- a/apps/shared/continuous_function_cache.cpp +++ b/apps/shared/continuous_function_cache.cpp @@ -65,7 +65,11 @@ void ContinuousFunctionCache::ComputeNonCartesianSteps(float * tStep, float * tC const int numberOfWholeSteps = static_cast(Graph::GraphView::k_graphStepDenominator); static_assert(numberOfCacheablePoints % numberOfWholeSteps == 0, "numberOfCacheablePoints should be a multiple of numberOfWholeSteps for optimal caching"); const int multiple = numberOfCacheablePoints / numberOfWholeSteps; + // Ignore this on Casio calculators for now, as the screen resolution breaks this + // TODO: fix this. if it's possible + #ifndef _FXCG static_assert(multiple && !(multiple & (multiple - 1)), "multiple should be a power of 2 for optimal caching"); + #endif /* Define cacheStep such that every whole graph steps are equally divided * For instance, with : * graphStepDenominator = 10.1 diff --git a/apps/shared/double_pair_store.cpp b/apps/shared/double_pair_store.cpp index b35c56de70f..f78d4f08133 100644 --- a/apps/shared/double_pair_store.cpp +++ b/apps/shared/double_pair_store.cpp @@ -115,15 +115,15 @@ bool DoublePairStore::seriesNumberOfAbscissaeGreaterOrEqualTo(int series, int i) if (count >= i) { return true; } - double currentAbsissa = m_data[series][0][j]; - bool firstOccurence = true; + double currentAbscissa = m_data[series][0][j]; + bool firstOccurrence = true; for (int k = 0; k < j; k++) { - if (m_data[series][0][k] == currentAbsissa) { - firstOccurence = false; + if (m_data[series][0][k] == currentAbscissa) { + firstOccurrence = false; break; } } - if (firstOccurence) { + if (firstOccurrence) { count++; } } diff --git a/apps/shared/expression_field_delegate_app.h b/apps/shared/expression_field_delegate_app.h index 0e175281355..466e5eb2153 100644 --- a/apps/shared/expression_field_delegate_app.h +++ b/apps/shared/expression_field_delegate_app.h @@ -9,6 +9,7 @@ namespace Shared { class ExpressionFieldDelegateApp : public TextFieldDelegateApp, public LayoutFieldDelegate { public: virtual ~ExpressionFieldDelegateApp() = default; + void layoutFieldDidReceiveNoneXNTEvent() override { AppsContainer::sharedAppsContainer()->resetXNT(); } bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) override; bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) override; protected: diff --git a/apps/shared/expression_model.cpp b/apps/shared/expression_model.cpp index 54170b6447a..d37e6b4cada 100644 --- a/apps/shared/expression_model.cpp +++ b/apps/shared/expression_model.cpp @@ -72,7 +72,7 @@ Expression ExpressionModel::expressionReduced(const Storage::Record * record, Po if (isCircularlyDefined(record, context)) { m_expression = Undefined::Builder(); } else { - m_expression = Expression::ExpressionFromAddress(expressionAddress(record), expressionSize(record)); + m_expression = Expression::ExpressionFromAddress(expressionAddress(record), expressionSize(record), record); /* 'Simplify' routine might need to call expressionReduced on the very * same function. So we need to keep a valid m_expression while executing * 'Simplify'. Thus, we use a temporary expression. */ @@ -90,7 +90,7 @@ Expression ExpressionModel::expressionReduced(const Storage::Record * record, Po Expression ExpressionModel::expressionClone(const Storage::Record * record) const { assert(record->fullName() != nullptr); /* A new Expression has to be created at each call (because it might be tempered with after calling) */ - return Expression::ExpressionFromAddress(expressionAddress(record), expressionSize(record)); + return Expression::ExpressionFromAddress(expressionAddress(record), expressionSize(record), record); /* TODO * The substitution of UCodePointUnknown back and forth is done in the * methods text, setContent (through BuildExpressionFromText), layout and @@ -125,6 +125,39 @@ Ion::Storage::Record::ErrorStatus ExpressionModel::setExpressionContent(Ion::Sto Ion::Storage::Record::Data newData = record->value(); size_t previousExpressionSize = expressionSize(record); size_t newExpressionSize = newExpression.isUninitialized() ? 0 : newExpression.size(); +#ifdef STRING_STORAGE + size_t stringsize = 0; + char buf[1024] = {0}; + char repl = 0; + buf[0] = '"'; + if (!newExpression.isUninitialized()) { + size_t l = newExpression.serialize(buf+1,sizeof(buf)-2); + if (l >= sizeof(buf) - 3) { + newExpressionSize = 0; + } else { + buf[l + 1] = '"'; + // replace 0x1 by x for func or n for seq + const char * name = record->fullName(); + int namel = strlen(name); + if (namel > 4 && strncmp(name + namel - 4, ".seq", 4) == 0) { + repl = 'n'; + } else if (namel > 5 && strncmp(name + namel - 5, ".func", 5) == 0) { + repl = 'x'; + } + if (repl) { + for (char * ptr = buf; *ptr; ++ptr) { + if (*ptr == 1) { + *ptr = repl; + } + } + } + stringsize= l + 3; // 2 quotes and 0 at end + if (newExpressionSize < stringsize) { + newExpressionSize = stringsize; + } + } + } +#endif size_t previousDataSize = newData.size; size_t newDataSize = previousDataSize - previousExpressionSize + newExpressionSize; void * expAddress = expressionAddress(record); @@ -140,10 +173,16 @@ Ion::Storage::Record::ErrorStatus ExpressionModel::setExpressionContent(Ion::Sto * (as it is sometimes computed from metadata). Thus, the expression address * is given as a parameter to updateNewDataWithExpression. */ updateNewDataWithExpression(record, newExpression, expAddress, newExpressionSize, previousExpressionSize); +#ifdef STRING_STORAGE + if (stringsize && stringsizesetValue(newData); - // Any error would have occured at the first call to setValue + // Any error would have occurred at the first call to setValue assert(error == Ion::Storage::Record::ErrorStatus::None); /* Here we delete only the elements relative to the expression model kept in diff --git a/apps/shared/expression_model_handle.h b/apps/shared/expression_model_handle.h index 8d6229a1825..812f218a6e8 100644 --- a/apps/shared/expression_model_handle.h +++ b/apps/shared/expression_model_handle.h @@ -23,7 +23,6 @@ class ExpressionModelHandle : public Ion::Storage::Record { * not defined. We thus have to keep both methods. */ virtual bool isDefined(); virtual bool isEmpty(); - virtual bool shouldBeClearedBeforeRemove() { return !isEmpty(); } /* tidy is responsible to tidy the whole model whereas tidyExpressionModel * tidies only the members associated with the ExpressionModel. In * ExpressionModel, tidy and tidyExpressionModel trigger the same diff --git a/apps/shared/expression_model_list_controller.cpp b/apps/shared/expression_model_list_controller.cpp index dc1c18d0f52..3defcde2e2e 100644 --- a/apps/shared/expression_model_list_controller.cpp +++ b/apps/shared/expression_model_list_controller.cpp @@ -184,18 +184,19 @@ bool ExpressionModelListController::handleEventOnExpression(Ion::Events::Event e } if (event == Ion::Events::Backspace && !isAddEmptyRow(selectedRow())) { Ion::Storage::Record record = modelStore()->recordAtIndex(modelIndexForRow(selectedRow())); - ExpiringPointer model = modelStore()->modelForRecord(record); - if (model->shouldBeClearedBeforeRemove()) { - reinitSelectedExpression(model); - } else { - if (removeModelRow(record)) { - int newSelectedRow = selectedRow() >= numberOfExpressionRows() ? numberOfExpressionRows()-1 : selectedRow(); - selectCellAtLocation(selectedColumn(), newSelectedRow); - selectableTableView()->reloadData(); - } + ExpiringPointer model = modelStore()->modelForRecord(record); + if (removeModelRow(record)) { + int newSelectedRow = selectedRow() >= numberOfExpressionRows() ? numberOfExpressionRows()-1 : selectedRow(); + selectCellAtLocation(selectedColumn(), newSelectedRow); + selectableTableView()->reloadData(); } return true; } + if (event == Ion::Events::ShiftBack) { + Ion::Storage::sharedStorage()->reinsertTrash(recordExtension()); + selectableTableView()->reloadData(); + return true; + } if ((event.hasText() || event == Ion::Events::XNT || event == Ion::Events::Paste || event == Ion::Events::Toolbox || event == Ion::Events::Var) && !isAddEmptyRow(selectedRow())) { editExpression(event); diff --git a/apps/shared/expression_model_list_controller.h b/apps/shared/expression_model_list_controller.h index 45162107c9d..9ced3980a18 100644 --- a/apps/shared/expression_model_list_controller.h +++ b/apps/shared/expression_model_list_controller.h @@ -11,6 +11,7 @@ class ExpressionModelListController : public ViewController, public SelectableTa public: ExpressionModelListController(Responder * parentResponder, I18n::Message text); protected: + virtual const char * recordExtension() const = 0; static constexpr KDCoordinate k_expressionMargin = 5; // SelectableTableViewDelegate void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; diff --git a/apps/shared/function.cpp b/apps/shared/function.cpp index 3f3150d6f14..7248b6b6a71 100644 --- a/apps/shared/function.cpp +++ b/apps/shared/function.cpp @@ -55,6 +55,10 @@ void Function::setActive(bool active) { } } +void Function::setColor(KDColor color) { + recordData()->setColor(color); +} + int Function::printValue(double cursorT, double cursorX, double cursorY, char * buffer, int bufferSize, int precision, Poincare::Context * context) { return PoincareHelpers::ConvertFloatToText(cursorY, buffer, bufferSize, precision); } diff --git a/apps/shared/function.h b/apps/shared/function.h index a5daa9c5917..6fa039bac0e 100644 --- a/apps/shared/function.h +++ b/apps/shared/function.h @@ -36,6 +36,7 @@ class Function : public ExpressionModelHandle { bool isActive() const; KDColor color() const; void setActive(bool active); + void setColor(KDColor color); // Definition Interval virtual bool shouldClipTRangeToXRange() const { return true; } // Returns true if the function will not be displayed if t is outside x range. @@ -76,6 +77,7 @@ class Function : public ExpressionModelHandle { KDColor color() const { return KDColor::RGB16(m_color); } + void setColor(KDColor color) { m_color = color; } bool isActive() const { return m_active; } void setActive(bool active) { m_active = active; } private: diff --git a/apps/shared/function_app.cpp b/apps/shared/function_app.cpp index fbdc4f43aeb..a63ca328430 100644 --- a/apps/shared/function_app.cpp +++ b/apps/shared/function_app.cpp @@ -31,9 +31,13 @@ void FunctionApp::willBecomeInactive() { ::App::willBecomeInactive(); } - -bool FunctionApp::isAcceptableExpression(const Expression exp) { - return TextFieldDelegateApp::isAcceptableExpression(exp) && ExpressionCanBeSerialized(exp, false, Expression(), localContext()); +bool FunctionApp::isAcceptableExpression(const Poincare::Expression expression) { + /* We forbid functions whose type is equal because the input "2+f(3)" would be + * simplify to an expression with an nested equal node which makes no sense. */ + if (!TextFieldDelegateApp::ExpressionCanBeSerialized(expression, false, Expression(), localContext()) || expression.type() == ExpressionNode::Type::Equal) { + return false; + } + return TextFieldDelegateApp::isAcceptableExpression(expression); } } diff --git a/apps/shared/function_curve_parameter_controller.h b/apps/shared/function_curve_parameter_controller.h index 96e8668445f..a720e2e9d39 100644 --- a/apps/shared/function_curve_parameter_controller.h +++ b/apps/shared/function_curve_parameter_controller.h @@ -16,7 +16,7 @@ class FunctionCurveParameterController : public ViewController, public SimpleLis void setRecord(Ion::Storage::Record record) { m_record = record; } protected: bool handleGotoSelection(); - MessageTableCellWithChevron m_goToCell; + MessageTableCellWithChevron<> m_goToCell; SelectableTableView m_selectableTableView; Ion::Storage::Record m_record; private: diff --git a/apps/shared/function_list_controller.cpp b/apps/shared/function_list_controller.cpp index d8dbf2ad7a3..4083ab9551d 100644 --- a/apps/shared/function_list_controller.cpp +++ b/apps/shared/function_list_controller.cpp @@ -200,6 +200,11 @@ bool FunctionListController::handleEvent(Ion::Events::Event event) { } return true; } + if (event == Ion::Events::ShiftBack) { + Ion::Storage::sharedStorage()->reinsertTrash(recordExtension()); + selectableTableView()->reloadData(); + return true; + } return false; } diff --git a/apps/shared/function_list_controller.h b/apps/shared/function_list_controller.h index da6a7036a26..be62a843544 100644 --- a/apps/shared/function_list_controller.h +++ b/apps/shared/function_list_controller.h @@ -7,6 +7,7 @@ #include "list_parameter_controller.h" #include "expression_model_list_controller.h" #include +#include namespace Shared { diff --git a/apps/shared/global_context.cpp b/apps/shared/global_context.cpp index 5df9889538f..0bf41617540 100644 --- a/apps/shared/global_context.cpp +++ b/apps/shared/global_context.cpp @@ -128,20 +128,27 @@ const Expression GlobalContext::ExpressionForSequence(const SymbolAbstract & sym if (!rank.isUninitialized()) { bool rankIsInteger = false; double rankValue = rank.approximateToScalar(ctx, Poincare::Preferences::sharedPreferences()->complexFormat(), Poincare::Preferences::sharedPreferences()->angleUnit()); + int rankValueFloor = std::floor(rankValue); if (rank.type() == ExpressionNode::Type::Rational) { Rational n = static_cast(rank); rankIsInteger = n.isInteger(); } else if (!std::isnan(unknownSymbolValue)) { /* If unknownSymbolValue is not nan, then we are in the graph app. In order * to allow functions like f(x) = u(x+0.5) to be ploted, we need to - * approximate the rank and check if it is an integer. Unfortunatly this + * approximate the rank and check if it is an integer. Unfortunately this * leads to some edge cases were, because of quantification, we have * floor(x) = x while x is not integer.*/ - rankIsInteger = std::floor(rankValue) == rankValue; + rankIsInteger = rankValueFloor == rankValue; } - if (rankIsInteger && !seq.badlyReferencesItself(ctx)) { - SequenceContext sqctx(ctx, sequenceStore()); - return Float::Builder(seq.evaluateXYAtParameter(rankValue, &sqctx).x2()); + if (rankIsInteger) { + if (rankValueFloor - seq.initialRank() < (int) seq.type()) { // Seq can reference itself but be defined explicitly for first values + assert(rankValueFloor - seq.initialRank() == 0 || rankValueFloor - seq.initialRank() == 1); + return rankValueFloor - seq.initialRank() == 0 ? seq.firstInitialConditionExpressionClone() : seq.secondInitialConditionExpressionClone(); + } + if (!seq.badlyReferencesItself(ctx)) { + SequenceContext sqctx(ctx, sequenceStore()); + return Float::Builder(seq.evaluateXYAtParameter(rankValue, &sqctx).x2()); + } } } return Float::Builder(NAN); diff --git a/apps/shared/layout_field_delegate.h b/apps/shared/layout_field_delegate.h index 79f25607d6a..5b2bb1c3c5e 100644 --- a/apps/shared/layout_field_delegate.h +++ b/apps/shared/layout_field_delegate.h @@ -2,11 +2,13 @@ #define SHARED_LAYOUT_FIELD_DELEGATE_H #include "expression_field_delegate_app.h" +#include "../apps_container.h" namespace Shared { class LayoutFieldDelegate : public ::LayoutFieldDelegate { public: + void layoutFieldDidReceiveNoneXNTEvent() override { AppsContainer::sharedAppsContainer()->resetXNT(); } bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) override; bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) override; bool layoutFieldDidFinishEditing(LayoutField * layoutField, Poincare::Layout layoutR, Ion::Events::Event event) override; diff --git a/apps/shared/list_parameter_controller.cpp b/apps/shared/list_parameter_controller.cpp index 27c11745f71..e55e3bf2a8d 100644 --- a/apps/shared/list_parameter_controller.cpp +++ b/apps/shared/list_parameter_controller.cpp @@ -8,12 +8,12 @@ ListParameterController::ListParameterController(Responder * parentResponder, I1 ViewController(parentResponder), m_selectableTableView(this, this, this, tableDelegate), m_record(), -#if FUNCTION_COLOR_CHOICE - m_colorCell(functionColorMessage), -#endif + m_colorCell(), m_enableCell(I18n::Message::ActivateDeactivate), - m_deleteCell(deleteFunctionMessage) + m_deleteCell(deleteFunctionMessage), + m_colorParameterController(parentResponder, functionColorMessage) { + m_colorCell.setMessage(functionColorMessage); } const char * ListParameterController::title() { @@ -38,6 +38,16 @@ void ListParameterController::willDisplayCellForIndex(HighlightCell * cell, int if (cell == &m_enableCell) { SwitchView * switchView = (SwitchView *)m_enableCell.accessoryView(); switchView->setState(function()->isActive()); + } else if(cell == &m_colorCell) { + int index = -1; + KDColor color = function()->color(); + for(int i = 0; i < Palette::numberOfDataColors(); i++) { + if(color == Palette::DataColor[i]) { + index = i; + } + } + assert(index >= 0); + m_colorCell.setSubtitle(MessageTableCellWithColor::k_textForIndex[index]); } } @@ -64,11 +74,7 @@ int ListParameterController::indexFromCumulatedHeight(KDCoordinate offsetY) { HighlightCell * ListParameterController::reusableCell(int index, int type) { assert(index == 0); assert(index < totalNumberOfCells()); -#if FUNCTION_COLOR_CHOICE HighlightCell * cells[] = {&m_colorCell, &m_enableCell, &m_deleteCell}; -#else - HighlightCell * cells[] = {&m_enableCell, &m_deleteCell}; -#endif return cells[type]; } @@ -78,22 +84,17 @@ int ListParameterController::typeAtLocation(int i, int j) { bool ListParameterController::handleEnterOnRow(int rowIndex) { switch (rowIndex) { -#if FUNCTION_COLOR_CHOICE - case 0: - /* TODO: implement function color choice */ + case 0: { + StackViewController * stack = (StackViewController *)(parentResponder()); + m_colorParameterController.setRecord(m_record); + stack->push(&m_colorParameterController); return true; + } case 1: -#else - case 0: -#endif function()->setActive(!function()->isActive()); m_selectableTableView.reloadData(); return true; -#if FUNCTION_COLOR_CHOICE - case 2: -#else - case 1: -#endif + case 2: { assert(functionStore()->numberOfModels() > 0); functionStore()->removeModel(m_record); diff --git a/apps/shared/list_parameter_controller.h b/apps/shared/list_parameter_controller.h index 4ef2b616ec4..d948073bb6f 100644 --- a/apps/shared/list_parameter_controller.h +++ b/apps/shared/list_parameter_controller.h @@ -3,6 +3,7 @@ #include #include "function_store.h" +#include "color_parameter_controller.h" #include namespace Shared { @@ -31,22 +32,17 @@ class ListParameterController : public ViewController, public ListViewDataSource protected: virtual bool handleEnterOnRow(int rowIndex); virtual int totalNumberOfCells() const { -#if FUNCTION_COLOR_CHOICE return 3; -#else - return 2; -#endif } FunctionStore * functionStore(); ExpiringPointer function(); SelectableTableView m_selectableTableView; Ion::Storage::Record m_record; private: -#if FUNCTION_COLOR_CHOICE - MessageTableCellWithChevron m_colorCell; -#endif + MessageTableCellWithChevronAndMessage m_colorCell; MessageTableCellWithSwitch m_enableCell; - MessageTableCell m_deleteCell; + MessageTableCell<> m_deleteCell; + ColorParameterController m_colorParameterController; }; } diff --git a/apps/shared/localization_controller.cpp b/apps/shared/localization_controller.cpp index f518999b3ef..b1476aa3dcf 100644 --- a/apps/shared/localization_controller.cpp +++ b/apps/shared/localization_controller.cpp @@ -18,7 +18,7 @@ LocalizationController::ContentView::ContentView(LocalizationController * contro assert(k_numberOfCountryWarningLines == 2); // textMessages is not overflowed I18n::Message textMessages[k_numberOfCountryWarningLines] = {I18n::Message::CountryWarning1, I18n::Message::CountryWarning2}; for (int i = 0; i < k_numberOfCountryWarningLines; i++) { - m_countryWarningLines[i].setBackgroundColor(Palette::BackgroundHard); + m_countryWarningLines[i].setBackgroundColor(Palette::BackgroundApps); m_countryWarningLines[i].setFont(KDFont::SmallFont); m_countryWarningLines[i].setAlignment(0.5f, 0.5f); m_countryWarningLines[i].setMessage(textMessages[i]); @@ -191,10 +191,10 @@ bool LocalizationController::handleEvent(Ion::Events::Event event) { void LocalizationController::willDisplayCellForIndex(HighlightCell * cell, int index) { if (mode() == Mode::Language) { - static_cast(cell)->setMessage(I18n::LanguageNames[index]); + static_cast *>(cell)->setMessage(I18n::LanguageNames[index]); return; } assert(mode() == Mode::Country); - static_cast(cell)->setMessage(I18n::CountryNames[static_cast(CountryAtIndex(index))]); + static_cast *>(cell)->setMessage(I18n::CountryNames[static_cast(CountryAtIndex(index))]); } } diff --git a/apps/shared/localization_controller.h b/apps/shared/localization_controller.h index 327d3ef5d3c..ddc8c1d08da 100644 --- a/apps/shared/localization_controller.h +++ b/apps/shared/localization_controller.h @@ -71,7 +71,7 @@ class LocalizationController : public ViewController, public SimpleListViewDataS private: static constexpr int k_numberOfCells = I18n::NumberOfLanguages > I18n::NumberOfCountries ? I18n::NumberOfLanguages : I18n::NumberOfCountries; - MessageTableCell m_cells[k_numberOfCells]; + MessageTableCell<> m_cells[k_numberOfCells]; Mode m_mode; }; diff --git a/apps/shared/message_view.cpp b/apps/shared/message_view.cpp index 3bc257ab0f4..05975da8e95 100644 --- a/apps/shared/message_view.cpp +++ b/apps/shared/message_view.cpp @@ -1,13 +1,14 @@ #include "message_view.h" #include -MessageView::MessageView(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages) { +MessageView::MessageView(I18n::Message * messages, KDColor * fgcolors, KDColor * bgcolors, uint8_t numberOfMessages) { m_numberOfMessages = numberOfMessages < k_maxNumberOfMessages ? numberOfMessages : k_maxNumberOfMessages; for (uint8_t i = 0; i < m_numberOfMessages; i++) { m_messageTextViews[i].setFont(i == 0 ? KDFont::LargeFont : KDFont::SmallFont); m_messageTextViews[i].setMessage(messages[i]); m_messageTextViews[i].setAlignment(0.5f, 0.5f); - m_messageTextViews[i].setTextColor(colors[i]); + m_messageTextViews[i].setTextColor(fgcolors[i]); + m_messageTextViews[i].setBackgroundColor(bgcolors[i]); } } diff --git a/apps/shared/message_view.h b/apps/shared/message_view.h index 41fb9e648a1..f6b773e95dd 100644 --- a/apps/shared/message_view.h +++ b/apps/shared/message_view.h @@ -5,7 +5,7 @@ class MessageView : public View { public: - MessageView(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages); + MessageView(I18n::Message * messages, KDColor * fgcolors, KDColor * bgcolors, uint8_t numberOfMessages); void drawRect(KDContext * ctx, KDRect rect) const override; protected: int numberOfSubviews() const override { return m_numberOfMessages; } diff --git a/apps/shared/scrollable_multiple_expressions_view.cpp b/apps/shared/scrollable_multiple_expressions_view.cpp index 5d0331f5422..49021e1a888 100644 --- a/apps/shared/scrollable_multiple_expressions_view.cpp +++ b/apps/shared/scrollable_multiple_expressions_view.cpp @@ -24,13 +24,13 @@ void AbstractScrollableMultipleExpressionsView::ContentCell::setHighlighted(bool // Do not call HighlightCell::setHighlighted to avoid marking all cell as dirty m_highlighted = highlight; KDColor defaultColor = backgroundColor(); - KDColor color = highlight && m_selectedSubviewPosition == SubviewPosition::Center ? Palette::ExpressionInputBackground : defaultColor; + KDColor color = highlight && m_selectedSubviewPosition == SubviewPosition::Center ? Palette::Select : defaultColor; m_centeredExpressionView.setBackgroundColor(color); - color = highlight && m_selectedSubviewPosition == SubviewPosition::Right ? Palette::ExpressionInputBackground : defaultColor; + color = highlight && m_selectedSubviewPosition == SubviewPosition::Right ? Palette::Select : defaultColor; m_rightExpressionView.setBackgroundColor(color); m_approximateSign.setBackgroundColor(defaultColor); if (leftExpressionView()) { - color = highlight && m_selectedSubviewPosition == SubviewPosition::Left ? Palette::ExpressionInputBackground : defaultColor; + color = highlight && m_selectedSubviewPosition == SubviewPosition::Left ? Palette::Select : defaultColor; leftExpressionView()->setBackgroundColor(color); } } diff --git a/apps/shared/sequence_context.h b/apps/shared/sequence_context.h index 83c8d8e9f04..f2995a58e82 100644 --- a/apps/shared/sequence_context.h +++ b/apps/shared/sequence_context.h @@ -62,7 +62,7 @@ class SequenceContext : public Poincare::ContextWithParent { m_sequenceStore(sequenceStore) {} /* expressionForSymbolAbstract & setExpressionForSymbolAbstractName directly call the parent * context respective methods. Indeed, special chars like n, u(n), u(n+1), - * v(n), v(n+1) are taken into accound only when evaluating sequences which + * v(n), v(n+1) are taken into account only when evaluating sequences which * is done in another context. */ template T valueOfCommonRankSequenceAtPreviousRank(int sequenceIndex, int rank) { return static_cast*>(helper())->valueOfCommonRankSequenceAtPreviousRank(sequenceIndex, rank); diff --git a/apps/shared/store_controller.cpp b/apps/shared/store_controller.cpp index ee4244f3bbd..ebabb133983 100644 --- a/apps/shared/store_controller.cpp +++ b/apps/shared/store_controller.cpp @@ -232,7 +232,7 @@ bool StoreController::privateFillColumnWithFormula(Expression formula, Expressio variables[0][0] = 0; AppsContainer * appsContainer = AppsContainer::sharedAppsContainer(); int nbOfVariables = formula.getVariables(appsContainer->globalContext(), isVariable, (char *)variables, k_maxSizeOfStoreSymbols); - (void) nbOfVariables; // Remove compilation warning of nused variable + (void) nbOfVariables; // Remove compilation warning of unused variable assert(nbOfVariables >= 0); int numberOfValuesToCompute = -1; int index = 0; diff --git a/apps/shared/store_controller.h b/apps/shared/store_controller.h index 1083f8201f5..d0eebb316cb 100644 --- a/apps/shared/store_controller.h +++ b/apps/shared/store_controller.h @@ -49,7 +49,12 @@ class StoreController : public EditableCellTableViewController, public ButtonRow static constexpr KDCoordinate k_cellWidth = Poincare::PrintFloat::glyphLengthForFloatWithPrecision(Poincare::Preferences::LargeNumberOfSignificantDigits) * 7 + 2*Metric::CellMargin + Metric::TableSeparatorThickness; // KDFont::SmallFont->glyphSize().width() = 7 constexpr static int k_maxNumberOfEditableCells = (Ion::Display::Width/k_cellWidth+2) * ((Ion::Display::Height - Metric::TitleBarHeight - Metric::TabHeight)/k_cellHeight+2); + #ifndef _FXCG constexpr static int k_numberOfTitleCells = 4; + #else + // This is different here due to the changed screen resolution + constexpr static int k_numberOfTitleCells = 5; + #endif static constexpr int k_titleCellType = 0; static constexpr int k_editableCellType = 1; diff --git a/apps/shared/store_parameter_controller.h b/apps/shared/store_parameter_controller.h index efc8c6f1273..4302efcc84a 100644 --- a/apps/shared/store_parameter_controller.h +++ b/apps/shared/store_parameter_controller.h @@ -41,7 +41,7 @@ class StoreParameterController : public ViewController, public ListViewDataSourc private: virtual I18n::Message sortMessage() { return m_xColumnSelected ? I18n::Message::SortValues : I18n::Message::SortSizes; } constexpr static int k_totalNumberOfCell = 3; - MessageTableCell m_cells[k_totalNumberOfCell]; + MessageTableCell<> m_cells[k_totalNumberOfCell]; StoreController * m_storeController; bool m_xColumnSelected; }; diff --git a/apps/shared/sum_graph_controller.cpp b/apps/shared/sum_graph_controller.cpp index 423582e900c..08ddd6926b4 100644 --- a/apps/shared/sum_graph_controller.cpp +++ b/apps/shared/sum_graph_controller.cpp @@ -232,12 +232,12 @@ void SumGraphController::LegendView::layoutSubviews(bool force) { void SumGraphController::LegendView::layoutSubviews(Step step, bool force) { KDCoordinate width = bounds().width(); - KDCoordinate heigth = bounds().height(); + KDCoordinate height = bounds().height(); KDSize legendSize = m_legend.minimalSizeForOptimalDisplay(); if (legendSize.width() > 0) { m_sum.setFrame(KDRect(0, k_symbolHeightMargin, width-legendSize.width(), m_sum.minimalSizeForOptimalDisplay().height()), force); - m_legend.setFrame(KDRect(width-legendSize.width(), 0, legendSize.width(), heigth), force); + m_legend.setFrame(KDRect(width-legendSize.width(), 0, legendSize.width(), height), force); } else { m_sum.setFrame(bounds(), force); m_legend.setFrame(KDRectZero, force); diff --git a/apps/shared/text_field_delegate.h b/apps/shared/text_field_delegate.h index bc148e95a6c..cd04bcffb6c 100644 --- a/apps/shared/text_field_delegate.h +++ b/apps/shared/text_field_delegate.h @@ -2,11 +2,13 @@ #define SHARED_TEXT_FIELD_DELEGATE_H #include "text_field_delegate_app.h" +#include "../apps_container.h" namespace Shared { class TextFieldDelegate : public ::TextFieldDelegate { public: + void textFieldDidReceiveNoneXNTEvent() override { AppsContainer::sharedAppsContainer()->resetXNT(); } bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; protected: diff --git a/apps/shared/text_field_delegate_app.cpp b/apps/shared/text_field_delegate_app.cpp index e66a874224d..68ae84d0b3e 100644 --- a/apps/shared/text_field_delegate_app.cpp +++ b/apps/shared/text_field_delegate_app.cpp @@ -72,10 +72,12 @@ bool TextFieldDelegateApp::fieldDidReceiveEvent(EditableField * field, Responder if (XNTCanBeOverriden()) { xnt = field->XNTCodePoint(xnt); } + bool shouldRemoveLastCharacter = false; + xnt = AppsContainer::sharedAppsContainer()->XNT(xnt, &shouldRemoveLastCharacter); size_t length = UTF8Decoder::CodePointToChars(xnt, buffer, bufferSize); assert(length < bufferSize - 1); buffer[length] = 0; - return field->handleEventWithText(buffer); + return field->handleEventWithText(buffer, false, false, shouldRemoveLastCharacter); } return false; } @@ -85,7 +87,13 @@ bool TextFieldDelegateApp::isFinishingEvent(Ion::Events::Event event) { } bool TextFieldDelegateApp::isAcceptableExpression(const Expression exp) { - return !(exp.isUninitialized() || exp.type() == ExpressionNode::Type::Store || exp.type() == ExpressionNode::Type::Equal); + if (exp.isUninitialized()) { + return false; + } + if (exp.type() == ExpressionNode::Type::Store) { + return false; + } + return true; } bool TextFieldDelegateApp::ExpressionCanBeSerialized(const Expression expression, bool replaceAns, Expression ansExpression, Context * context) { diff --git a/apps/shared/text_field_delegate_app.h b/apps/shared/text_field_delegate_app.h index 0dd472e78e4..a33adffde77 100644 --- a/apps/shared/text_field_delegate_app.h +++ b/apps/shared/text_field_delegate_app.h @@ -5,6 +5,7 @@ #include "input_event_handler_delegate_app.h" #include #include +#include "../apps_container.h" class EditableField; @@ -16,6 +17,7 @@ class TextFieldDelegateApp : public InputEventHandlerDelegateApp, public TextFie Poincare::Context * localContext() override; virtual bool XNTCanBeOverriden() const { return true; } virtual CodePoint XNT() { return 'x'; } + virtual void textFieldDidReceiveNoneXNTEvent() override { AppsContainer::sharedAppsContainer()->resetXNT(); } bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; bool isAcceptableText(const char * text); diff --git a/apps/shared/values_function_parameter_controller.h b/apps/shared/values_function_parameter_controller.h index f4365312300..940b769844d 100644 --- a/apps/shared/values_function_parameter_controller.h +++ b/apps/shared/values_function_parameter_controller.h @@ -29,7 +29,7 @@ class ValuesFunctionParameterController : public ViewController, public SimpleLi int reusableCellCount() const override { return 1; } void setRecord(Ion::Storage::Record record) { m_record = record; } protected: - MessageTableCellWithChevron m_copyColumn; + MessageTableCellWithChevron<> m_copyColumn; SelectableTableView m_selectableTableView; Ion::Storage::Record m_record; private: diff --git a/apps/shared/values_parameter_controller.cpp b/apps/shared/values_parameter_controller.cpp index 272808ae947..edfeeb9f3c6 100644 --- a/apps/shared/values_parameter_controller.cpp +++ b/apps/shared/values_parameter_controller.cpp @@ -24,7 +24,7 @@ View * ValuesParameterController::view() { } void ValuesParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { - MessageTableCell * myCell = (MessageTableCell *)cell; + MessageTableCell<> * myCell = (MessageTableCell<> *)cell; #if COPY_COLUMN I18n::Message labels[k_totalNumberOfCell] = {I18n::Message::ClearColumn, I18n::Message::CopyColumnInList, I18n::Message::IntervalSet}; #else diff --git a/apps/shared/values_parameter_controller.h b/apps/shared/values_parameter_controller.h index 5cc842f72df..777d1d205fb 100644 --- a/apps/shared/values_parameter_controller.h +++ b/apps/shared/values_parameter_controller.h @@ -21,13 +21,13 @@ class ValuesParameterController : public ViewController, public SimpleListViewDa private: #if COPY_COLUMN constexpr static int k_totalNumberOfCell = 3; - MessageTableCellWithChevron m_copyColumn; + MessageTableCellWithChevron<> m_copyColumn; #else constexpr static int k_totalNumberOfCell = 2; #endif I18n::Message m_pageTitle; - MessageTableCell m_deleteColumn; - MessageTableCellWithChevron m_setInterval; + MessageTableCell<> m_deleteColumn; + MessageTableCellWithChevron<> m_setInterval; SelectableTableView m_selectableTableView; }; diff --git a/apps/solver/app.cpp b/apps/solver/app.cpp index 11f96a32ae6..1c88b7df52c 100644 --- a/apps/solver/app.cpp +++ b/apps/solver/app.cpp @@ -68,10 +68,4 @@ void App::willBecomeInactive() { ::App::willBecomeInactive(); } - -bool App::isAcceptableExpression(const Poincare::Expression exp) { - return TextFieldDelegateApp::ExpressionCanBeSerialized(exp, false, Poincare::Expression(), localContext()) - && !(exp.isUninitialized() || exp.type() == Poincare::ExpressionNode::Type::Store); -} - } diff --git a/apps/solver/app.h b/apps/solver/app.h index a4919978e1b..898c3c6b804 100644 --- a/apps/solver/app.h +++ b/apps/solver/app.h @@ -47,9 +47,6 @@ class App : public Shared::ExpressionFieldDelegateApp { void willBecomeInactive() override; TELEMETRY_ID("Solver"); private: - // TextFieldDelegateApp - bool isAcceptableExpression(const Poincare::Expression expression) override; - App(Snapshot * snapshot); SolutionsController m_solutionsController; IntervalController m_intervalController; diff --git a/apps/solver/base.de.i18n b/apps/solver/base.de.i18n index f3a41673887..89b770872f1 100644 --- a/apps/solver/base.de.i18n +++ b/apps/solver/base.de.i18n @@ -11,7 +11,7 @@ TooManyVariables = "Es gibt zu viele Unbekannte" NonLinearSystem = "Das System ist nicht linear" Solution = "Lösung" ApproximateSolution = "Ungefähre Lösung" -SearchInverval = "Lösungssuche Intervall" +SearchInterval = "Lösungssuche Intervall" NoSolutionSystem = "Das System hat keine Lösung" NoSolutionEquation = "Die Gleichung hat keine Lösung" NoSolutionInterval = "Keine Lösung im Intervall gefunden" diff --git a/apps/solver/base.en.i18n b/apps/solver/base.en.i18n index 017cad04d3d..b8239dae01c 100644 --- a/apps/solver/base.en.i18n +++ b/apps/solver/base.en.i18n @@ -11,7 +11,7 @@ TooManyVariables = "There are too many unknowns" NonLinearSystem = "The system is not linear" Solution = "Solution" ApproximateSolution = "Approximate solution" -SearchInverval = "Search interval" +SearchInterval = "Search interval" NoSolutionSystem = "The system has no solution" NoSolutionEquation = "The equation has no solution" NoSolutionInterval = "No solution found in the interval" diff --git a/apps/solver/base.es.i18n b/apps/solver/base.es.i18n index ea595cf2dcd..40c567c9d1a 100644 --- a/apps/solver/base.es.i18n +++ b/apps/solver/base.es.i18n @@ -11,7 +11,7 @@ TooManyVariables = "Hay demasiadas incógnitas" NonLinearSystem = "El sistema no es lineal" Solution = "Solución" ApproximateSolution = "Solución aproximada" -SearchInverval = "Intervalo de búsqueda" +SearchInterval = "Intervalo de búsqueda" NoSolutionSystem = "El sistema no tiene solución" NoSolutionEquation = "La ecuación no tiene solución" NoSolutionInterval = "Ninguna solución encontrada en el intervalo" diff --git a/apps/solver/base.fr.i18n b/apps/solver/base.fr.i18n index c337a0525d3..424c15cfebe 100644 --- a/apps/solver/base.fr.i18n +++ b/apps/solver/base.fr.i18n @@ -11,7 +11,7 @@ TooManyVariables = "Le nombre d'inconnues est trop grand" NonLinearSystem = "Le système n'est pas linéaire" Solution = "Solution" ApproximateSolution = "Solution approchée" -SearchInverval = "Intervalle de recherche" +SearchInterval = "Intervalle de recherche" NoSolutionSystem = "Le système n'admet aucune solution" NoSolutionEquation = "L'équation n'admet aucune solution" NoSolutionInterval = "Aucune solution trouvée dans cet intervalle" diff --git a/apps/solver/base.hu.i18n b/apps/solver/base.hu.i18n index baba451b794..a36ed7ad4cf 100644 --- a/apps/solver/base.hu.i18n +++ b/apps/solver/base.hu.i18n @@ -11,7 +11,7 @@ TooManyVariables = "Túl sok ismeretlen van" NonLinearSystem = "A rendszer nem lineáris" Solution = "Megoldás" ApproximateSolution = "Hozzávetöleges megoldás" -SearchInverval = "Keresési intervallum" +SearchInterval = "Keresési intervallum" NoSolutionSystem = "A rendszernek nincs megoldása" NoSolutionEquation = "Az egyenletnek nincs megoldása" NoSolutionInterval = "Nincs megoldás ebben az intervallumban" diff --git a/apps/solver/base.it.i18n b/apps/solver/base.it.i18n index 5b6f79dbfa3..bf0848d14e2 100644 --- a/apps/solver/base.it.i18n +++ b/apps/solver/base.it.i18n @@ -11,7 +11,7 @@ TooManyVariables = "Il numero di incognite è troppo elevato" NonLinearSystem = "Il sistema non è lineare" Solution = "Soluzione" ApproximateSolution = "Soluzione approssimata" -SearchInverval = "Intervallo di ricerca" +SearchInterval = "Intervallo di ricerca" NoSolutionSystem = "Il sistema non ammette nessuna soluzione" NoSolutionEquation = "L'equazione non ammette nessuna soluzione" NoSolutionInterval = "Nessuna soluzione trovata dentro questo intervallo" diff --git a/apps/solver/base.nl.i18n b/apps/solver/base.nl.i18n index 89e9d3d4da3..b6c838f8566 100644 --- a/apps/solver/base.nl.i18n +++ b/apps/solver/base.nl.i18n @@ -11,7 +11,7 @@ TooManyVariables = "Er zijn te veel onbekenden" NonLinearSystem = "Het stelsel is niet lineair" Solution = "Oplossing" ApproximateSolution = "Benaderde oplossing" -SearchInverval = "Intervalbepaling" +SearchInterval = "Intervalbepaling" NoSolutionSystem = "Het stelsel heeft geen oplossing" NoSolutionEquation = "De vergelijking heeft geen oplossing" NoSolutionInterval = "Geen oplossing gevonden binnen het interval" diff --git a/apps/solver/base.pt.i18n b/apps/solver/base.pt.i18n index 356eaa24f01..e3e0c19de15 100644 --- a/apps/solver/base.pt.i18n +++ b/apps/solver/base.pt.i18n @@ -11,7 +11,7 @@ TooManyVariables = "Existem muitas incógnitas" NonLinearSystem = "O sistema não é linear" Solution = "Solução" ApproximateSolution = "Solução aproximada" -SearchInverval = "Intervalo de pesquisa" +SearchInterval = "Intervalo de pesquisa" NoSolutionSystem = "O sistema não tem solução" NoSolutionEquation = "A equação não tem solução" NoSolutionInterval = "Nenhuma solução encontrada no intervalo" diff --git a/apps/solver/equation.h b/apps/solver/equation.h index a43db56c822..cae60c7ce77 100644 --- a/apps/solver/equation.h +++ b/apps/solver/equation.h @@ -8,9 +8,6 @@ namespace Solver { class Equation : public Shared::ExpressionModelHandle { public: Equation(Ion::Storage::Record record = Record()) : ExpressionModelHandle(record) {} - bool shouldBeClearedBeforeRemove() override { - return false; - } Poincare::Expression standardForm(Poincare::Context * context, bool replaceFunctionsButNotSymbols, Poincare::ExpressionNode::ReductionTarget reductionTarget) const { return m_model.standardForm(this, context, replaceFunctionsButNotSymbols, reductionTarget); } bool containsIComplex(Poincare::Context * context) const; diff --git a/apps/solver/equation_models_parameter_controller.h b/apps/solver/equation_models_parameter_controller.h index bb6812cb1ae..3eb88b5c317 100644 --- a/apps/solver/equation_models_parameter_controller.h +++ b/apps/solver/equation_models_parameter_controller.h @@ -30,7 +30,7 @@ class EquationModelsParameterController : public ViewController, public ListView }; StackViewController * stackController() const; constexpr static int k_numberOfExpressionCells = k_numberOfModels-1; - MessageTableCell m_emptyModelCell; + MessageTableCell<> m_emptyModelCell; ExpressionTableCell m_modelCells[k_numberOfExpressionCells]; Poincare::Layout m_layouts[k_numberOfExpressionCells]; SelectableTableView m_selectableTableView; diff --git a/apps/solver/equation_store.cpp b/apps/solver/equation_store.cpp index e5865a19a74..30812405b56 100644 --- a/apps/solver/equation_store.cpp +++ b/apps/solver/equation_store.cpp @@ -136,8 +136,8 @@ EquationStore::Error EquationStore::exactSolve(Poincare::Context * context, bool * the exact answer is given to the user. * 3) If no classic form has been found in the developped form, we need to use * numerical approximation. Therefore, to prevent precision losses, we work - * with the undevelopped form of the equation. Therefore we set reductionTarget - * to SystemForApproximation. Solutions are then numericaly approximated + * with the undeveloped form of the equation. Therefore we set reductionTarget + * to SystemForApproximation. Solutions are then numerically approximated * between the bounds provided by the user. */ EquationStore::Error EquationStore::privateExactSolve(Poincare::Context * context, bool replaceFunctionsButNotSymbols) { @@ -244,7 +244,7 @@ EquationStore::Error EquationStore::privateExactSolve(Poincare::Context * contex if (degree == 2) { // Polynomial degree <= 2 m_type = Type::PolynomialMonovariable; - error = oneDimensialPolynomialSolve(exactSolutions, exactSolutionsApproximations, polynomialCoefficients, degree, context); + error = oneDimensionalPolynomialSolve(exactSolutions, exactSolutionsApproximations, polynomialCoefficients, degree, context); } else { // Step 4. Monovariable non-polynomial or polynomial with degree > 2 m_type = Type::Monovariable; @@ -340,7 +340,7 @@ EquationStore::Error EquationStore::resolveLinearSystem(Expression exactSolution return Error::NoError; } -EquationStore::Error EquationStore::oneDimensialPolynomialSolve(Expression exactSolutions[k_maxNumberOfExactSolutions], Expression exactSolutionsApproximations[k_maxNumberOfExactSolutions], Expression coefficients[Expression::k_maxNumberOfPolynomialCoefficients], int degree, Context * context) { +EquationStore::Error EquationStore::oneDimensionalPolynomialSolve(Expression exactSolutions[k_maxNumberOfExactSolutions], Expression exactSolutionsApproximations[k_maxNumberOfExactSolutions], Expression coefficients[Expression::k_maxNumberOfPolynomialCoefficients], int degree, Context * context) { /* Equation ax^2+bx+c = 0 */ assert(degree == 2); // Compute delta = b*b-4ac @@ -441,13 +441,13 @@ void EquationStore::tidySolution() { Preferences::ComplexFormat EquationStore::updatedComplexFormat(Context * context) { Preferences::ComplexFormat complexFormat = Preferences::sharedPreferences()->complexFormat(); - if (complexFormat == Preferences::ComplexFormat::Real && isExplictlyComplex(context)) { + if (complexFormat == Preferences::ComplexFormat::Real && isExplicitlyComplex(context)) { return Preferences::ComplexFormat::Cartesian; } return complexFormat; } -bool EquationStore::isExplictlyComplex(Context * context) { +bool EquationStore::isExplicitlyComplex(Context * context) { for (int i = 0; i < numberOfDefinedModels(); i++) { if (modelForRecord(definedRecordAtIndex(i))->containsIComplex(context)) { return true; diff --git a/apps/solver/equation_store.h b/apps/solver/equation_store.h index 362392091dc..a473587a97a 100644 --- a/apps/solver/equation_store.h +++ b/apps/solver/equation_store.h @@ -100,9 +100,9 @@ class EquationStore : public Shared::ExpressionModelStore { Error privateExactSolve(Poincare::Context * context, bool replaceFunctionsButNotSymbols); Error resolveLinearSystem(Poincare::Expression solutions[k_maxNumberOfExactSolutions], Poincare::Expression solutionApproximations[k_maxNumberOfExactSolutions], Poincare::Expression coefficients[k_maxNumberOfEquations][Poincare::Expression::k_maxNumberOfVariables], Poincare::Expression constants[k_maxNumberOfEquations], Poincare::Context * context); - Error oneDimensialPolynomialSolve(Poincare::Expression solutions[k_maxNumberOfExactSolutions], Poincare::Expression solutionApproximations[k_maxNumberOfExactSolutions], Poincare::Expression polynomialCoefficients[Poincare::Expression::k_maxNumberOfPolynomialCoefficients], int degree, Poincare::Context * context); + Error oneDimensionalPolynomialSolve(Poincare::Expression solutions[k_maxNumberOfExactSolutions], Poincare::Expression solutionApproximations[k_maxNumberOfExactSolutions], Poincare::Expression polynomialCoefficients[Poincare::Expression::k_maxNumberOfPolynomialCoefficients], int degree, Poincare::Context * context); void tidySolution(); - bool isExplictlyComplex(Poincare::Context * context); + bool isExplicitlyComplex(Poincare::Context * context); Poincare::Preferences::ComplexFormat updatedComplexFormat(Poincare::Context * context); mutable Equation m_equations[k_maxNumberOfEquations]; diff --git a/apps/solver/interval_controller.cpp b/apps/solver/interval_controller.cpp index 986f5b4cf4b..9f83a9685d1 100644 --- a/apps/solver/interval_controller.cpp +++ b/apps/solver/interval_controller.cpp @@ -57,7 +57,7 @@ IntervalController::IntervalController(Responder * parentResponder, InputEventHa } const char * IntervalController::title() { - return I18n::translate(I18n::Message::SearchInverval); + return I18n::translate(I18n::Message::SearchInterval); } int IntervalController::numberOfRows() const { diff --git a/apps/solver/list_controller.h b/apps/solver/list_controller.h index 93e9173fda7..a64cc6ec024 100644 --- a/apps/solver/list_controller.h +++ b/apps/solver/list_controller.h @@ -9,6 +9,7 @@ #include "equation_list_view.h" #include "equation_models_parameter_controller.h" #include +#include namespace Solver { @@ -45,6 +46,8 @@ class ListController : public Shared::ExpressionModelListController, public Butt bool layoutFieldDidFinishEditing(LayoutField * layoutField, Poincare::Layout layout, Ion::Events::Event event) override; /* Specific to Solver */ void resolveEquations(); +protected: + virtual const char * recordExtension() const override { return Ion::Storage::eqExtension; } private: constexpr static int k_maxNumberOfRows = 5; // Ion::Display::Height / Metric::StoreRowHeight = 4.8; SelectableTableView * selectableTableView() override; diff --git a/apps/solver/solutions_controller.cpp b/apps/solver/solutions_controller.cpp index 8e5e985dcc1..59bd8a9512f 100644 --- a/apps/solver/solutions_controller.cpp +++ b/apps/solver/solutions_controller.cpp @@ -168,7 +168,7 @@ int SolutionsController::numberOfRows() const { void SolutionsController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { const int rowOfUserVariablesMessage = userVariablesMessageRow(); if (j == rowOfUserVariablesMessage) { - // Predefined varaible used/ignored message + // Predefined variable used/ignored message assert(i >= 0); MessageCell * messageCell = static_cast(cell); messageCell->setHorizontalAlignment(i == 0 ? 1.0f : 0.0f); diff --git a/apps/solver/solutions_controller.h b/apps/solver/solutions_controller.h index 4fac929a191..2bf90d04bf9 100644 --- a/apps/solver/solutions_controller.h +++ b/apps/solver/solutions_controller.h @@ -87,7 +87,7 @@ class SolutionsController : public ViewController, public AlternateEmptyViewDefa // Number of cells constexpr static int k_maxNumberOfVisibleCells = (Ion::Display::Height - 3 * Metric::TitleBarHeight) / k_defaultCellHeight + 1; // When displaying approximate solutions for cos(x) = 0 between 0 and 1800 and scrolling down - static_assert(k_maxNumberOfVisibleCells == 10, "k_maxNumberOfVisibleCells has changed"); //This assert is just for information purposes + // static_assert(k_maxNumberOfVisibleCells == 10, "k_maxNumberOfVisibleCells has changed"); //This assert is just for information purposes static_assert(k_maxNumberOfVisibleCells <= EquationStore::k_maxNumberOfSolutions + Poincare::Expression::k_maxNumberOfVariables, "We can reduce the number of cells in Solver:SolutionsController."); constexpr static int k_maxNumberOfSymbols = EquationStore::k_maxNumberOfSolutions + Poincare::Expression::k_maxNumberOfVariables; constexpr static int k_numberOfSymbolCells = k_maxNumberOfVisibleCells < k_maxNumberOfSymbols ? k_maxNumberOfVisibleCells : k_maxNumberOfSymbols; diff --git a/apps/statistics/base.de.i18n b/apps/statistics/base.de.i18n index 8a84b549edd..bb7260e8450 100644 --- a/apps/statistics/base.de.i18n +++ b/apps/statistics/base.de.i18n @@ -8,7 +8,6 @@ Values3 = "Werte V3" Frequencies1 = "Häufigkeiten N1" Frequencies2 = "Häufigkeiten N2" Frequencies3 = "Häufigkeiten N3" -ImportList = "Laden einer Liste" Interval = " Intervall" Frequency = " Häufigkeit:" RelativeFrequency = "Relative:" @@ -25,3 +24,6 @@ SampleStandardDeviationS = "Standardabweichung s" SumValues = "Summe" SumSquareValues = "Quadratsumme" InterquartileRange = "Interquartilsabstand" +GeometricMean = "Geometrisches Mittel" +HarmonicMean = "Harmonische Mittel" +StatisticsMode = "Modus " diff --git a/apps/statistics/base.en.i18n b/apps/statistics/base.en.i18n index 4bb66972991..70a86dda6b9 100644 --- a/apps/statistics/base.en.i18n +++ b/apps/statistics/base.en.i18n @@ -8,7 +8,6 @@ Values3 = "Value V3" Frequencies1 = "Frequency N1" Frequencies2 = "Frequency N2" Frequencies3 = "Frequency N3" -ImportList = "Import from a list" Interval = " Interval " Frequency = " Frequency:" RelativeFrequency = "Relative:" @@ -25,3 +24,6 @@ SampleStandardDeviationS = "Sample std deviation s" SumValues = "Sum of values" SumSquareValues = "Sum of squared values" InterquartileRange = "Interquartile range" +GeometricMean = "Geometric mean" +HarmonicMean = "Harmonic Mean" +StatisticsMode = "Mode" diff --git a/apps/statistics/base.es.i18n b/apps/statistics/base.es.i18n index 648c851693d..c38da099c00 100644 --- a/apps/statistics/base.es.i18n +++ b/apps/statistics/base.es.i18n @@ -8,7 +8,6 @@ Values3 = "Valores V3" Frequencies1 = "Frecuencias N1" Frequencies2 = "Frecuencias N2" Frequencies3 = "Frecuencias N3" -ImportList = "Importar una lista" Interval = " Intervalo" Frequency = " Frecuencia:" RelativeFrequency = "Relativa:" @@ -24,4 +23,7 @@ StandardDeviationSigma = "Desviación típica σ" SampleStandardDeviationS = "Desviación típica s" SumValues = "Suma" SumSquareValues = "Suma cuadrados" -InterquartileRange = "Rango intercuartilo" \ No newline at end of file +InterquartileRange = "Rango intercuartilo" +GeometricMean = "Significado geometrico" +HarmonicMean = "Significado armonico" +StatisticsMode = "Modo" diff --git a/apps/statistics/base.fr.i18n b/apps/statistics/base.fr.i18n index c6fbf2966be..dd97cb98593 100644 --- a/apps/statistics/base.fr.i18n +++ b/apps/statistics/base.fr.i18n @@ -8,7 +8,6 @@ Values3 = "Valeurs V3" Frequencies1 = "Effectifs N1" Frequencies2 = "Effectifs N2" Frequencies3 = "Effectifs N3" -ImportList = "Importer une liste" Interval = " Intervalle " Frequency = " Effectif:" RelativeFrequency = "Fréquence:" @@ -17,6 +16,9 @@ RectangleWidth = "Largeur des rectangles" BarStart = "Début de la série" FirstQuartile = "Premier quartile" Median = "Médiane" +GeometricMean = "Moyenne géométrique" +HarmonicMean = "Moyenne harmonique" +StatisticsMode = "Mode" ThirdQuartile = "Troisième quartile" TotalFrequency = "Effectif total" Range = "Étendue" diff --git a/apps/statistics/base.hu.i18n b/apps/statistics/base.hu.i18n index 097a28b2224..544a5fe11d0 100644 --- a/apps/statistics/base.hu.i18n +++ b/apps/statistics/base.hu.i18n @@ -1,27 +1,29 @@ -StatsApp = "Statisztika" -StatsAppCapital = "STATISZTIKA" -HistogramTab = "Hisztogram" -BoxTab = "Doboz" -Values1 = "V1 értékek" -Values2 = "V2 értékek" -Values3 = "V3 értékek" -Frequencies1 = "N1 Frekvencia" -Frequencies2 = "N2 Frekvencia" -Frequencies3 = "N3 Frekvencia" -ImportList = "Importálás egy listáról" -Interval = "Intervallum" -Frequency = "Frekvencia:" -RelativeFrequency = "Relatív:" -HistogramSet = "Hisztogram beállítások" -RectangleWidth = "Tálca szélessége" -BarStart = "X kezdet" -FirstQuartile = "Elsö kvartilis" -Median = "Medián" -ThirdQuartile = "Harmadik kvartilis" -TotalFrequency = "Adatpontok száma " -Range = "Intervallum" -StandardDeviationSigma = "σ szórás" -SampleStandardDeviationS = "Minta std eltérés σ" -SumValues = "Értékek összege" -SumSquareValues = "Négyzetértékek összege" -InterquartileRange = "Interkvartilis tartomány" +StatsApp = "Statisztika" +StatsAppCapital = "STATISZTIKA" +HistogramTab = "Hisztogram" +BoxTab = "Doboz" +Values1 = "V1 értékek" +Values2 = "V2 értékek" +Values3 = "V3 értékek" +Frequencies1 = "N1 Frekvencia" +Frequencies2 = "N2 Frekvencia" +Frequencies3 = "N3 Frekvencia" +Interval = "Intervallum" +Frequency = "Frekvencia:" +RelativeFrequency = "Relatív:" +HistogramSet = "Hisztogram beállítások" +RectangleWidth = "Tálca szélessége" +BarStart = "X kezdet" +FirstQuartile = "Elsö kvartilis" +Median = "Medián" +ThirdQuartile = "Harmadik kvartilis" +TotalFrequency = "Adatpontok száma " +Range = "Intervallum" +StandardDeviationSigma = "σ szórás" +SampleStandardDeviationS = "Minta std eltérés σ" +SumValues = "Értékek összege" +SumSquareValues = "Négyzetértékek összege" +InterquartileRange = "Interkvartilis tartomány" +GeometricMean = "Geometriai átlag" +HarmonicMean = "Harmonikus átlag" +StatisticsMode = "Mód" diff --git a/apps/statistics/base.it.i18n b/apps/statistics/base.it.i18n index 2879e7e637a..5cbd3650fe9 100644 --- a/apps/statistics/base.it.i18n +++ b/apps/statistics/base.it.i18n @@ -8,7 +8,6 @@ Values3 = "Valori V3" Frequencies1 = "Frequenze N1" Frequencies2 = "Frequenze N2" Frequencies3 = "Frequenze N3" -ImportList = "Importare una lista" Interval = " Intervallo " Frequency = " Frequenza:" RelativeFrequency = "Relativa:" @@ -24,4 +23,7 @@ StandardDeviationSigma = "Deviazione standard σ" SampleStandardDeviationS = "Dev. std campionaria s" SumValues = "Somma" SumSquareValues = "Somma dei quadrati" -InterquartileRange = "Scarto interquartile" \ No newline at end of file +InterquartileRange = "Scarto interquartile" +GeometricMean = "Media geometrica" +HarmonicMean = "Media armonica" +StatisticsMode = "Modalità" diff --git a/apps/statistics/base.nl.i18n b/apps/statistics/base.nl.i18n index 2e43cbc204c..4caad6efc2a 100644 --- a/apps/statistics/base.nl.i18n +++ b/apps/statistics/base.nl.i18n @@ -8,7 +8,6 @@ Values3 = "Waarden V3" Frequencies1 = "Frequenties N1" Frequencies2 = "Frequenties N2" Frequencies3 = "Frequenties N3" -ImportList = "Importeren uit een lijst" Interval = " Interval " Frequency = " Frequentie:" RelativeFrequency = "Relatieve:" @@ -24,4 +23,7 @@ StandardDeviationSigma = "Standaardafwijking σ" SampleStandardDeviationS = "Standaardafwijking s" SumValues = "Som" SumSquareValues = "Som van kwadraten" -InterquartileRange = "Interkwartielafstand" \ No newline at end of file +InterquartileRange = "Interkwartielafstand" +GeometricMean = "Geometrisch gemiddelde" +HarmonicMean = "Harmonisch gemiddelde" +StatisticsMode = "Modus" diff --git a/apps/statistics/base.pt.i18n b/apps/statistics/base.pt.i18n index 29ac75e6e57..73e76f58c45 100644 --- a/apps/statistics/base.pt.i18n +++ b/apps/statistics/base.pt.i18n @@ -8,7 +8,6 @@ Values3 = "Valores V3" Frequencies1 = "Frequências N1" Frequencies2 = "Frequências N2" Frequencies3 = "Frequências N3" -ImportList = "Importar de uma lista" Interval = " Intervalo" Frequency = " Frequência:" RelativeFrequency = "Relativa:" @@ -24,4 +23,7 @@ StandardDeviationSigma = "Desvio padrão σ" SampleStandardDeviationS = "Desvio padrão amostral s" SumValues = "Somatório" SumSquareValues = "Soma dos quadrados" -InterquartileRange = "Amplitude interquartil" \ No newline at end of file +InterquartileRange = "Amplitude interquartil" +GeometricMean = "Média geométrica" +HarmonicMean = "Média Harmônica" +StatisticsMode = "Modo" diff --git a/apps/statistics/calculation_controller.cpp b/apps/statistics/calculation_controller.cpp index fe21be45812..4dce5526640 100644 --- a/apps/statistics/calculation_controller.cpp +++ b/apps/statistics/calculation_controller.cpp @@ -79,12 +79,15 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int I18n::Message::Maximum, I18n::Message::Range, I18n::Message::Mean, + I18n::Message::GeometricMean, + I18n::Message::HarmonicMean, I18n::Message::StandardDeviationSigma, I18n::Message::Deviation, I18n::Message::FirstQuartile, I18n::Message::ThirdQuartile, I18n::Message::Median, I18n::Message::InterquartileRange, + I18n::Message::StatisticsMode, I18n::Message::SumValues, I18n::Message::SumSquareValues, I18n::Message::SampleStandardDeviationS}; @@ -94,8 +97,8 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int } // Display a calculation cell CalculPointer calculationMethods[k_totalNumberOfRows] = {&Store::sumOfOccurrences, &Store::minValue, - &Store::maxValue, &Store::range, &Store::mean, &Store::standardDeviation, &Store::variance, &Store::firstQuartile, - &Store::thirdQuartile, &Store::median, &Store::quartileRange, &Store::sum, &Store::squaredValueSum, &Store::sampleStandardDeviation}; + &Store::maxValue, &Store::range, &Store::mean, &Store::geometricMean, &Store::harmonicMean, &Store::standardDeviation, &Store::variance, &Store::firstQuartile, + &Store::thirdQuartile, &Store::median, &Store::quartileRange, &Store::mode, &Store::sum, &Store::squaredValueSum, &Store::sampleStandardDeviation}; int seriesIndex = m_store->indexOfKthNonEmptySeries(i-1); double calculation = (m_store->*calculationMethods[j-1])(seriesIndex); EvenOddBufferTextCell * calculationCell = static_cast(cell); diff --git a/apps/statistics/calculation_controller.h b/apps/statistics/calculation_controller.h index ae001ccaa5e..7d7df8faa15 100644 --- a/apps/statistics/calculation_controller.h +++ b/apps/statistics/calculation_controller.h @@ -43,7 +43,7 @@ class CalculationController : public Shared::TabTableController, public ButtonRo bool handleEvent(Ion::Events::Event event) override; void didBecomeFirstResponder() override; private: - static constexpr int k_totalNumberOfRows = 15; + static constexpr int k_totalNumberOfRows = 18; static constexpr int k_maxNumberOfDisplayableRows = 11; static constexpr int k_numberOfCalculationCells = 3 * k_maxNumberOfDisplayableRows; static constexpr int k_numberOfSeriesTitleCells = 3; diff --git a/apps/statistics/store.cpp b/apps/statistics/store.cpp index 219f4c8dc1c..ecfade0d144 100644 --- a/apps/statistics/store.cpp +++ b/apps/statistics/store.cpp @@ -162,6 +162,34 @@ double Store::mean(int series) const { return sum(series)/sumOfOccurrences(series); } +double Store::geometricMean(int series) const { + double geometricMean = 1; + int numberOfCoefficients = 0; + int numberOfPairs = numberOfPairsOfSeries(series); + for (int k = 0; k < numberOfPairs; k++) { + if (m_data[series][0][k] <= 0) { + return NAN; + } + geometricMean *= std::pow(m_data[series][0][k], m_data[series][1][k]); + numberOfCoefficients += m_data[series][1][k]; + } + return std::pow(geometricMean, 1.0/numberOfCoefficients); +} + +double Store::harmonicMean(int series) const { + double harmonicMean = 0; + int numberOfCoefficients = 0; + int numberOfPairs = numberOfPairsOfSeries(series); + for (int k = 0; k < numberOfPairs; k++) { + if (m_data[series][0][k] <= 0) { + return NAN; + } + harmonicMean += m_data[series][1][k]/m_data[series][0][k]; + numberOfCoefficients += m_data[series][1][k]; + } + return numberOfCoefficients/harmonicMean; +} + double Store::variance(int series) const { /* We use the Var(X) = E[(X-E[X])^2] definition instead of Var(X) = E[X^2] - E[X]^2 * to ensure a positive result and to minimize rounding errors */ @@ -217,6 +245,22 @@ double Store::quartileRange(int series) const { return thirdQuartile(series)-firstQuartile(series); } +double Store::mode(int series) const { + double modeValue = NAN; + double numberOfRepeats = 0; + int numberOfPairs = numberOfPairsOfSeries(series); + for (int k = 0; k < numberOfPairs; k++) { + if (m_data[series][1][k] > numberOfRepeats) { + modeValue = m_data[series][0][k]; + numberOfRepeats = m_data[series][1][k]; + } + else if (m_data[series][1][k] == numberOfRepeats) { + modeValue = NAN; + } + } + return modeValue; +} + double Store::median(int series) const { return sortedElementAtCumulatedFrequency(series, 1.0/2.0, true); } diff --git a/apps/statistics/store.h b/apps/statistics/store.h index 136791a237f..aa19c799888 100644 --- a/apps/statistics/store.h +++ b/apps/statistics/store.h @@ -35,12 +35,15 @@ class Store : public Shared::MemoizedCurveViewRange, public Shared::DoublePairSt double minValue(int series) const; double range(int series) const; double mean(int series) const; + double geometricMean(int series) const; + double harmonicMean(int series) const; double variance(int series) const; double standardDeviation(int series) const; double sampleStandardDeviation(int series) const; double firstQuartile(int series) const; double thirdQuartile(int series) const; double quartileRange(int series) const; + double mode(int series) const; double median(int series) const; double sum(int series) const; double squaredValueSum(int series) const; diff --git a/apps/suspend_timer.cpp b/apps/suspend_timer.cpp index 34562d8190c..b21c1d6b39b 100644 --- a/apps/suspend_timer.cpp +++ b/apps/suspend_timer.cpp @@ -2,7 +2,7 @@ #include "apps_container.h" SuspendTimer::SuspendTimer() : - Timer(k_idleBeforeSuspendDuration/Timer::TickDuration) + Timer(GlobalPreferences::sharedGlobalPreferences()->idleBeforeSuspendSeconds()*1000/Timer::TickDuration) { } diff --git a/apps/suspend_timer.h b/apps/suspend_timer.h index 0a7b735d674..8125ca85513 100644 --- a/apps/suspend_timer.h +++ b/apps/suspend_timer.h @@ -7,7 +7,6 @@ class SuspendTimer : public Timer { public: SuspendTimer(); private: - constexpr static int k_idleBeforeSuspendDuration = 5*60*1000; // In miliseconds bool fire() override; }; diff --git a/apps/timer_manager.cpp b/apps/timer_manager.cpp new file mode 100644 index 00000000000..1d13ec37661 --- /dev/null +++ b/apps/timer_manager.cpp @@ -0,0 +1,10 @@ +#include +#include + +void TimerManager::AddTimer(Timer * timer) { + AppsContainer::sharedAppsContainer()->addTimer(timer); +} + +void TimerManager::RemoveTimer(Timer * timer) { + AppsContainer::sharedAppsContainer()->removeTimer(timer); +} diff --git a/apps/title_bar_view.cpp b/apps/title_bar_view.cpp index a842b394e50..cd0bb5337b4 100644 --- a/apps/title_bar_view.cpp +++ b/apps/title_bar_view.cpp @@ -102,7 +102,7 @@ View * TitleBarView::subviewAtIndex(int index) { void TitleBarView::layoutSubviews(bool force) { /* We here cheat to layout the main title. The application title is written * with upper cases. But, as upper letters are on the same baseline as lower - * letters, they seem to be slightly above when they are perferctly centered + * letters, they seem to be slightly above when they are perfectly centered * (because their glyph never cross the baseline). To avoid this effect, we * translate the frame of the title downwards.*/ m_titleView.setFrame(KDRect(0, 2, bounds().width(), bounds().height()-2), force); diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index e39e80693bc..2582f6d1bf4 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -1,6 +1,5 @@ Unit = "Einheiten" UnitTimeMenu = "Zeit" -UnitTimeSecondMenu = "Sekunde" UnitTimeSecond = "Sekunde" UnitTimeSecondMilli = "Millisekunde" UnitTimeSecondMicro = "Mikrosekunde" @@ -12,7 +11,6 @@ UnitTimeWeek = "Woche" UnitTimeMonth = "Monat" UnitTimeYear = "Jahr" UnitDistanceMenu = "Entfernung" -UnitDistanceMeterMenu = "Meter" UnitDistanceMeterKilo = "Kilometer" UnitDistanceMeter = "Meter" UnitDistanceMeterMilli = "Millimeter" @@ -31,9 +29,6 @@ UnitMassGramKilo = "Kilogramm" UnitMassGram = "Gramm" UnitMassGramMilli = "Milligramm" UnitMassGramMicro = "Mikrogramm" -UnitMassGramNano = "Nanogramm" -UnitDistanceImperialMenu = "Angloamerikanisch" -UnitMassImperialMenu = "Angloamerikanisch" UnitMassTonne = "Tonne" UnitMassOunce = "Unze" UnitMassPound = "Pfund" @@ -58,7 +53,7 @@ UnitPressurePascalHecto = "Hektopascal" UnitPressureAtm = "Atmosphere" UnitEnergyMenu = "Energie" UnitEnergyJouleMilli = "Millijoule" -UnitEnergyEletronVoltMenu = "Electronvolt" +UnitEnergyElectronVoltMenu = "Electronvolt" UnitEnergyElectronVoltMega = "Megaelectronvolt" UnitEnergyElectronVoltKilo = "Kiloelectronvolt" UnitEnergyElectronVolt = "Electronvolt" @@ -116,7 +111,7 @@ Integral = "Integral" Sum = "Summe" Product = "Produkt" ComplexAbsoluteValue = "Betrag" -Agument = "Argument" +Argument = "Argument" RealPart = "Realteil" ImaginaryPart = "Imaginärteil" Conjugate = "Konjugiert" @@ -423,13 +418,33 @@ EscapeVelocity = "Fluchtgeschwindigkeit" EscapeVelocityFromEarth = "Von der Erde" EscapeVelocityFromMoon = "Vom Mond" EscapeVelocityFromSun = "Von der Sonne" -YearLightTag = "Lichtjahr" Thermodynamics = "Thermodynamik" BoltzmannTag = "Boltzmann Konstante" AvogadroTag = "Avogadro-Konstante" GasTag = "Gaskonstante" Electromagnetism = "Elektromagnetismus" CoulombTag = "Coulomb-Konstante" +ConductivityConstants = "Leitfähigkeitskonstanten" +Electricity = "Elektrizität" +ResistivityConstants = "Konstanten der Widerstandsfähigkeit" +Silver = "Silber" +Copper = "Kupfer" +Gold = "Gold" +Aluminium = "Aluminium" +Calcium = "Kalzium" +Tungsten = "Wolfram" +Zinc = "Zink" +Cobalt = "Kobalt" +Nickel = "Nickel" +Lithium = "Lithium" +Iron = "eisen" +Platinum = "Platin" +Tin = "Zinn" +Sea_water = "Meerwasser" +Water = "Wasser" +Air = "Luft" +Glass = "Glas" +Wood = "Holz" Vacuum_permittivityTag = "Vakuum-Durchlässigkeit" Vacuum_permeabilityTag = "Vakuumdurchlässigkeit" PlanckTag = "Planck - Konstante" @@ -445,7 +460,6 @@ MoonMassTag = "Mond" EarthMassTag = "Erde" SunMassTag = "Sonne" ParticleMass = "Partikel Masse" -AstronomicalMass = "Astronomische" Radiuses = "Radien" Length = "Länge" Distances = "Entfernungen" @@ -496,3 +510,5 @@ HartreeConstantTag = "Hartbaum-Konstante" MagneticFluxQuantumTag = "Magnetisches Fluss-Quantum" ConductanceQuantumTag = "Leitwertquantum" CirculationQuantumTag = "Auflage-Quantum" +MatricesAndVectors = "Matrizen und Vektoren" +Factorial = "Fakultät" diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index 7e28841366a..e6781e868c8 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -1,6 +1,5 @@ Unit = "Units" UnitTimeMenu = "Time" -UnitTimeSecondMenu = "Second" UnitTimeSecond = "Second" UnitTimeSecondMilli = "Millisecond" UnitTimeSecondMicro = "Microsecond" @@ -12,14 +11,12 @@ UnitTimeWeek = "Week" UnitTimeMonth = "Month" UnitTimeYear = "Year" UnitDistanceMenu = "Distance" -UnitDistanceMeterMenu = "Meter" UnitDistanceMeterKilo = "Kilometer" UnitDistanceMeter = "Meter" UnitDistanceMeterMilli = "Millimeter" UnitDistanceMeterMicro = "Micrometer" UnitDistanceMeterNano = "Nanometer" UnitDistanceMeterPico = "Picometer" -UnitDistanceImperialMenu = "US Customary" UnitDistanceInch = "Inch" UnitDistanceFoot = "Foot" UnitDistanceYard = "Yard" @@ -27,13 +24,11 @@ UnitDistanceMile = "Mile" UnitDistanceAstronomicalUnit = "Astronomical unit" UnitDistanceLightYear = "Light year" UnitDistanceParsec = "Parsec" -UnitMassImperialMenu = "US Customary" UnitMassMenu = "Mass" UnitMassGramKilo = "Kilogram" UnitMassGram = "Gram" UnitMassGramMilli = "Milligram" UnitMassGramMicro = "Microgram" -UnitMassGramNano = "Nanogram" UnitMassTonne = "Metric Ton" UnitMassOunce = "Ounce" UnitMassPound = "Pound" @@ -58,7 +53,7 @@ UnitPressurePascalHecto = "Hectopascal" UnitPressureAtm = "Atmosphere" UnitEnergyMenu = "Energy" UnitEnergyJouleMilli = "Millijoule" -UnitEnergyEletronVoltMenu = "Electronvolt" +UnitEnergyElectronVoltMenu = "Electronvolt" UnitEnergyElectronVoltMega = "Megaelectronvolt" UnitEnergyElectronVoltKilo = "Kiloelectronvolt" UnitEnergyElectronVolt = "Electronvolt" @@ -104,6 +99,7 @@ Calculation = "Calculation" ComplexNumber = "Complex numbers" Combinatorics = "Combinatorics" Arithmetic = "Arithmetic" +MatricesAndVectors = "Matrices and vectors" Matrices = "Matrix" NewMatrix = "New matrix" Identity = "Identity matrix of size n" @@ -116,7 +112,7 @@ Integral = "Integral" Sum = "Sum" Product = "Product" ComplexAbsoluteValue = "Absolute value" -Agument = "Argument" +Argument = "Argument" RealPart = "Real part" ImaginaryPart = "Imaginary part" Conjugate = "Conjugate" @@ -423,13 +419,33 @@ EscapeVelocityFromEarth = "Of Earth" EscapeVelocityFromMoon = "Of Moon" EscapeVelocityFromSun = "Of Sun" SpeedOfLightTag = "Speed of light" -YearLightTag = "One year light" Thermodynamics = "Thermodynamics" BoltzmannTag = "Boltzmann Constant" AvogadroTag = "Avogadro Constant" GasTag = "Gas Constant" Electromagnetism = "Electromagnetism" CoulombTag = "Coulomb Constant" +ConductivityConstants = "Conductivity constants" +Electricity = "Electricity" +ResistivityConstants = "Resistivity Constants" +Silver = "Silver" +Copper = "Copper" +Gold = "Gold" +Aluminium = "Aluminium" +Calcium = "Calcium" +Tungsten = "Tungsten" +Zinc = "Zinc" +Cobalt = "Cobalt" +Nickel = "Nickel" +Lithium = "Lithium" +Iron = "Iron" +Platinum = "Platinum" +Tin = "Tin" +Sea_water = "Sea Water" +Water = "Water" +Air = "Air" +Glass = "Glass" +Wood = "Wood" Vacuum_permittivityTag = "Vacuum permittivity" Vacuum_permeabilityTag = "Vacuum permeability" PlanckTag = "Planck Constant" @@ -445,7 +461,6 @@ MoonMassTag = "Moon" EarthMassTag = "Earth" SunMassTag = "Sun" ParticleMass = "Particles Mass" -AstronomicalMass = "Astronomical" Radiuses = "Radiuses" Length = "Length" Distances = "Distances" @@ -496,3 +511,4 @@ HartreeConstantTag = "Hartree Constant" MagneticFluxQuantumTag = "Magnetic Flux Quantum" ConductanceQuantumTag = "Conductance Quantum" CirculationQuantumTag = "Circulation Quantum" +Factorial = "Factorial" \ No newline at end of file diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index a61c44d33a8..ddb8dcf1c55 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -1,6 +1,5 @@ Unit = "Units" UnitTimeMenu = "Time" -UnitTimeSecondMenu = "Second" UnitTimeSecond = "Second" UnitTimeSecondMilli = "Millisecond" UnitTimeSecondMicro = "Microsecond" @@ -12,18 +11,15 @@ UnitTimeWeek = "Week" UnitTimeMonth = "Month" UnitTimeYear = "Year" UnitDistanceMenu = "Distance" -UnitDistanceMeterMenu = "Meter" UnitDistanceMeterKilo = "Kilometer" UnitDistanceMeter = "Meter" UnitDistanceMeterMilli = "Millimeter" UnitDistanceMeterMicro = "Micrometer" UnitDistanceMeterNano = "Nanometer" UnitDistanceMeterPico = "Picometer" -UnitDistanceImperialMenu = "US Customary" UnitDistanceAstronomicalUnit = "Astronomical unit" UnitDistanceLightYear = "Light year" UnitDistanceParsec = "Parsec" -UnitMassImperialMenu = "US Customary" UnitDistanceMile = "Milla" UnitDistanceYard = "Yardas" UnitDistanceFoot = "Pie" @@ -33,7 +29,6 @@ UnitMassGramKilo = "Kilogram" UnitMassGram = "Gram" UnitMassGramMilli = "Milligram" UnitMassGramMicro = "Microgram" -UnitMassGramNano = "Nanogram" UnitMassTonne = "Tonne" UnitMassOunce = "Onza" UnitMassPound = "Libra" @@ -58,7 +53,7 @@ UnitPressurePascalHecto = "Hectopascal" UnitPressureAtm = "Atmosphere" UnitEnergyMenu = "Energy" UnitEnergyJouleMilli = "Millijoule" -UnitEnergyEletronVoltMenu = "Electronvolt" +UnitEnergyElectronVoltMenu = "Electronvolt" UnitEnergyElectronVoltMega = "Megaelectronvolt" UnitEnergyElectronVoltKilo = "Kiloelectronvolt" UnitEnergyElectronVolt = "Electronvolt" @@ -116,7 +111,7 @@ Integral = "Integral" Sum = "Suma" Product = "Productorio" ComplexAbsoluteValue = "Modulo" -Agument = "Argumento" +Argument = "Argumento" RealPart = "Parte real" ImaginaryPart = "Parte imaginaria" Conjugate = "Conjugado" @@ -415,7 +410,6 @@ AlphaElementUbn = "Ubn - Unbinilio (120)" Speed = "Velocidad" SpeedOfLightTag = "Velocidad de la luz" SpeedOfSound = "La velocidad del sonido" -YearLightTag = "Un año de luz" Thermodynamics = "Termodinámica" SpeedOfSound0Tag = "Nivel del mar, 20 ° C" SpeedOfSoundWaterTag = "En el agua" @@ -430,6 +424,27 @@ AvogadroTag = "Constante de Avogadro" GasTag = "Constante de gas" Electromagnetism = "Electromagnetismo" CoulombTag = "Constante de Coulomb" +ConductivityConstants = "Constantes de conductividad" +Electricity = "Electricidad" +ResistivityConstants = "Constantes de resistividad" +Silver = "Plata" +Copper = "cobre" +Gold = "Oro" +Aluminium = "Aluminio" +Calcium = "Calcio" +Tungsten = "Tungsteno" +Zinc = "Zinc" +Cobalt = "Cobalto" +Nickel = "Níquel" +Lithium = "Litio" +Iron = "Hierro" +Platinum = "Platino" +Tin = "Tin" +Sea_water = "Agua de mar" +Water = "Agua" +Air = "Aire" +Glass = "Vidrio" +Wood = "Madera" Vacuum_permittivityTag = "Permisividad de vacío" Vacuum_permeabilityTag = "Permeabilidad al vacío" PlanckTag = "Constante de Planck" @@ -445,7 +460,6 @@ MoonMassTag = "Luna" EarthMassTag = "Tierra" SunMassTag = "Sol" ParticleMass = "Misa de las partículas" -AstronomicalMass = "Astronómica" Radiuses = "Radios" Length = "Lenght" Distances = "Distancias" @@ -496,3 +510,5 @@ HartreeConstantTag = "Constante de Hartree" MagneticFluxQuantumTag = "Flujo Magnético Cuántico" ConductanceQuantumTag = "Conductancia Quantum" CirculationQuantumTag = "Circulación Quantum" +MatricesAndVectors = "Matrices y vectores" +Factorial = "Factorial" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index f33aa140fb2..ac0f40b36d3 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -1,6 +1,5 @@ Unit = "Unités" UnitTimeMenu = "Temps" -UnitTimeSecondMenu = "Seconde" UnitTimeSecond = "Seconde" UnitTimeSecondMilli = "Milliseconde" UnitTimeSecondMicro = "Microseconde" @@ -12,14 +11,12 @@ UnitTimeWeek = "Semaine" UnitTimeMonth = "Mois" UnitTimeYear = "Année" UnitDistanceMenu = "Distance" -UnitDistanceMeterMenu = "Mètre" UnitDistanceMeterKilo = "Kilomètre" UnitDistanceMeter = "Mètre" UnitDistanceMeterMilli = "Millimètre" UnitDistanceMeterMicro = "Micromètre" UnitDistanceMeterNano = "Nanomètre" UnitDistanceMeterPico = "Picomètre" -UnitDistanceImperialMenu = "US Customary" UnitDistanceInch = "Inch" UnitDistanceFoot = "Foot" UnitDistanceYard = "Yard" @@ -27,7 +24,6 @@ UnitDistanceMile = "Mile" UnitDistanceAstronomicalUnit = "Unité astronomique" UnitDistanceLightYear = "Année-lumière" UnitDistanceParsec = "Parsec" -UnitMassImperialMenu = "US Customary" UnitDistanceMile = "Mile" UnitDistanceYard = "Yard" UnitDistanceFoot = "Pied" @@ -37,7 +33,6 @@ UnitMassGramKilo = "Kilogramme" UnitMassGram = "Gramme" UnitMassGramMilli = "Milligramme" UnitMassGramMicro = "Microgramme" -UnitMassGramNano = "Nanogramme" UnitMassTonne = "Tonne" UnitMassOunce = "Once" UnitMassPound = "Livre" @@ -62,7 +57,7 @@ UnitPressurePascalHecto = "Hectopascal" UnitPressureAtm = "Atmosphère" UnitEnergyMenu = "Énergie" UnitEnergyJouleMilli = "Millijoule" -UnitEnergyEletronVoltMenu = "Électronvolt" +UnitEnergyElectronVoltMenu = "Électronvolt" UnitEnergyElectronVoltMega = "Mégaélectronvolt" UnitEnergyElectronVoltKilo = "Kiloélectronvolt" UnitEnergyElectronVolt = "Électronvolt" @@ -120,7 +115,7 @@ Integral = "Intégrale de f sur [a;b]" Sum = "Somme" Product = "Produit" ComplexAbsoluteValue = "Module" -Agument = "Argument" +Argument = "Argument" RealPart = "Partie réelle" ImaginaryPart = "Partie imaginaire" Conjugate = "Conjugué" @@ -423,12 +418,32 @@ SpeedOfSoundWaterTag = "Dans l'eau" SpeedOfSoundSteelTag = "Dans l'acier" SpeedOfSoundGlassTag = "Dans le verre" SpeedOfLightTag = "Vitesse de la lumière" -YearLightTag = "Année lumière" Thermodynamics = "Thermodynamique" BoltzmannTag = "Constante de Boltzmann" AvogadroTag = "Constante d'Avogadro" GasTag = "Constante des gaz parfaits" Electromagnetism = "Electromagnétisme" +ConductivityConstants = "Constantes de Conductivité" +Electricity = "Electricité" +ResistivityConstants = "Constantes de Resistivité" +Silver = "Argent" +Copper = "Cuivre" +Gold = "Or" +Aluminium = "Aluminium" +Calcium = "Calcium" +Tungsten = "Tungstène" +Zinc = "Zinc" +Cobalt = "Cobalt" +Nickel = "Nickel" +Lithium = "Lithium" +Iron = "Fer" +Platinum = "Platine" +Tin = "Étain" +Sea_water = "Eau de mer" +Water = "Eau" +Air = "Air" +Glass = "Verre" +Wood = "Bois" CoulombTag = "Constante de Coulomb" EscapeVelocity = "Vitesse de libération" EscapeVelocityFromEarth = "De la Terre" @@ -449,7 +464,6 @@ MoonMassTag = "Lune" EarthMassTag = "Terre" SunMassTag = "Soleil" ParticleMass = "Masses des Particules" -AstronomicalMass = "Astronomiques" Radiuses = "Rayons" Length = "Longueur" Distances = "Distances" @@ -500,3 +514,5 @@ HartreeConstantTag = "Constante de Hartree" MagneticFluxQuantumTag = "Quantum de Flux Magnétique" ConductanceQuantumTag = "Quantum de Conductance" CirculationQuantumTag = "Quantum de Circulation" +MatricesAndVectors = "Matrices et vecteurs" +Factorial = "Factorielle" diff --git a/apps/toolbox.hu.i18n b/apps/toolbox.hu.i18n index 4cb79e66647..76fe34d08a4 100644 --- a/apps/toolbox.hu.i18n +++ b/apps/toolbox.hu.i18n @@ -1,498 +1,513 @@ -Unit = "Mértékegység" -UnitTimeMenu = "Idö" -UnitTimeSecondMenu = "Másodperc" -UnitTimeSecond = "Másodperc" -UnitTimeSecondMilli = "Miliszekundum" -UnitTimeSecondMicro = "Mikroszekundum" -UnitTimeSecondNano = "Nanoszekundum" -UnitTimeMinute = "Perc" -UnitTimeHour = "Óra" -UnitTimeDay = "Nap" -UnitTimeWeek = "hét" -UnitTimeMonth = "Hónap" -UnitTimeYear = "Év" -UnitDistanceMenu = "Távolság" -UnitDistanceMeterMenu = "Méter" -UnitDistanceMeterKilo = "Kilométer" -UnitDistanceMeter = "Méter" -UnitDistanceMeterMilli = "Milliméter" -UnitDistanceMeterMicro = "Mikrométer" -UnitDistanceMeterNano = "Nanométer" -UnitDistanceMeterPico = "Pikométer" -UnitDistanceImperialMenu = "Angolszász mértékegységek" -UnitDistanceInch = "Hüvelyk" -UnitDistanceFoot = "Láb" -UnitDistanceYard = "Yard" -UnitDistanceMile = "Mérföld" -UnitDistanceAstronomicalUnit = "Csillagászati egység" -UnitDistanceLightYear = "Fény év" -UnitDistanceParsec = "Parsec" -UnitMassShortTon = "Rövid tonna" -UnitMassLongTon = "Hosszú tonna" -UnitMassImperialMenu = "Angolszász mértékegységek" -UnitMassPound = "Font" -UnitMassOunce = "Uncia" -UnitMassMenu = "Tömeg" -UnitMassGramKilo = "Kilogramm" -UnitMassGram = "Gramm" -UnitMassGramMilli = "Milligramm" -UnitMassGramMicro = "Mikrogramm" -UnitMassGramNano = "Nanogramm" -UnitMassTonne = "Tonna" -UnitCurrentMenu = "Áram" -UnitCurrentAmpere = "Amper" -UnitCurrentAmpereMilli = "Milliamper" -UnitCurrentAmpereMicro = "Mikroamper" -UnitTemperatureMenu = "Hömérséklet" -UnitAmountMenu = "Az anyag mennyisége" -UnitAmountMole = "Mól" -UnitAmountMoleMilli = "Millimól" -UnitAmountMoleMicro = "Mikromól" -UnitLuminousIntensityMenu = "Fényerö" -UnitFrequencyMenu = "Frekvencia" -UnitFrequencyHertzMega = "Megahertz" -UnitForceMenu = "Erö" -UnitForceNewtonMilli = "Millinewton" -UnitPressureMenu = "Nyomás" -UnitPressurePascalHecto = "Hectopascal" -UnitPressureAtm = "Légkör" -UnitEnergyMenu = "Energia" -UnitEnergyJouleMilli = "Millijoule" -UnitEnergyEletronVoltMenu = "Electronvolt" -UnitEnergyElectronVoltMega = "Megaelectronvolt" -UnitEnergyElectronVoltKilo = "Kiloelectronvolt" -UnitEnergyElectronVolt = "Electronvolt" -UnitEnergyElectronVoltMilli = "Millielectronvolt" -UnitPowerMenu = "Teljesítmény" -UnitPowerWattMega = "Megawatt" -UnitPowerWattMilli = "Milliwatt" -UnitPowerWattMicro = "Microwatt" -UnitElectricChargeMenu = "Elektromos töltés" -UnitPotentialMenu = "Elektromos potenciál" -UnitPotentialVoltMilli = "Millivolt" -UnitPotentialVoltMicro = "Microvolt" -UnitCapacitanceMenu = "Elektromos kapacitás" -UnitCapacitanceFaradMilli = "Millifarad" -UnitCapacitanceFaradMicro = "Microfarad" -UnitResistanceMenu = "Elektromos ellenállás" -UnitConductanceMenu = "Elektromos vezetöképesség" -UnitConductanceSiemensMilli = "Millisiemens" -UnitMagneticFieldMenu = "Mágneses mezö" -InductanceMenu = "Elektromos induktivitás" -UnitSurfaceMenu = "Terület" -UnitSurfaceAcre = "Acre" -UnitSurfaceHectar = "Hektár" -UnitVolumeMenu = "Kötet" -UnitVolumeLiter = "Liter" -UnitVolumeLiterDeci = "Deciliter" -UnitVolumeLiterCenti = "Centiliter" -UnitVolumeLiterMilli = "Milliliter" -UnitVolumeTeaspoon = "Teáskanál" -UnitVolumeTablespoon = "evőkanál" -UnitVolumeFluidOunce = "Folyadék uncia" -UnitVolumeCup = "Kupa" -UnitVolumePint = "Pint" -UnitVolumeQuart = "Quart" -UnitVolumeGallon = "Gallon" -UnitMetricMenu = "Metrikus" -UnitImperialMenu = "Birodalmi" -Toolbox = "Eszköztár" -AbsoluteValue = "Abszolút érték" -NthRoot = "n-gyökér" -BasedLogarithm = "Logaritmus az alap" -Calculation = "Számítás" -ComplexNumber = "Komplex számok" -Combinatorics = "Kombinatorika" -Arithmetic = "számtani" -Matrices = "Mátrix" -NewMatrix = "Új mátrix" -Identity = "n méretü azonosító mátrix" -Lists = "Lista" -HyperbolicTrigonometry = "Hiperbolikus trigonometria" -Fluctuation = "Jósolt intervallum" -InfinityMessage = "Végtelen" -DerivateNumber = "Származékos" -Integral = "Integral" -Sum = "Összeg" -Product = "Termék" -ComplexAbsoluteValue = "Abszolút érték" -Agument = "érv" -RealPart = "Igazi rész" -ImaginaryPart = "Képzeletbeli rész" -Conjugate = "Konjugátum" -Combination = "Kombináció" -Permutation = "Permutáció" -GreatCommonDivisor = "GCD" -LeastCommonMultiple = "LCM" -Remainder = "P maradékos osztás q-val" -Quotient = "P hányados osztás q-val" -Inverse = "Inverz" -Determinant = "determináns" -Transpose = "Átültetés" -Trace = "Trace" -Dimension = "Méret" -RowEchelonForm = "Sor echelon forma" -ReducedRowEchelonForm = "Csökkentett sorú echelon forma" -Vectors = "Vektorok" -Dot = "Pont termék" -Cross = "Kereszt termék" -NormVector = "Norm" -Sort = "Növekvö sorrend" -InvSort = "Csökkenö rendezés" -Maximum = "Maximum" -Minimum = "Minimum" -Floor = "Emelet" -FracPart = "Frakciós rész" -Ceiling = "Plafon" -Rounding = "N számjegyre kerekítés" -HyperbolicCosine = "Hiperbolikus koszinusz" -HyperbolicSine = "Hiperbolikus szinusz" -HyperbolicTangent = "Hiperbolikus érintö" -InverseHyperbolicCosine = "Inverz hiperbolikus koszinusz" -InverseHyperbolicSine = "Inverz hiperbolikus szinusz" -InverseHyperbolicTangent = "Inverz hiperbolikus érintö" -Prediction95 = "Jóslási intervallum 95%" -Prediction = "Egyszerü elörejelzési intervallum" -Confidence = "Bizalmi intervallum" -RandomAndApproximation = "Véletlen és közelítés" -RandomFloat = "Lebegöpontos szám [0,1-ben [" -RandomInteger = "Véletlen egész szám [a, b] -ben" -PrimeFactorDecomposition = "Egész szám tényezö" -NormCDF = "P (X +#include "../global_preferences.h" namespace USB { -static I18n::Message sUSBConnectedMessages[] = { +static constexpr I18n::Message sUSBConnectedMessages[] = { I18n::Message::USBConnected, I18n::Message::ConnectedMessage1, I18n::Message::ConnectedMessage2, @@ -11,9 +12,10 @@ static I18n::Message sUSBConnectedMessages[] = { I18n::Message::BlankMessage, I18n::Message::ConnectedMessage4, I18n::Message::ConnectedMessage5, - I18n::Message::ConnectedMessage6}; + I18n::Message::ConnectedMessage6 +}; -static KDColor sUSBConnectedColors[] = { +static constexpr KDColor sUSBConnectedFGColors[] = { Palette::PrimaryText, Palette::PrimaryText, Palette::PrimaryText, @@ -21,12 +23,43 @@ static KDColor sUSBConnectedColors[] = { KDColorWhite, Palette::PrimaryText, Palette::PrimaryText, - Palette::PrimaryText}; + Palette::PrimaryText, + Palette::PrimaryText, + Palette::AccentText}; -USBConnectedController::USBConnectedController() : - ViewController(nullptr), - m_messageView(sUSBConnectedMessages, sUSBConnectedColors, 8) +USBConnectedController::USBConnectedController() : + ViewController(nullptr) { + +} + +USBConnectedController::ContentView::ContentView() { + for (uint8_t i = 0; i < k_numberOfUSBMessages; i++) { + m_messageTextViews[i].setFont(i == 0 ? KDFont::LargeFont : KDFont::SmallFont); + m_messageTextViews[i].setAlignment(0.5f, 0.5f); + m_messageTextViews[i].setTextColor(sUSBConnectedFGColors[i]); + m_messageTextViews[i].setBackgroundColor(Palette::BackgroundHard); + m_messageTextViews[i].setText(I18n::translate(sUSBConnectedMessages[i])); + } +} + +void USBConnectedController::ContentView::drawRect(KDContext *ctx, KDRect rect) const { + ctx->fillRect(bounds(), Palette::BackgroundHard); +} + +View *USBConnectedController::ContentView::subviewAtIndex(int index) { + assert(index < k_numberOfUSBMessages); + return &(m_messageTextViews[index]); +} + +void USBConnectedController::ContentView::layoutSubviews(bool force) { + KDCoordinate width = bounds().width(); + KDCoordinate titleHeight = m_messageTextViews[0].minimalSizeForOptimalDisplay().height(); + KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height(); + m_messageTextViews[0].setFrame(KDRect(0, k_titleMargin, width, titleHeight), force); + for (uint8_t i = 1; i < k_numberOfUSBMessages; i++) { + m_messageTextViews[i].setFrame(KDRect(0, k_paragraphHeight + (i - 1) * textHeight, width, textHeight), force); + } } } diff --git a/apps/usb/usb_connected_controller.h b/apps/usb/usb_connected_controller.h index c733062ed14..d6e3de3eb7a 100644 --- a/apps/usb/usb_connected_controller.h +++ b/apps/usb/usb_connected_controller.h @@ -2,20 +2,32 @@ #define USB_USB_CONNECTED_CONTROLLER_H #include -#include "../shared/message_view.h" namespace USB { class USBConnectedController : public ViewController { public: USBConnectedController(); - View * view() override { return &m_messageView; } - bool handleEvent(Ion::Events::Event event) override { return false; } + View * view() override { return &m_contentView; } + bool handleEvent(Ion::Events::Event event) override { return false; }; private: - MessageView m_messageView; + class ContentView : public View { + public: + ContentView(); + void drawRect(KDContext * ctx, KDRect rect) const override; + protected: + int numberOfSubviews() const override { return k_numberOfUSBMessages; } + View * subviewAtIndex(int index) override; + void layoutSubviews(bool force = false) override; + private: + constexpr static KDCoordinate k_titleMargin = 30; + constexpr static KDCoordinate k_paragraphHeight = 80; + constexpr static uint8_t k_numberOfUSBMessages = 8; + MessageTextView m_messageTextViews[k_numberOfUSBMessages]; + }; + ContentView m_contentView; }; } - #endif diff --git a/apps/usb/usb_view.cpp b/apps/usb/usb_view.cpp new file mode 100644 index 00000000000..2eab7ee40d2 --- /dev/null +++ b/apps/usb/usb_view.cpp @@ -0,0 +1,56 @@ +#include "usb_view.h" +#include + +namespace USB { + USBView::USBView(I18n::Message *messages, KDColor *fgcolors, KDColor *bgcolors, uint8_t numberOfMessages) { + m_numberOfMessages = numberOfMessages < k_maxNumberOfMessages ? numberOfMessages : k_maxNumberOfMessages; + for (uint8_t i = 0; i < m_numberOfMessages; i++) { + m_messageTextViews[i].setFont(i == 0 ? KDFont::LargeFont : KDFont::SmallFont); + m_messageTextViews[i].setMessage(messages[i]); + m_messageTextViews[i].setAlignment(0.5f, 0.5f); + m_messageTextViews[i].setTextColor(fgcolors[i]); + m_messageTextViews[i].setBackgroundColor(bgcolors[i]); + } + } + + void USBView::drawRect(KDContext *ctx, KDRect rect) const { + ctx->fillRect(bounds(), Palette::BackgroundHard); + } + + View *USBView::subviewAtIndex(int index) { + if (index >= m_numberOfMessages) { + assert(false); + return nullptr; + } + return &(m_messageTextViews[index]); + } + + void USBView::update(I18n::Message *messages, KDColor *fgcolors, KDColor *bgcolors, uint8_t numberOfMessages){ + m_numberOfMessages = numberOfMessages < k_maxNumberOfMessages ? numberOfMessages : k_maxNumberOfMessages; + for (uint8_t i = 0; i < m_numberOfMessages; i++) { + m_messageTextViews[i].setFont(i == 0 ? KDFont::LargeFont : KDFont::SmallFont); + m_messageTextViews[i].setMessage(messages[i]); + m_messageTextViews[i].setAlignment(0.5f, 0.5f); + m_messageTextViews[i].setTextColor(fgcolors[i]); + m_messageTextViews[i].setBackgroundColor(bgcolors[i]); + } + reload(); + } + + void USBView::reload() { + markRectAsDirty(bounds()); + } + + void USBView::layoutSubviews(bool force) { + if (m_numberOfMessages == 0) { + return; + } + KDCoordinate width = bounds().width(); + KDCoordinate titleHeight = m_messageTextViews[0].minimalSizeForOptimalDisplay().height(); + KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height(); + m_messageTextViews[0].setFrame(KDRect(0, k_titleMargin, width, titleHeight), force); + for (uint8_t i = 1; i < m_numberOfMessages; i++) { + m_messageTextViews[i].setFrame(KDRect(0, k_paragraphHeight + (i - 1) * textHeight, width, textHeight), force); + } + } +} diff --git a/apps/usb/usb_view.h b/apps/usb/usb_view.h new file mode 100644 index 00000000000..67e274093aa --- /dev/null +++ b/apps/usb/usb_view.h @@ -0,0 +1,30 @@ +#ifndef USB_VIEW_H +#define USB_VIEW_H + +#include + +namespace USB +{ + class USBView : public View + { + public: + USBView(I18n::Message *messages, KDColor *fgcolors, KDColor *bgcolors, uint8_t numberOfMessages); + void drawRect(KDContext *ctx, KDRect rect) const override; + void reload(); + void update(I18n::Message *messages, KDColor *fgcolors, KDColor *bgcolors, uint8_t numberOfMessages); + protected: + int numberOfSubviews() const override { return m_numberOfMessages; } + View *subviewAtIndex(int index) override; + void layoutSubviews(bool force = false) override; + + private: + constexpr static KDCoordinate k_titleMargin = 30; + constexpr static KDCoordinate k_paragraphHeight = 80; + constexpr static uint8_t k_maxNumberOfMessages = 10; + MessageTextView m_messageTextViews[k_maxNumberOfMessages]; + uint8_t m_numberOfMessages; + }; + +} + +#endif diff --git a/apps/xnt_loop.cpp b/apps/xnt_loop.cpp new file mode 100644 index 00000000000..e81b6d1aa59 --- /dev/null +++ b/apps/xnt_loop.cpp @@ -0,0 +1,22 @@ +#include "xnt_loop.h" +#include + +CodePoint XNTLoop::XNT(CodePoint defaultCodePoint, bool * shouldRemoveLastCharacter) { + assert(shouldRemoveLastCharacter != nullptr); + static constexpr CodePoint XNTCodePoints[] = {'x', 'n', 't', UCodePointGreekSmallLetterTheta}; + int XNTCodePointSize = sizeof(XNTCodePoints) / sizeof(CodePoint); + + if (m_loopIndex == -1) { + for (int i = 0; i < XNTCodePointSize; i++) { + if (XNTCodePoints[i] == defaultCodePoint) { + m_loopIndex = i; + break; + } + } + } else { + *shouldRemoveLastCharacter = true; + m_loopIndex = (m_loopIndex + 1) % XNTCodePointSize; + } + + return XNTCodePoints[m_loopIndex]; +} diff --git a/apps/xnt_loop.h b/apps/xnt_loop.h new file mode 100644 index 00000000000..e9ed8a0c915 --- /dev/null +++ b/apps/xnt_loop.h @@ -0,0 +1,15 @@ +#ifndef APPS_XNT_LOOP +#define APPS_XNT_LOOP + +#include + +class XNTLoop { +public: + XNTLoop(): m_loopIndex(-1) {} + void reset() { m_loopIndex = -1; } + CodePoint XNT(CodePoint defaultCodePoint, bool * shouldRemoveLastCharacter); +private: + int m_loopIndex; +}; + +#endif diff --git a/bootloader/Makefile b/bootloader/Makefile index 519a847cda7..31edbf00e8b 100644 --- a/bootloader/Makefile +++ b/bootloader/Makefile @@ -1,21 +1,48 @@ bootloader_src += $(addprefix bootloader/,\ + utility.cpp \ boot.cpp \ main.cpp \ - kernel_header.cpp \ - userland_header.cpp \ - slot.cpp \ - interface.cpp \ jump_to_firmware.s \ trampoline.cpp \ - usb_desc.cpp \ + recovery.cpp \ + usb_data.cpp \ +) + +bootloader_src += $(addprefix bootloader/slots/, \ + slot_exam_mode.cpp \ + slot.cpp \ + userland_header.cpp \ + kernel_header.cpp \ +) + +bootloader_src += $(addprefix bootloader/drivers/, \ + stm32_drivers.cpp \ +) + +bootloader_src += $(addprefix bootloader/interface/static/, \ + interface.cpp \ +) + +bootloader_src += $(addprefix bootloader/interface/src/,\ + menu.cpp \ +) + + +bootloader_src += $(addprefix bootloader/interface/menus/, \ + about.cpp \ + home.cpp \ + dfu.cpp \ + installer.cpp \ + warning.cpp \ + slot_recovery.cpp \ + crash.cpp \ ) bootloader_images = $(addprefix bootloader/, \ - cable.png \ computer.png \ ) -bootloader_src += $(filter-out ion/src/device/shared/drivers/usb_desc.cpp,$(ion_src)) $(kandinsky_src) $(liba_src) $(libaxx_src) $(bootloader_images) +bootloader_src += $(ion_src) $(simple_kandinsky_src) $(liba_src) $(libaxx_src) $(bootloader_images) -$(eval $(call depends_on_image,bootloader/interface.cpp,$(bootloader_images))) +$(eval $(call depends_on_image,bootloader/interface/static/interface.cpp,$(bootloader_images))) diff --git a/bootloader/boot.cpp b/bootloader/boot.cpp index 1ec899b4a2c..8734d757644 100644 --- a/bootloader/boot.cpp +++ b/bootloader/boot.cpp @@ -1,66 +1,151 @@ #include -#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include -#include +#include +#include #include +using namespace Utility; + +extern "C" { + extern char _fake_isr_function_start; +} + namespace Bootloader { +BootConfig * Boot::config() { + static BootConfig * bootcfg = new BootConfig(); + return bootcfg; +} + BootMode Boot::mode() { - // We use the exam mode driver as storage for the boot mode - uint8_t mode = Ion::ExamMode::FetchExamMode(); + return BootMode::SlotA; +} - if (mode > 3) - return Unknown; +void Boot::setMode(BootMode mode) { + // We dont use the exam mode driver as storage for the boot mode because we need the 16k of storage x) +} + +void Boot::busError() { + Ion::Device::Flash::ClearInternalFlashErrors(); + asm("mov r12, lr"); + if (config()->isBooting()) { // Bus error is normal if we are booting, it's triggered when we lock OPTCR + asm("mov lr, r12"); + asm("bx lr"); + } + Bootloader::Recovery::crash_handler("BusFault"); +} + +bool Boot::isKernelPatched(const Slot & s) { + if (s.userlandHeader()->isOmega()) { + // we don't need to patch the kernel + return true; + } + + // It's an epsilon kernel, so we need to check if it's patched + + uint32_t origin_isr = s.address() + sizeof(Bootloader::KernelHeader) - sizeof(uint32_t) * 3; + + if (*(uint32_t *)(origin_isr + sizeof(uint32_t) * 12) == (uint32_t)0x0) { + // fake epsilon + return true; + } + + // return *(uint32_t *)(origin_isr + sizeof(uint32_t) * 4) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 5) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 6) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 7) == ((uint32_t)&_fake_isr_function_start) + 1;*(uint32_t *)(origin_isr + sizeof(uint32_t) * 4) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 5) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 6) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 7) == ((uint32_t)&_fake_isr_function_start) + 1;*(uint32_t *)(origin_isr + sizeof(uint32_t) * 4) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 5) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 6) == ((uint32_t)&_fake_isr_function_start) + 1 && *(uint32_t *)(origin_isr + sizeof(uint32_t) * 7) == ((uint32_t)&_fake_isr_function_start) + 1; + return *(uint32_t *)(origin_isr + sizeof(uint32_t) * 7) == ((uint32_t)&_fake_isr_function_start) + 1; +} + +__attribute((section(".fake_isr_function"))) __attribute__((used)) void Boot::flash_interrupt() { + // a simple function + Ion::Device::Flash::ClearInternalFlashErrors(); + asm("bx lr"); +} + +void Boot::patchKernel(const Slot & s) { + uint32_t origin_isr = s.address() + sizeof(Bootloader::KernelHeader) - sizeof(uint32_t) * 3 - 0x90000000; + // we allocate a big buffer to store the first sector + uint8_t data[1024*4]; + memcpy(data, (void*)0x90000000, 1024*4); + uint32_t dummy_address = (uint32_t)&_fake_isr_function_start + 1; + uint8_t * ptr = (uint8_t *)&dummy_address; + data[origin_isr + sizeof(uint32_t) * 6] = ptr[0]; // BusFault + data[origin_isr + sizeof(uint32_t) * 6 + 1] = ptr[1]; + data[origin_isr + sizeof(uint32_t) * 6 + 2] = ptr[2]; + data[origin_isr + sizeof(uint32_t) * 6 + 3] = ptr[3]; + + // data[origin_isr + sizeof(uint32_t) * 5] = ptr[0]; // MemManage + // data[origin_isr + sizeof(uint32_t) * 5 + 1] = ptr[1]; + // data[origin_isr + sizeof(uint32_t) * 5 + 2] = ptr[2]; + // data[origin_isr + sizeof(uint32_t) * 5 + 3] = ptr[3]; - return (BootMode) mode; + data[origin_isr + sizeof(uint32_t) * 7] = ptr[0]; // UsageFault + data[origin_isr + sizeof(uint32_t) * 7 + 1] = ptr[1]; + data[origin_isr + sizeof(uint32_t) * 7 + 2] = ptr[2]; + data[origin_isr + sizeof(uint32_t) * 7 + 3] = ptr[3]; + + // data[origin_isr + sizeof(uint32_t) * 4] = ptr[0];//hardfault + // data[origin_isr + sizeof(uint32_t) * 4 + 1] = ptr[1]; + // data[origin_isr + sizeof(uint32_t) * 4 + 2] = ptr[2]; + // data[origin_isr + sizeof(uint32_t) * 4 + 3] = ptr[3]; + + Ion::Device::ExternalFlash::EraseSector(0); + Ion::Device::ExternalFlash::WriteMemory((uint8_t*)0x90000000, data, 1024*4); } -void Boot::setMode(BootMode mode) { - BootMode currentMode = Boot::mode(); - if (currentMode == mode) - return; +void Boot::bootSlot(Bootloader::Slot s) { + config()->setSlot(&s); + if (!s.userlandHeader()->isOmega() && !s.userlandHeader()->isUpsilon()) { + // We are trying to boot epsilon, so we check the version and show an advertisement if needed + const char * version = s.userlandHeader()->version(); + const char * min = "21.2.1"; + int versionSum = Utility::versionSum(version, strlen(version)); + int minimalVersionTrigger = Utility::versionSum(min, strlen(min)); + if (versionSum >= minimalVersionTrigger) { + WarningMenu menu = WarningMenu(); + menu.open(); + return; + } + } + bootSelectedSlot(); +} - assert(mode != BootMode::Unknown); - int8_t deltaMode = (int8_t)mode - (int8_t)currentMode; - deltaMode = deltaMode < 0 ? deltaMode + 4 : deltaMode; - assert(deltaMode > 0); - Ion::ExamMode::IncrementExamMode(deltaMode); +void Boot::bootSelectedSlot() { + lockInternal(); + config()->setBooting(true); + Ion::Device::Flash::LockInternalFlashForSession(); + config()->slot()->boot(); } __attribute__((noreturn)) void Boot::boot() { assert(mode() != BootMode::Unknown); - if (!Slot::A().kernelHeader()->isValid() && !Slot::B().kernelHeader()->isValid()) { - // Bootloader if both invalid - bootloader(); - } else if (!Slot::A().kernelHeader()->isValid()) { - // If slot A is invalid and B valid, boot B - setMode(BootMode::SlotB); - Slot::B().boot(); - } else if (!Slot::B().kernelHeader()->isValid()) { - // If slot B is invalid and A valid, boot A - setMode(BootMode::SlotA); - Slot::A().boot(); - } else { - // Both valid, boot the selected one - if (mode() == BootMode::SlotA) { - Slot::A().boot(); - } else if (mode() == BootMode::SlotB) { - Slot::B().boot(); - } + Boot::config()->clearSlot(); + Boot::config()->setBooting(false); + + while (true) { + HomeMenu menu = HomeMenu(); + menu.open(true); } - // Achivement unlocked: How Did We Get Here? + // Achievement unlocked: How Did We Get Here? bootloader(); } -__attribute__ ((noreturn)) void Boot::bootloader() { +void Boot::bootloader() { + USBData data = USBData::DEFAULT(); for(;;) { // Draw the interfaces and infos - Bootloader::Interface::draw(); + Bootloader::Interface::drawFlasher(); // Enable USB Ion::USB::enable(); @@ -70,13 +155,34 @@ __attribute__ ((noreturn)) void Boot::bootloader() { // If we pressed back while waiting, reset. uint64_t scan = Ion::Keyboard::scan(); if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { - Ion::Device::Reset::core(); + // Disable USB, redraw the menu and return + Ion::USB::disable(); + return; + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { + Ion::Power::standby(); // Force a core reset to exit } } while (!Ion::USB::isEnumerated()); // Launch the DFU stack, allowing to press Back to quit and reset - Ion::USB::DFU(true); + Ion::USB::DFU(true, &data); } } +void Boot::jumpToInternalBootloader() { + Ion::Device::Board::jumpToInternalBootloader(); +} + +void Boot::lockInternal() { + Ion::Device::Flash::DisableInternalProtection(); + Ion::Device::Flash::SetInternalSectorProtection(0, true); + Ion::Device::Flash::SetInternalSectorProtection(1, true); + Ion::Device::Flash::SetInternalSectorProtection(2, true); + Ion::Device::Flash::SetInternalSectorProtection(3, true); + Ion::Device::Flash::EnableInternalProtection(); +} + +void Boot::EnableInternalFlashInterrupt() { + Ion::Device::Flash::EnableInternalFlashInterrupt(); +} + } diff --git a/bootloader/boot.h b/bootloader/boot.h index bb5df17c1b1..300fb762c0a 100644 --- a/bootloader/boot.h +++ b/bootloader/boot.h @@ -2,27 +2,59 @@ #define BOOTLOADER_BOOT_H #include +#include namespace Bootloader { +class BootConfig { +public: + BootConfig() : m_slot(nullptr), m_booting(false) {}; + + void setSlot(Slot * slot) { m_slot = slot; } + Slot * slot() const { return m_slot; } + void clearSlot() { m_slot = nullptr; } + + void setBooting(bool booting) { m_booting = booting; } + bool isBooting() const { return m_booting; } +private: + Bootloader::Slot * m_slot; + bool m_booting; +}; + enum BootMode: uint8_t { - SlotA = 0, - SlotB = 1, - // These modes exists so that you can launch the bootloader from a running slot - // They mean "Launch bootloader then go back to slot X" - SlotABootloader = 2, - SlotBBootloader = 3, - Unknown + SlotA = 0, + SlotB = 1, + // These modes exists so that you can launch the bootloader from a running slot + // They mean "Launch bootloader then go back to slot X" + SlotABootloader = 2, + SlotBBootloader = 3, + Unknown }; class Boot { public: static BootMode mode(); static void setMode(BootMode mode); + static BootConfig * config(); + + static bool isKernelPatched(const Slot & slot); + static void patchKernel(const Slot & slot); + + static void busError(); + __attribute__ ((noreturn)) static void boot(); - __attribute__ ((noreturn)) static void bootloader(); + + static void bootSlot(Bootloader::Slot slot); + static void bootSelectedSlot(); + __attribute__ ((noreturn)) static void jumpToInternalBootloader(); + __attribute((section(".fake_isr_function"))) __attribute__((used)) static void flash_interrupt(); + + static void bootloader(); + static void lockInternal(); + static void EnableInternalFlashInterrupt(); }; + } -#endif \ No newline at end of file +#endif diff --git a/bootloader/boot/isr.c b/bootloader/boot/isr.c new file mode 100644 index 00000000000..77b1121938e --- /dev/null +++ b/bootloader/boot/isr.c @@ -0,0 +1,129 @@ +#include "isr.h" +extern const void * _stack_start; + +/* Interrupt Service Routines are void->void functions */ +typedef void(*ISR)(void); + +/* Notice: The Cortex-M4 expects all jumps to be made at an odd address when + * jumping to Thumb code. For example, if you want to execute Thumb code at + * address 0x100, you'll have to jump to 0x101. Luckily, this idiosyncrasy is + * properly handled by the C compiler that will generate proper addresses when + * using function pointers. */ + +#define INITIALISATION_VECTOR_SIZE 0x71 + +ISR InitialisationVector[INITIALISATION_VECTOR_SIZE] + __attribute__((section(".isr_vector_table"))) + __attribute__((used)) + = { + (ISR)&_stack_start, // Stack start + start, // Reset service routine, + 0, // NMI service routine, + hard_fault_handler, // HardFault service routine, + mem_fault_handler, // MemManage service routine, + bus_fault_handler, // BusFault service routine, + usage_fault_handler, // UsageFault service routine, + 0, 0, 0, 0, // Reserved + 0, // SVCall service routine, + 0, // DebugMonitor service routine, + 0, // Reserved + 0, // PendSV service routine, + isr_systick, // SysTick service routine + 0, // WWDG service routine + 0, // PVD service routine + 0, // TampStamp service routine + 0, // RtcWakeup service routine + 0, // Flash service routine + 0, // RCC service routine + 0, // EXTI0 service routine + 0, // EXTI1 service routine + 0, // EXTI2 service routine + 0, // EXTI3 service routine + 0, // EXTI4 service routine + 0, // DMA1Stream0 service routine + 0, // DMA1Stream1 service routine + 0, // DMA1Stream2 service routine + 0, // DMA1Stream3 service routine + 0, // DMA1Stream4 service routine + 0, // DMA1Stream5 service routine + 0, // DMA1Stream6 service routine + 0, // ADC1 global interrupt + 0, // CAN1 TX interrupt + 0, // CAN1 RX0 interrupt + 0, // CAN1 RX1 interrupt + 0, // CAN1 SCE interrupt + 0, // EXTI Line[9:5] interrupts + 0, // TIM1 Break interrupt and TIM9 global interrupt + 0, // TIM1 update interrupt and TIM10 global interrupt + 0, // TIM1 Trigger & Commutation interrupts and TIM11 global interrupt + 0, // TIM1 Capture Compare interrupt + 0, // TIM2 global interrupt + 0, // TIM3 global interrupt + 0, // TIM4 global interrupt + 0, // I2C1 global event interrupt + 0, // I2C1 global error interrupt + 0, // I2C2 global event interrupt + 0, // I2C2 global error interrupt + 0, // SPI1 global interrupt + 0, // SPI2 global interrupt + 0, // USART1 global interrupt + 0, // USART2 global interrupt + 0, // USART3 global interrupt + 0, // EXTI Line[15:10] interrupts + 0, // EXTI Line 17 interrupt RTC Alarms (A and B) through EXTI line interrupt + 0, // EXTI Line 18 interrupt / USB On-The-Go FS Wakeup through EXTI line interrupt + 0, // TIM8 Break interrupt TIM12 global interrupt + 0, // TIM8 Update interrupt TIM13 global interrupt + 0, // TIM8 Trigger & Commutation interrupt TIM14 global interrupt + 0, // TIM8 Cap/Com interrupt + 0, // DMA1 global interrupt Channel 7 + 0, // FSMC global interrupt + 0, // SDIO global interrupt + 0, // TIM5 global interrupt + 0, // SPI3 global interrupt + 0, // ? + 0, // ? + 0, // TIM6 global interrupt + 0, // TIM7 global interrupt + 0, // DMA2 Stream0 global interrupt + 0, // DMA2 Stream1 global interrupt + 0, // DMA2 Stream2 global interrupt + 0, // DMA2 Stream3 global interrupt + 0, // DMA2 Stream4 global interrupt + 0, // SD filter0 global interrupt + 0, // SD filter1 global interrupt + 0, // CAN2 TX interrupt + 0, // BXCAN2 RX0 interrupt + 0, // BXCAN2 RX1 interrupt + 0, // CAN2 SCE interrupt + 0, // USB On The Go FS global interrupt + 0, // DMA2 Stream5 global interrupts + 0, // DMA2 Stream6 global interrupt + 0, // DMA2 Stream7 global interrupt + 0, // USART6 global interrupt + 0, // I2C3 event interrupt + 0, // I2C3 error interrupt + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // RNG global interrupt + 0, // FPU global interrupt + 0, // ? + 0, // ? + 0, // SPI4 global interrupt + 0, // SPI5 global interrupt + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // Quad-SPI global interrupt + 0, // ? + 0, // ? + 0, // I2CFMP1 event interrupt + 0 // I2CFMP1 error interrupt +}; diff --git a/bootloader/boot/isr.h b/bootloader/boot/isr.h new file mode 100644 index 00000000000..aa980065e25 --- /dev/null +++ b/bootloader/boot/isr.h @@ -0,0 +1,23 @@ +#ifndef ION_DEVICE_BOOT_ISR_H +#define ION_DEVICE_BOOT_ISR_H + +#ifdef __cplusplus +extern "C" { +#endif + +void start(); +void abort(); +void isr_systick(); + +// Fault handlers + +void hard_fault_handler(); +void mem_fault_handler(); +void usage_fault_handler(); +void bus_fault_handler(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/bootloader/boot/rt0.cpp b/bootloader/boot/rt0.cpp new file mode 100644 index 00000000000..06221b25274 --- /dev/null +++ b/bootloader/boot/rt0.cpp @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef void (*cxx_constructor)(); + +extern "C" { + extern char _data_section_start_flash; + extern char _data_section_start_ram; + extern char _data_section_end_ram; + extern char _bss_section_start_ram; + extern char _bss_section_end_ram; + extern cxx_constructor _init_array_start; + extern cxx_constructor _init_array_end; +} + +void __attribute__((noinline)) abort() { +#ifdef NDEBUG + Ion::Device::Reset::core(); +#else + while (1) { + } +#endif +} + + +void __attribute__((interrupt, noinline)) isr_systick() { + auto t = Ion::Device::Timing::MillisElapsed; + t++; + Ion::Device::Timing::MillisElapsed = t; +} + +void __attribute__((noinline)) hard_fault_handler() { + Bootloader::Recovery::crash_handler("HardFault"); +} + +void __attribute__((noinline)) mem_fault_handler() { + Bootloader::Recovery::crash_handler("MemoryFault"); +} + +void __attribute__((noinline)) usage_fault_handler() { + Bootloader::Recovery::crash_handler("UsageFault"); +} + +void __attribute__((noinline)) bus_fault_handler() { + Bootloader::Boot::busError(); +} + +/* In order to ensure that this method is execute from the external flash, we + * forbid inlining it.*/ + +static void __attribute__((noinline)) external_flash_start() { + /* Init the peripherals. We do not initialize the backlight in case there is + * an on boarding app: indeed, we don't want the user to see the LCD tests + * happening during the on boarding app. The backlight will be initialized + * after the Power-On Self-Test if there is one or before switching to the + * home app otherwise. */ + Ion::Device::Board::initPeripherals(false); + + return ion_main(0, nullptr); +} + +/* This additional function call 'jump_to_external_flash' serves two purposes: + * - By default, the compiler is free to inline any function call he wants. If + * the compiler decides to inline some functions that make use of VFP + * registers, it will need to push VFP them onto the stack in calling + * function's prologue. + * Problem: in start()'s prologue, we would never had a chance to enable the + * FPU since this function is the first thing called after reset. + * We can safely assume that neither memcpy, memset, nor any Ion::Device::init* + * method will use floating-point numbers, but ion_main very well can. + * To make sure ion_main's potential usage of VFP registers doesn't bubble-up to + * start(), we isolate it in its very own non-inlined function call. + * - To avoid jumping on the external flash when it is shut down, we ensure + * there is no symbol references from the internal flash to the external + * flash except this jump. In order to do that, we isolate this + * jump in a symbol that we link in a special section separated from the + * internal flash section. We can than forbid cross references from the + * internal flash to the external flash. */ + +static void __attribute__((noinline)) jump_to_external_flash() { + external_flash_start(); +} + +/* When 'start' is executed, the external flash is supposed to be shutdown. We + * thus forbid inlining to prevent executing this code from external flash + * (just in case 'start' was to be called from the external flash). */ + +void __attribute__((noinline)) start() { + /* This is where execution starts after reset. + * Many things are not initialized yet so the code here has to pay attention. */ + + /* Initialize the FPU as early as possible. + * For example, static C++ objects are very likely to manipulate float values */ + Ion::Device::Board::initFPU(); + + + + /* Copy data section to RAM + * The data section is R/W but its initialization value matters. It's stored + * in Flash, but linked as if it were in RAM. Now's our opportunity to copy + * it. Note that until then the data section (e.g. global variables) contains + * garbage values and should not be used. */ + size_t dataSectionLength = (&_data_section_end_ram - &_data_section_start_ram); + memcpy(&_data_section_start_ram, &_data_section_start_flash, dataSectionLength); + + /* Zero-out the bss section in RAM + * Until we do, any uninitialized global variable will be unusable. */ + size_t bssSectionLength = (&_bss_section_end_ram - &_bss_section_start_ram); + memset(&_bss_section_start_ram, 0, bssSectionLength); + + + /* Call static C++ object constructors + * The C++ compiler creates an initialization function for each static object. + * The linker then stores the address of each of those functions consecutively + * between _init_array_start and _init_array_end. So to initialize all C++ + * static objects we just have to iterate between theses two addresses and + * call the pointed function. */ +#define SUPPORT_CPP_GLOBAL_CONSTRUCTORS 0 +#if SUPPORT_CPP_GLOBAL_CONSTRUCTORS + for (cxx_constructor * c = &_init_array_start; c<&_init_array_end; c++) { + (*c)(); + } +#else + /* In practice, static initialized objects are a terrible idea. Since the init + * order is not specified, most often than not this yields the dreaded static + * init order fiasco. How about bypassing the issue altogether? */ + if (&_init_array_start != &_init_array_end) { + abort(); + } +#endif + + /* At this point, we initialized clocks and the external flash but no other + * peripherals. */ + Ion::Device::Board::init(); + + jump_to_external_flash(); + + abort(); +} diff --git a/bootloader/cable.png b/bootloader/cable.png new file mode 100644 index 00000000000..ac0996ca746 Binary files /dev/null and b/bootloader/cable.png differ diff --git a/bootloader/computer.png b/bootloader/computer.png new file mode 100644 index 00000000000..d30a99af2af Binary files /dev/null and b/bootloader/computer.png differ diff --git a/bootloader/drivers/board.cpp b/bootloader/drivers/board.cpp new file mode 100644 index 00000000000..0958a942f43 --- /dev/null +++ b/bootloader/drivers/board.cpp @@ -0,0 +1,463 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace STM32; +typedef void(*ISR)(void); +extern ISR InitialisationVector[]; + +// Public Ion methods + +const char * Ion::fccId() { + return "2ALWP-N0110"; +} + +// Private Ion::Device methods + +namespace Ion { +namespace Device { +namespace Board { + +using namespace Regs; + +void bootloaderMPU() { + // 1. Disable the MPU + // 1.1 Memory barrier + Cache::dmb(); + + // 1.3 Disable the MPU and clear the control register + MPU.CTRL()->setENABLE(false); + + MPU.RNR()->setREGION(7); + MPU.RBAR()->setADDR(0x90000000); + MPU.RASR()->setXN(false); + MPU.RASR()->setENABLE(true); + + // 2.3 Enable MPU + MPU.CTRL()->setENABLE(true); + + // 3. Data/instruction synchronisation barriers to ensure that the new MPU configuration is used by subsequent instructions. + Cache::disable(); + Cache::dsb(); + Cache::isb(); +} + +void initMPU() { + // 1. Disable the MPU + // 1.1 Memory barrier + Cache::dmb(); + + // 1.2 Disable fault exceptions + CORTEX.SHCRS()->setMEMFAULTENA(false); + + // 1.3 Disable the MPU and clear the control register + MPU.CTRL()->setENABLE(false); + + // 2. MPU settings + // 2.1 Configure a MPU region for the FMC memory area + /* This is needed for interfacing with the LCD + * We define the whole FMC memory bank 1 as strongly ordered, non-executable + * and not accessible. We define the FMC command and data addresses as + * writeable non-cachable, non-buffereable and non shareable. */ + int sector = 0; + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x60000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_256MB); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::NoAccess); + MPU.RASR()->setXN(true); + MPU.RASR()->setTEX(2); + MPU.RASR()->setS(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x60000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_32B); + MPU.RASR()->setXN(true); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW); + MPU.RASR()->setTEX(2); + MPU.RASR()->setS(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x60000000+0x20000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_32B); + MPU.RASR()->setXN(true); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW); + MPU.RASR()->setTEX(2); + MPU.RASR()->setS(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + // 2.2 Configure MPU regions for the QUADSPI peripheral + /* L1 Cache can issue speculative reads to any memory address. But, when the + * Quad-SPI is in memory-mapped mode, if an access is made to an address + * outside of the range defined by FSIZE but still within the 256Mbytes range, + * then an AHB error is given (AN4760). To prevent this to happen, we + * configure the MPU to define the whole Quad-SPI addressable space as + * strongly ordered, non-executable and not accessible. Plus, we define the + * Quad-SPI region corresponding to the Expternal Chip as executable and + * fully accessible (AN4861). */ + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x90000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_256MB); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::NoAccess); + MPU.RASR()->setXN(true); + MPU.RASR()->setTEX(0); + MPU.RASR()->setS(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + MPU.RNR()->setREGION(sector++); + MPU.RBAR()->setADDR(0x90000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_8MB); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW); + MPU.RASR()->setXN(false); + MPU.RASR()->setTEX(0); + MPU.RASR()->setS(0); + MPU.RASR()->setC(1); + MPU.RASR()->setB(0); + MPU.RASR()->setENABLE(true); + + // 2.3 Enable MPU + MPU.CTRL()->setPRIVDEFENA(true); + MPU.CTRL()->setENABLE(true); + + //2.4 Enable fault exceptions + CORTEX.SHCRS()->setMEMFAULTENA(true); + CORTEX.SHCRS()->setBUSFAULTENA(true); + CORTEX.SHCRS()->setUSGFAULTENA(true); + + // 3. Data/instruction synchronisation barriers to ensure that the new MPU configuration is used by subsequent instructions. + Cache::dsb(); + Cache::isb(); +} + +void init() { + initFPU(); + initMPU(); + initClocks(); + + // The bootloader leaves its own after flashing + //SYSCFG.MEMRMP()->setMEM_MODE(SYSCFG::MEMRMP::MemMode::MainFlashmemory); + // Ensure right location of interrupt vectors + CORTEX.VTOR()->setVTOR((void*)&InitialisationVector); + + // Put all inputs as Analog Input, No pull-up nor pull-down + // Except for the SWD port (PB3, PA13, PA14) + GPIOA.MODER()->set(0xEBFFFFFF); + GPIOA.PUPDR()->set(0x24000000); + GPIOB.MODER()->set(0xFFFFFFBF); + GPIOB.PUPDR()->set(0x00000000); + for (int g=2; g<5; g++) { + GPIO(g).MODER()->set(0xFFFFFFFF); // All to "Analog" + GPIO(g).PUPDR()->set(0x00000000); // All to "None" + } + + ExternalFlash::init(); + // Initiate L1 cache after initiating the external flash + Cache::enable(); +} + +void initClocks() { + /* System clock + * Configure the CPU at 192 MHz and USB at 48 MHz. */ + + /* After reset, the device is using the high-speed internal oscillator (HSI) + * as a clock source, which runs at a fixed 16 MHz frequency. The HSI is not + * accurate enough for reliable USB operation, so we need to use the external + * high-speed oscillator (HSE). */ + + // Enable the HSI and wait for it to be ready + RCC.CR()->setHSION(true); + while(!RCC.CR()->getHSIRDY()) { + } + + // Enable the HSE and wait for it to be ready + RCC.CR()->setHSEON(true); + while(!RCC.CR()->getHSERDY()) { + } + + // Enable PWR peripheral clock + RCC.APB1ENR()->setPWREN(true); + + /* To pass electromagnetic compatibility tests, we activate the Spread + * Spectrum clock generation, which adds jitter to the PLL clock in order to + * "lower peak-energy on the central frequency" and its harmonics. + * It must be done before enabling the PLL. */ + class RCC::SSCGR sscgr(0); // Reset value + sscgr.setMODPER(Clocks::Config::SSCG_MODPER); + sscgr.setINCSTEP(Clocks::Config::SSCG_INCSTEP); + sscgr.setSPREADSEL(RCC::SSCGR::SPREADSEL::CenterSpread); + sscgr.setSSCGEN(true); + RCC.SSCGR()->set(sscgr); + + /* Given the crystal used on our device, the HSE will oscillate at 8 MHz. By + * piping it through a phase-locked loop (PLL) we can derive other frequencies + * for use in different parts of the system. */ + + // Configure the PLL ratios and use HSE as a PLL input + RCC.PLLCFGR()->setPLLM(Clocks::Config::PLL_M); + RCC.PLLCFGR()->setPLLN(Clocks::Config::PLL_N); + RCC.PLLCFGR()->setPLLQ(Clocks::Config::PLL_Q); + RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); + + // Enable the PLL and wait for it to be ready + RCC.CR()->setPLLON(true); + + // Enable Over-drive + PWR.CR()->setODEN(true); + while(!PWR.CSR()->getODRDY()) { + } + + PWR.CR()->setODSWEN(true); + while(!PWR.CSR()->getODSWRDY()) { + } + + // Choose Voltage scale 1 + PWR.CR()->setVOS(PWR::CR::Voltage::Scale1); + while (!PWR.CSR()->getVOSRDY()) {} + + /* After reset the Flash runs as fast as the CPU. When we clock the CPU faster + * the flash memory cannot follow and therefore flash memory accesses need to + * wait a little bit. + * The spec tells us that at 2.8V and over 210MHz the flash expects 7 WS. */ + FLASH.ACR()->setLATENCY(7); + + /* Enable prefetching flash instructions */ + /* Fetching instructions increases slightly the power consumption but the + * increase is negligible compared to the screen consumption. */ + FLASH.ACR()->setPRFTEN(true); + + /* Enable the ART */ + FLASH.ACR()->setARTEN(true); + + // 192 MHz is too fast for APB1. Divide it by four to reach 48 MHz + RCC.CFGR()->setPPRE1(Clocks::Config::APB1PrescalerReg); + // 192 MHz is too fast for APB2. Divide it by two to reach 96 MHz + RCC.CFGR()->setPPRE2(Clocks::Config::APB2PrescalerReg); + + while(!RCC.CR()->getPLLRDY()) { + } + + // Use the PLL output as a SYSCLK source + RCC.CFGR()->setSW(RCC::CFGR::SW::PLL); + while (RCC.CFGR()->getSWS() != RCC::CFGR::SW::PLL) { + } + + // Now that we don't need use it anymore, turn the HSI off + RCC.CR()->setHSION(false); + + // Peripheral clocks + + // AHB1 bus + // Our peripherals are using GPIO A, B, C, D and E. + // We're not using the CRC nor DMA engines. + class RCC::AHB1ENR ahb1enr(0x00100000); // Reset value + ahb1enr.setGPIOAEN(true); + ahb1enr.setGPIOBEN(true); + ahb1enr.setGPIOCEN(true); + ahb1enr.setGPIODEN(true); + ahb1enr.setGPIOEEN(true); + ahb1enr.setDMA2EN(false); + RCC.AHB1ENR()->set(ahb1enr); + + // AHB2 bus + RCC.AHB2ENR()->setOTGFSEN(true); + + // AHB3 bus + RCC.AHB3ENR()->setFSMCEN(true); + + // APB1 bus + // We're using TIM3 for the LEDs (disabled to stick with E16+ Bootloader) + /*RCC.APB1ENR()->setTIM3EN(true); */ + RCC.APB1ENR()->setPWREN(true); + RCC.APB1ENR()->setRTCAPB(true); + + // APB2 bus + class RCC::APB2ENR apb2enr(0); // Reset value + apb2enr.setADC1EN(true); + apb2enr.setSYSCFGEN(true); + apb2enr.setUSART6EN(false); // TODO required if building bench target only? (disabled to stick with E16+ Bootloader) + RCC.APB2ENR()->set(apb2enr); + + // Configure clocks in sleep mode + // AHB1 peripheral clock enable in low-power mode register + class RCC::AHB1LPENR ahb1lpenr(0x7EF7B7FF); // Reset value + ahb1lpenr.setGPIOALPEN(true); // Enable IO port A for Charging/USB plug/Keyboard pins + ahb1lpenr.setGPIOBLPEN(true); // Enable IO port B for LED pins + ahb1lpenr.setGPIOCLPEN(true); // Enable IO port C for LED/Keyboard pins + ahb1lpenr.setGPIODLPEN(true); // Enable IO port D (LCD...) + ahb1lpenr.setGPIOELPEN(true); // Enable IO port E for Keyboard/Battery pins + ahb1lpenr.setGPIOFLPEN(true); // Enable IO port F + ahb1lpenr.setGPIOGLPEN(true); // Enable IO port G + ahb1lpenr.setGPIOHLPEN(true); // Enable IO port H + ahb1lpenr.setGPIOILPEN(true); // Enable IO port I + ahb1lpenr.setCRCLPEN(true); + ahb1lpenr.setFLITFLPEN(true); + ahb1lpenr.setSRAM1LPEN(true); + ahb1lpenr.setDMA1LPEN(true); + ahb1lpenr.setDMA2LPEN(true); + ahb1lpenr.setAXILPEN(true); + ahb1lpenr.setSRAM2LPEN(true); + ahb1lpenr.setBKPSRAMLPEN(true); + ahb1lpenr.setDTCMLPEN(true); + ahb1lpenr.setOTGHSLPEN(true); + ahb1lpenr.setOTGHSULPILPEN(true); + RCC.AHB1LPENR()->set(ahb1lpenr); + + // AHB2 peripheral clock enable in low-power mode register + class RCC::AHB2LPENR ahb2lpenr(0x000000F1); // Reset value + ahb2lpenr.setOTGFSLPEN(true); + ahb2lpenr.setRNGLPEN(true); + ahb2lpenr.setAESLPEN(true); + RCC.AHB2LPENR()->set(ahb2lpenr); + + // AHB3 peripheral clock enable in low-power mode register + class RCC::AHB3LPENR ahb3lpenr(0x00000003); // Reset value + ahb3lpenr.setFMCLPEN(true); + ahb3lpenr.setQSPILPEN(true); + RCC.AHB3LPENR()->set(ahb3lpenr); + + // APB1 peripheral clock enable in low-power mode register + class RCC::APB1LPENR apb1lpenr(0xFFFFCBFF); // Reset value + apb1lpenr.setTIM2LPEN(true); + apb1lpenr.setTIM3LPEN(true); // Enable TIM3 in sleep mode for LEDs + apb1lpenr.setTIM4LPEN(true); + apb1lpenr.setTIM5LPEN(true); + apb1lpenr.setTIM6LPEN(true); + apb1lpenr.setTIM7LPEN(true); + apb1lpenr.setTIM12LPEN(true); + apb1lpenr.setTIM13LPEN(true); + apb1lpenr.setTIM14LPEN(true); + apb1lpenr.setRTCAPBLPEN(true); + apb1lpenr.setWWDGLPEN(true); + apb1lpenr.setSPI2LPEN(true); + apb1lpenr.setSPI3LPEN(true); + apb1lpenr.setUSART2LPEN(true); + apb1lpenr.setUSART3LPEN(true); + apb1lpenr.setI2C1LPEN(true); + apb1lpenr.setI2C2LPEN(true); + apb1lpenr.setI2C3LPEN(true); + apb1lpenr.setCAN1LPEN(true); + apb1lpenr.setPWRLPEN(true); + apb1lpenr.setLPTIM1LPEN(true); + apb1lpenr.setUSART4LPEN(true); + apb1lpenr.setUSART5LPEN(true); + apb1lpenr.setOTGHSLPEN(true); + apb1lpenr.setOTGHSULPILPEN(true); + RCC.APB1LPENR()->set(apb1lpenr); + + // APB2 peripheral clock enable in low-power mode register + class RCC::APB2LPENR apb2lpenr(0x04F77F33); // Reset value + apb2lpenr.setTIM1LPEN(true); + apb2lpenr.setTIM8LPEN(true); + apb2lpenr.setUSART1LPEN(true); + apb2lpenr.setUSART6LPEN(true); + apb2lpenr.setADC1LPEN(true); + apb2lpenr.setSPI1LPEN(true); + apb2lpenr.setSPI4LPEN(true); + apb2lpenr.setSYSCFGLPEN(true); + apb2lpenr.setTIM9LPEN(true); + apb2lpenr.setTIM10LPEN(true); + apb2lpenr.setTIM11LPEN(true); + apb2lpenr.setSPI5LPEN(true); + apb2lpenr.setSDMMC2LPEN(true); + apb2lpenr.setADC2LPEN(true); + apb2lpenr.setADC3LPEN(true); + apb2lpenr.setSAI1LPEN(true); + apb2lpenr.setSAI2LPEN(true); + RCC.APB2LPENR()->set(apb2lpenr); +} + +void shutdownClocks(bool keepLEDAwake) { + // APB2 bus + RCC.APB2ENR()->set(0); // Reset value + + // AHB2 bus + RCC.AHB2ENR()->set(0); // Reset value + + // AHB3 bus + RCC.AHB3ENR()->set(0); // Reset value + + // APB1 + class RCC::APB1ENR apb1enr(0); // Reset value + // AHB1 bus + class RCC::AHB1ENR ahb1enr(0x00100000); // Reset value + if (keepLEDAwake) { + apb1enr.setTIM3EN(true); + apb1enr.setTIM5EN(true); + ahb1enr.setGPIOBEN(true); + } + RCC.APB1ENR()->set(apb1enr); + RCC.AHB1ENR()->set(ahb1enr); +} + +constexpr int k_pcbVersionOTPIndex = 0; + +/* As we want the PCB versions to be in ascending order chronologically, and + * because the OTP are initialized with 1s, we store the bitwise-not of the + * version number. This way, devices with blank OTP are considered version 0. */ + +PCBVersion pcbVersion() { +#if IN_FACTORY + /* When flashing for the first time, we want all systems that depend on the + * PCB version to function correctly before flashing the PCB version. This + * way, flashing the PCB version can be done last. */ + return PCB_LATEST; +#else + PCBVersion version = readPCBVersionInMemory(); + return (version == k_alternateBlankVersion ? 0 : version); +#endif +} + +PCBVersion readPCBVersionInMemory() { + return ~(*reinterpret_cast(InternalFlash::Config::OTPAddress(k_pcbVersionOTPIndex))); +} + +void writePCBVersion(PCBVersion version) { + uint8_t * destination = reinterpret_cast(InternalFlash::Config::OTPAddress(k_pcbVersionOTPIndex)); + PCBVersion formattedVersion = ~version; + InternalFlash::WriteMemory(destination, reinterpret_cast(&formattedVersion), sizeof(formattedVersion)); +} + +void lockPCBVersion() { + uint8_t * destination = reinterpret_cast(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex)); + uint8_t zero = 0; + InternalFlash::WriteMemory(destination, &zero, sizeof(zero)); +} + +bool pcbVersionIsLocked() { + return *reinterpret_cast(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex)) == 0; +} + +void jumpToInternalBootloader() { + asm volatile ("cpsie i" : : : "memory"); + + STM32::rcc_deinit(); + STM32::hal_deinit(); + STM32::systick_deinit(); + + const uint32_t p = (*((uint32_t *) 0x1FF00000)); + asm volatile ("MSR msp, %0" : : "r" (p) : ); + void (*SysMemBootJump)(void); + SysMemBootJump = (void (*)(void)) (*((uint32_t *) 0x1FF00004)); + SysMemBootJump(); +} + +} +} +} diff --git a/bootloader/drivers/stm32_drivers.cpp b/bootloader/drivers/stm32_drivers.cpp new file mode 100644 index 00000000000..ac5d097529b --- /dev/null +++ b/bootloader/drivers/stm32_drivers.cpp @@ -0,0 +1,71 @@ +#include "stm32_drivers.h" + +/** + * THIS CODE COMES FROM THE STM32_HAL LIBRARY (LICENSE ABOVE) AND HAVE BEEN MODIFIED + * WE USE ONLY THE HAL_deinit, RCC_deinit and systick_deninit FUNCTIONS AND ONLY COPIED THE CODE NEEDED. + * WE NEEDED THIS CODE TO BE ABLE TO BOOT THE STM32 BOOTLOADER + */ + +/* +This software component is provided to you as part of a software package and +applicable license terms are in the Package_license file. If you received this +software component outside of a package or without applicable license terms, +the terms of the BSD-3-Clause license shall apply. +You may obtain a copy of the BSD-3-Clause at: +https://opensource.org/licenses/BSD-3-Clause +*/ + +void STM32::rcc_deinit() { + SET_BIT(STM_32_RCC->CR, (0x1UL << (0U))); + while (READ_BIT(STM_32_RCC->CR, (0x1UL << (1U))) == 0) {} + SET_BIT(STM_32_RCC->CR, (0x10UL << (3U))); + CLEAR_REG(STM_32_RCC->CFGR); + while (READ_BIT(STM_32_RCC->CFGR, (0x3UL << (2U))) != 0) {} + CLEAR_BIT(STM_32_RCC->CR, (0x1UL << (16U)) | (0x1UL << (18U)) | (0x1UL << (19U))); + while (READ_BIT(STM_32_RCC->CR, (0x1UL << (17U))) != 0) {} + CLEAR_BIT(STM_32_RCC->CR, (0x1UL << (24U))); + while (READ_BIT(STM_32_RCC->CR, (0x1UL << (25U))) != 0) {} + CLEAR_BIT(STM_32_RCC->CR, (0x1UL << (26U))); + while (READ_BIT(STM_32_RCC->CR, (0x1UL << (27U))) != 0) {} + CLEAR_BIT(STM_32_RCC->CR, (0x1UL << (28U))); + while (READ_BIT(STM_32_RCC->CR, (0x1UL << (29U))) != 0) {} + STM_32_RCC->PLLCFGR = ((0x10UL << (0x0U)) | (0x040UL << (6U)) | (0x080UL << (6U)) | (0x4UL << (24U)) | 0x20000000U); + STM_32_RCC->PLLI2SCFGR = ((0x040UL << (6U)) | (0x080UL << (6U)) | (0x4UL << (24U)) | (0x2UL << (28U))); + STM_32_RCC->PLLSAICFGR = ((0x040UL << (6U)) | (0x080UL << (6U)) | (0x4UL << (24U)) | 0x20000000U); + CLEAR_BIT(STM_32_RCC->CIR, ((0x1UL << (8U)) | (0x1UL << (9U)) | (0x1UL << (10U)) | (0x1UL << (11U)) | (0x1UL << (12U)) | (0x1UL << (13U)) | (0x1UL << (14U)))); + SET_BIT(STM_32_RCC->CIR, ((0x1UL << (16U)) | (0x1UL << (17U)) | (0x1UL << (18U)) | (0x1UL << (19U)) | (0x1UL << (20U)) | (0x1UL << (21U)) | (0x1UL << (22U)) | (0x1UL << (23U)))); + CLEAR_BIT(STM_32_RCC->CSR, ((0x1UL << (0U)))); + SET_BIT(STM_32_RCC->CSR, ((0x1UL << (24U)))); + uint32_t sysclock = ((uint32_t)16000000U); + uint32_t a = ((sysclock / 1000U)); + uint32_t b = 15U; + STM_32_SysTick->LOAD = (uint32_t)(a - 1UL); + STM_32_SCB->SHPR[(((uint32_t)(-1))&0xFUL)-4UL] = (uint8_t)((((1UL << 4U)-1UL) << (8U - 4UL)) & (uint32_t)0xFFUL); + STM_32_SysTick->VAL = 0U; + STM_32_SysTick->CTRL = (1UL << 2U) | (1UL << 1U) | (1UL); + uint32_t c = ((uint32_t)((STM_32_SCB->AIRCR & (7UL << 8U)) >> 8U)); + uint32_t d = (c & (uint32_t)0x07UL); + uint32_t e; + uint32_t f; + e = ((7UL - d) > (uint32_t)(4UL)) ? (uint32_t)(4UL) : (7UL - d); + f = ((d + (uint32_t)(4UL)) < (uint32_t)(7UL)) ? (uint32_t)(0UL) : (uint32_t)((d - 7UL) + (uint32_t)(4UL)); + uint32_t g = (((b & (uint32_t)((1UL << (e)) - 1UL)) << f) | ((0UL & (uint32_t)((1UL << (f)) - 1UL)))); + STM_32_SCB->SHPR[(((uint32_t)(-1))&0xFUL)-4UL] = (uint8_t)((g << (8U - 4UL)) & (uint32_t)0xFFUL); +} + +void STM32::hal_deinit() { + STM_32_RCC->APB1RSTR = 0xFFFFFFFFU; + STM_32_RCC->APB1RSTR = 0x00U; + STM_32_RCC->APB2RSTR = 0xFFFFFFFFU; + STM_32_RCC->APB2RSTR = 0x00U; + STM_32_RCC->AHB1RSTR = 0xFFFFFFFFU; + STM_32_RCC->AHB1RSTR = 0x00U; + STM_32_RCC->AHB2RSTR = 0xFFFFFFFFU; + STM_32_RCC->AHB2RSTR = 0x00U; + STM_32_RCC->AHB3RSTR = 0xFFFFFFFFU; + STM_32_RCC->AHB3RSTR = 0x00U; +} + +void STM32::systick_deinit() { + STM_32_SysTick->CTRL = STM_32_SysTick->LOAD = STM_32_SysTick->VAL = 0; +} diff --git a/bootloader/drivers/stm32_drivers.h b/bootloader/drivers/stm32_drivers.h new file mode 100644 index 00000000000..9efa636dd96 --- /dev/null +++ b/bootloader/drivers/stm32_drivers.h @@ -0,0 +1,159 @@ +#include + +/* + Here we implement a very little part of the code from the default stm32 libs because we only need the unload function. + Now we include the license of the original code as required. +*/ + +/** + * THIS CODE COMES FROM THE STM32_HAL LIBRARY (LICENSE ABOVE) AND HAVE BEEN MODIFIED + * WE USE ONLY THE HAL_deinit, RCC_deinit and systick_deninit FUNCTIONS AND ONLY COPIED THE CODE NEEDED. + * WE NEEDED THIS CODE TO BE ABLE TO BOOT THE STM32 BOOTLOADER + */ + +/* +This software component is provided to you as part of a software package and +applicable license terms are in the Package_license file. If you received this +software component outside of a package or without applicable license terms, +the terms of the BSD-3-Clause license shall apply. +You may obtain a copy of the BSD-3-Clause at: +https://opensource.org/licenses/BSD-3-Clause +*/ + +namespace STM32 { + + typedef struct + { + volatile uint32_t CR; + volatile uint32_t PLLCFGR; + volatile uint32_t CFGR; + volatile uint32_t CIR; + volatile uint32_t AHB1RSTR; + volatile uint32_t AHB2RSTR; + volatile uint32_t AHB3RSTR; + uint32_t RESERVED0; + volatile uint32_t APB1RSTR; + volatile uint32_t APB2RSTR; + uint32_t RESERVED1[2]; + volatile uint32_t AHB1ENR; + volatile uint32_t AHB2ENR; + volatile uint32_t AHB3ENR; + uint32_t RESERVED2; + volatile uint32_t APB1ENR; + volatile uint32_t APB2ENR; + uint32_t RESERVED3[2]; + volatile uint32_t AHB1LPENR; + volatile uint32_t AHB2LPENR; + volatile uint32_t AHB3LPENR; + uint32_t RESERVED4; + volatile uint32_t APB1LPENR; + volatile uint32_t APB2LPENR; + uint32_t RESERVED5[2]; + volatile uint32_t BDCR; + volatile uint32_t CSR; + uint32_t RESERVED6[2]; + volatile uint32_t SSCGR; + volatile uint32_t PLLI2SCFGR; + volatile uint32_t PLLSAICFGR; + volatile uint32_t DCKCFGR1; + volatile uint32_t DCKCFGR2; + } STM32_RCC_TypeDef; + + typedef struct + { + volatile uint32_t CTRL; + volatile uint32_t LOAD; + volatile uint32_t VAL; + volatile const uint32_t CALIB; + } STM32_SysTick_Type; + + typedef struct + { + volatile uint32_t ISER[8U]; + uint32_t RESERVED0[24U]; + volatile uint32_t ICER[8U]; + uint32_t RSERVED1[24U]; + volatile uint32_t ISPR[8U]; + uint32_t RESERVED2[24U]; + volatile uint32_t ICPR[8U]; + uint32_t RESERVED3[24U]; + volatile uint32_t IABR[8U]; + uint32_t RESERVED4[56U]; + volatile uint8_t IP[240U]; + uint32_t RESERVED5[644U]; + volatile uint32_t STIR; + } STM32_NVIC_Type; + + typedef struct { + volatile const uint32_t CPUID; + volatile uint32_t ICSR; + volatile uint32_t VTOR; + volatile uint32_t AIRCR; + volatile uint32_t SCR; + volatile uint32_t CCR; + volatile uint8_t SHPR[12U]; + volatile uint32_t SHCSR; + volatile uint32_t CFSR; + volatile uint32_t HFSR; + volatile uint32_t DFSR; + volatile uint32_t MMFAR; + volatile uint32_t BFAR; + volatile uint32_t AFSR; + volatile const uint32_t ID_PFR[2U]; + volatile const uint32_t ID_DFR; + volatile const uint32_t ID_AFR; + volatile const uint32_t ID_MFR[4U]; + volatile const uint32_t ID_ISAR[5U]; + uint32_t RESERVED0[1U]; + volatile const uint32_t CLIDR; + volatile const uint32_t CTR; + volatile const uint32_t CCSIDR; + volatile uint32_t CSSELR; + volatile uint32_t CPACR; + uint32_t RESERVED3[93U]; + volatile uint32_t STIR; + uint32_t RESERVED4[15U]; + volatile const uint32_t MVFR0; + volatile const uint32_t MVFR1; + volatile const uint32_t MVFR2; + uint32_t RESERVED5[1U]; + volatile uint32_t ICIALLU; + uint32_t RESERVED6[1U]; + volatile uint32_t ICIMVAU; + volatile uint32_t DCIMVAC; + volatile uint32_t DCISW; + volatile uint32_t DCCMVAU; + volatile uint32_t DCCMVAC; + volatile uint32_t DCCSW; + volatile uint32_t DCCIMVAC; + volatile uint32_t DCCISW; + uint32_t RESERVED7[6U]; + volatile uint32_t ITCMCR; + volatile uint32_t DTCMCR; + volatile uint32_t AHBPCR; + volatile uint32_t CACR; + volatile uint32_t AHBSCR; + uint32_t RESERVED8[1U]; + volatile uint32_t ABFSR; + } STM32_SCB_Type; + + #define RCC_BASE 0x40023800UL + #define SysTick_BASE 0xE000E010UL + #define NVIC_BASE 0xE000E100UL + #define SCB_BASE 0xE000ED00UL + + #define SET_BIT(REG, BIT) ((REG) |= (BIT)) + #define CLEAR_BIT(REG, BIT) ((REG) &= ~(BIT)) + #define READ_BIT(REG, BIT) ((REG) & (BIT)) + #define CLEAR_REG(REG) ((REG) = (0x0)) + #define READ_REG(REG) ((REG)) + + #define STM_32_RCC ((STM32::STM32_RCC_TypeDef *) RCC_BASE) + #define STM_32_SysTick ((STM32::STM32_SysTick_Type *) SysTick_BASE) + #define STM_32_NVIC ((STM32::STM32_NVIC_Type *) NVIC_BASE) + #define STM_32_SCB ((STM32_SCB_Type *) SCB_BASE) + + extern void rcc_deinit(); + extern void hal_deinit(); + extern void systick_deinit(); +} diff --git a/bootloader/interface.cpp b/bootloader/interface.cpp deleted file mode 100644 index 672f86b7e6f..00000000000 --- a/bootloader/interface.cpp +++ /dev/null @@ -1,83 +0,0 @@ - -#include -#include - -#include -#include -#include - -#include "computer.h" -#include "cable.h" - -namespace Bootloader { - -void Interface::drawImage(KDContext* ctx, const Image* image, int offset) { - const uint8_t* data; - size_t size; - size_t pixelBufferSize; - - if (image != nullptr) { - data = image->compressedPixelData(); - size = image->compressedPixelDataSize(); - pixelBufferSize = image->width() * image->height(); - } else { - return; - } - - KDColor pixelBuffer[4000]; - assert(pixelBufferSize <= 4000); - assert(Ion::stackSafe()); // That's a VERY big buffer we're allocating on the stack - - Ion::decompress( - data, - reinterpret_cast(pixelBuffer), - size, - pixelBufferSize * sizeof(KDColor) - ); - - KDRect bounds((320 - image->width()) / 2, offset, image->width(), image->height()); - - ctx->fillRectWithPixels(bounds, pixelBuffer, nullptr); -} - -void Interface::draw() { - KDContext * ctx = KDIonContext::sharedContext(); - ctx->fillRect(KDRect(0,0,320,240), KDColorBlack); - drawImage(ctx, ImageStore::Computer, 70); - drawImage(ctx, ImageStore::Cable, 172); - - ctx->drawString("Slot A:", KDPoint(0, 0), KDFont::SmallFont, KDColorWhite, KDColorBlack); - ctx->drawString("Slot B:", KDPoint(0, 13), KDFont::SmallFont, KDColorWhite, KDColorBlack); - ctx->drawString("Current:", KDPoint(0, 26), KDFont::SmallFont, KDColorWhite, KDColorBlack); - - if (Boot::mode() == BootMode::SlotA) { - ctx->drawString("Slot A", KDPoint(63, 26), KDFont::SmallFont, KDColorWhite, KDColorBlack); - } else if (Boot::mode() == BootMode::SlotB) { - ctx->drawString("Slot B", KDPoint(63, 26), KDFont::SmallFont, KDColorWhite, KDColorBlack); - } - - Slot slots[2] = {Slot::A(), Slot::B()}; - - for(uint8_t i = 0; i < 2; i++) { - Slot slot = slots[i]; - - if (slot.kernelHeader()->isValid() && slot.userlandHeader()->isValid()) { - if (slot.userlandHeader()->isOmega() && slot.userlandHeader()->isUpsilon()) { - ctx->drawString("Upsilon", KDPoint(56, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); - ctx->drawString(slot.userlandHeader()->upsilonVersion(), KDPoint(112, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); - } else if (slot.userlandHeader()->isOmega()) { - ctx->drawString("Omega", KDPoint(56, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); - ctx->drawString(slot.userlandHeader()->omegaVersion(), KDPoint(112, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); - } else { - ctx->drawString("Epsilon", KDPoint(56, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); - ctx->drawString(slot.userlandHeader()->version(), KDPoint(112, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); - } - ctx->drawString(slot.kernelHeader()->patchLevel(), KDPoint(168, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); - } else { - ctx->drawString("Invalid", KDPoint(56, i*13), KDFont::SmallFont, KDColorWhite, KDColorBlack); - } - } - -} - -} diff --git a/bootloader/interface.h b/bootloader/interface.h deleted file mode 100644 index 0a98c2b57b6..00000000000 --- a/bootloader/interface.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef BOOTLOADER_INTERFACE -#define BOOTLOADER_INTERFACE - -#include -#include -#include - -namespace Bootloader { - -class Interface { - -private: - static void drawImage(KDContext* ctx, const Image* image, int offset); - -public: - static void draw(); - -}; - -} - -#endif \ No newline at end of file diff --git a/bootloader/interface/menus/about.cpp b/bootloader/interface/menus/about.cpp new file mode 100644 index 00000000000..aadd26332d9 --- /dev/null +++ b/bootloader/interface/menus/about.cpp @@ -0,0 +1,20 @@ +#include "about.h" +#include + +Bootloader::AboutMenu::AboutMenu() : Menu(KDColorBlack, KDColorWhite, Messages::aboutMenuTitle, Messages::bootloaderVersion) { + setup(); +} + +void Bootloader::AboutMenu::setup() { + m_defaultColumns[0] = Column(Messages::aboutMessage1, k_small_font, 0, true); + m_defaultColumns[1] = Column(Messages::aboutMessage2, k_small_font, 0, true); + m_defaultColumns[2] = Column(Messages::aboutMessage3, k_small_font, 0, true); + m_defaultColumns[3] = Column(Messages::aboutMessage4, k_small_font, 0, true); + m_defaultColumns[4] = Column(Messages::aboutMessage5, k_small_font, 0, true); + + m_columns[0] = ColumnBinder(&m_defaultColumns[0]); + m_columns[1] = ColumnBinder(&m_defaultColumns[1]); + m_columns[2] = ColumnBinder(&m_defaultColumns[2]); + m_columns[3] = ColumnBinder(&m_defaultColumns[3]); + m_columns[4] = ColumnBinder(&m_defaultColumns[4]); +} diff --git a/bootloader/interface/menus/about.h b/bootloader/interface/menus/about.h new file mode 100644 index 00000000000..00f48270540 --- /dev/null +++ b/bootloader/interface/menus/about.h @@ -0,0 +1,17 @@ +#ifndef _BOOTLOADER_INTERFACE_ABOUT_H_ +#define _BOOTLOADER_INTERFACE_ABOUT_H_ + +#include + +namespace Bootloader { + class AboutMenu : public Menu { + public: + AboutMenu(); + + void setup() override; + void postOpen() override {}; + }; +} + + +#endif diff --git a/bootloader/interface/menus/crash.cpp b/bootloader/interface/menus/crash.cpp new file mode 100644 index 00000000000..4d09492ad28 --- /dev/null +++ b/bootloader/interface/menus/crash.cpp @@ -0,0 +1,22 @@ +#include "crash.h" + +Bootloader::CrashMenu::CrashMenu(const char * err) : Menu(KDColorBlack, KDColorWhite, Bootloader::Messages::bootloaderCrashTitle, Bootloader::Messages::mainTitle), m_error(err) { + setup(); +} + +void Bootloader::CrashMenu::setup() { + m_defaultColumns[0] = Column(m_error, k_large_font, 0, true); + m_defaultColumns[1] = Column(Bootloader::Messages::bootloaderCrashMessage1, k_small_font, 0, true); + m_defaultColumns[2] = Column(Bootloader::Messages::bootloaderCrashMessage2, k_small_font, 0, true); + + m_columns[0] = ColumnBinder(&m_defaultColumns[0]); + m_columns[1] = ColumnBinder(&m_defaultColumns[1]); + m_columns[2] = ColumnBinder(&m_defaultColumns[2]); +} + +void Bootloader::CrashMenu::postOpen() { + // We override the open method + for (;;) { + // Infinite loop + } +} diff --git a/bootloader/interface/menus/crash.h b/bootloader/interface/menus/crash.h new file mode 100644 index 00000000000..bcb74ec81e7 --- /dev/null +++ b/bootloader/interface/menus/crash.h @@ -0,0 +1,19 @@ +#ifndef _BOOTLOADER_INTERFACE_MENUS_CRASH_H_ +#define _BOOTLOADER_INTERFACE_MENUS_CRASH_H_ + +#include + +namespace Bootloader { + class CrashMenu : public Menu { + public: + CrashMenu(const char * error); + + void setup() override; + void postOpen() override; + + private: + const char * m_error; + }; +} + +#endif diff --git a/bootloader/interface/menus/dfu.cpp b/bootloader/interface/menus/dfu.cpp new file mode 100644 index 00000000000..72eb9c893cb --- /dev/null +++ b/bootloader/interface/menus/dfu.cpp @@ -0,0 +1,37 @@ +#include "dfu.h" +#include +#include + +Bootloader::DfuMenu::DfuMenu(const char * text, const USBData * data) : Menu(KDColorBlack, KDColorWhite, Messages::dfuTitle, Messages::mainTitle), m_submenuText(text), m_data(data) { + setup(); +} + +void Bootloader::DfuMenu::setup() { + m_defaultColumns[0] = Column(m_submenuText, k_small_font, 0, true); + + m_columns[0] = ColumnBinder(&m_defaultColumns[0]); +} + +void Bootloader::DfuMenu::postOpen() { + // We override the open method + if (!m_data->getData().isProtectedInternal() && m_data->getData().isProtectedExternal()) { + // Because we want to flash the internal, we will jump into the stm32 bootloader + Bootloader::Boot::jumpToInternalBootloader(); + return; // We never reach this point + } + for (;;) { + Ion::USB::enable(); + do { + uint64_t scan = Ion::Keyboard::scan(); + if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { + Ion::USB::disable(); + forceExit(); + return; + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { + Ion::Power::standby(); + return; + } + } while (!Ion::USB::isEnumerated()); + Ion::USB::DFU(true, (void *)m_data); + } +} diff --git a/bootloader/interface/menus/dfu.h b/bootloader/interface/menus/dfu.h new file mode 100644 index 00000000000..17a4873b2d3 --- /dev/null +++ b/bootloader/interface/menus/dfu.h @@ -0,0 +1,21 @@ +#ifndef _BOOTLOADER_INTERFACE_MENUS_DFU_H_ +#define _BOOTLOADER_INTERFACE_MENUS_DFU_H_ + +#include +#include + +namespace Bootloader { + class DfuMenu : public Menu { + public: + DfuMenu(const char * submenu, const USBData * usbData); + + void setup() override; + void postOpen() override; + + private: + const char * m_submenuText; + const USBData * m_data; + }; +} + +#endif diff --git a/bootloader/interface/menus/home.cpp b/bootloader/interface/menus/home.cpp new file mode 100644 index 00000000000..aa559a308bd --- /dev/null +++ b/bootloader/interface/menus/home.cpp @@ -0,0 +1,107 @@ +#include "home.h" +#include +#include +#include +#include + +Bootloader::AboutMenu * Bootloader::HomeMenu::aboutMenu() { + static AboutMenu * aboutMenu = new AboutMenu(); + return aboutMenu; +} + +Bootloader::InstallerMenu * Bootloader::HomeMenu::installerMenu() { + static InstallerMenu * installerMenu = new InstallerMenu(); + return installerMenu; +} + +Bootloader::HomeMenu::HomeMenu() : Menu(KDColorBlack, KDColorWhite, Messages::homeTitle, Messages::mainTitle) { + setup(); +} + +bool slotA_submenu() { + if (Bootloader::Slot::isFullyValid(Bootloader::Slot::A())) { + Bootloader::Boot::bootSlot(Bootloader::Slot::A()); + return true; + } + return false; +} + +bool slotKhi_submenu() { + if (Bootloader::Slot::isFullyValid(Bootloader::Slot::Khi())) { + Bootloader::Boot::bootSlot(Bootloader::Slot::Khi()); + return true; + } + return false; +} + +bool slotB_submenu() { + if (Bootloader::Slot::isFullyValid(Bootloader::Slot::B())) { + Bootloader::Boot::bootSlot(Bootloader::Slot::B()); + return true; + } + return false; +} + +bool installer_submenu() { + Bootloader::HomeMenu::installerMenu()->open(); + return true; +} + +bool about_submenu() { + Bootloader::HomeMenu::aboutMenu()->open(); + return true; +} + +const char * Bootloader::HomeMenu::getSlotOsText(Slot slot) { + if (Slot::isFullyValid(slot)) { + if (slot.userlandHeader()->isOmega() && slot.userlandHeader()->isUpsilon()) { + return Messages::upsilonSlot; + } else if (slot.userlandHeader()->isOmega() && slot.kernelHeader()->patchLevel()[0] != '\0') { + return Messages::omegaSlot; + } else if (slot.userlandHeader()->isOmega()) { + return Messages::khiSlot; + } else { + return Messages::epsilonSlot; + } + } + return nullptr; +} + +const char * Bootloader::HomeMenu::getSlotText(Slot slot) { + if(Slot::isFullyValid(slot)) { + if (slot.address() == Slot::A().address()) { + return Messages::homeSlotASubmenu; + } else if (slot.address() == Slot::Khi().address()) { + return Messages::homeSlotKhiSubmenu; + } else if (slot.address() == Slot::B().address()) { + return Messages::homeSlotBSubmenu; + } + } + return Messages::invalidSlot; +} + +const char * Bootloader::HomeMenu::getKernelText(Slot slot) { + return Slot::isFullyValid(slot) ? slot.kernelHeader()->patchLevel() : nullptr; +} + +const char * Bootloader::HomeMenu::getVersionText(Slot slot) { + return Slot::isFullyValid(slot) ? slot.userlandHeader()->isOmega() && slot.userlandHeader()->isUpsilon() ? slot.userlandHeader()->upsilonVersion() : slot.userlandHeader()->isOmega() ? slot.userlandHeader()->omegaVersion() : slot.kernelHeader()->version() : nullptr; +} + +void Bootloader::HomeMenu::setup() { + Slot A = Slot::A(); + Slot Khi = Slot::Khi(); + Slot B = Slot::B(); + + m_slotColumns[0] = SlotColumn(getSlotText(A), getKernelText(A), getSlotOsText(A), getVersionText(A), Ion::Keyboard::Key::One, k_small_font, 10, false, &slotA_submenu); + m_slotColumns[1] = SlotColumn(getSlotText(Khi), getKernelText(Khi), getSlotOsText(Khi), getVersionText(Khi), Ion::Keyboard::Key::Two, k_small_font, 10, false, &slotKhi_submenu); + m_slotColumns[2] = SlotColumn(getSlotText(B), getKernelText(B), getSlotOsText(B), getVersionText(B), Ion::Keyboard::Key::Three, k_small_font, 10, false, &slotB_submenu); + m_defaultColumns[0] = Column(Messages::homeInstallerSubmenu, Ion::Keyboard::Key::Four, k_small_font, 10, false, &installer_submenu); + m_defaultColumns[1] = Column(Messages::homeAboutSubmenu, Ion::Keyboard::Key::Five, k_small_font, 10, false, &about_submenu); + + m_columns[0] = ColumnBinder(&m_slotColumns[0]); + m_columns[1] = ColumnBinder(&m_slotColumns[1]); + m_columns[2] = ColumnBinder(&m_slotColumns[2]); + m_columns[3] = ColumnBinder(&m_defaultColumns[0]); + m_columns[4] = ColumnBinder(&m_defaultColumns[1]); +} diff --git a/bootloader/interface/menus/home.h b/bootloader/interface/menus/home.h new file mode 100644 index 00000000000..afe3520e351 --- /dev/null +++ b/bootloader/interface/menus/home.h @@ -0,0 +1,28 @@ +#ifndef _BOOTLOADER_INTERFACE_MENUS_HOME_H_ +#define _BOOTLOADER_INTERFACE_MENUS_HOME_H_ + +#include +#include +#include +#include + +namespace Bootloader { + class HomeMenu : public Menu { + public: + HomeMenu(); + + void setup() override; + void postOpen() override {}; + + static AboutMenu * aboutMenu(); + static InstallerMenu * installerMenu(); + + private: + const char * getSlotOsText(Slot slot); + const char * getSlotText(Slot slot); + const char * getKernelText(Slot slot); + const char * getVersionText(Slot slot); + }; +} + +#endif diff --git a/bootloader/interface/menus/installer.cpp b/bootloader/interface/menus/installer.cpp new file mode 100644 index 00000000000..5d1b1a17cf7 --- /dev/null +++ b/bootloader/interface/menus/installer.cpp @@ -0,0 +1,41 @@ +#include "installer.h" +#include +#include + +#include + +Bootloader::DfuMenu * Bootloader::InstallerMenu::SlotsDFU() { + USBData data = USBData::DEFAULT(); + static DfuMenu * slotsDfu = new DfuMenu(Messages::dfuSlotsUpdate, &data); + return slotsDfu; +} + +Bootloader::DfuMenu * Bootloader::InstallerMenu::BootloaderDFU() { + USBData data = USBData::BOOTLOADER_UPDATE(); + static DfuMenu * bootloaderDfu = new DfuMenu(Messages::dfuBootloaderUpdate, &data); + return bootloaderDfu; +} + +Bootloader::InstallerMenu::InstallerMenu() : Menu(KDColorBlack, KDColorWhite, Messages::installerTitle, Messages::mainTitle) { + setup(); +} + +bool slotsSubmenu() { + Bootloader::InstallerMenu::SlotsDFU()->open(); + return true; +} + +bool bootloaderSubmenu() { + Bootloader::InstallerMenu::BootloaderDFU()->open(); + return true; +} + +void Bootloader::InstallerMenu::setup() { + m_defaultColumns[0] = Column(Messages::installerText1, k_large_font, 0, true); + m_defaultColumns[1] = Column(Messages::installerSlotsSubmenu, Ion::Keyboard::Key::One, k_small_font, 30, false, &slotsSubmenu); + m_defaultColumns[2] = Column(Messages::installerBootloaderSubmenu, Ion::Keyboard::Key::Two, k_small_font, 30, false, &bootloaderSubmenu); + + m_columns[0] = ColumnBinder(&m_defaultColumns[0]); + m_columns[1] = ColumnBinder(&m_defaultColumns[1]); + m_columns[2] = ColumnBinder(&m_defaultColumns[2]); +} diff --git a/bootloader/interface/menus/installer.h b/bootloader/interface/menus/installer.h new file mode 100644 index 00000000000..dd98e4a98b0 --- /dev/null +++ b/bootloader/interface/menus/installer.h @@ -0,0 +1,20 @@ +#ifndef _BOOTLOADER_INTERFACE_MENUS_INSTALLER_H_ +#define _BOOTLOADER_INTERFACE_MENUS_INSTALLER_H_ + +#include +#include + +namespace Bootloader { + class InstallerMenu : public Menu { + public: + InstallerMenu(); + + void setup() override; + void postOpen() override {}; + + static DfuMenu * SlotsDFU(); + static DfuMenu * BootloaderDFU(); + }; +} + +#endif diff --git a/bootloader/interface/menus/slot_recovery.cpp b/bootloader/interface/menus/slot_recovery.cpp new file mode 100644 index 00000000000..ef4b8ae3158 --- /dev/null +++ b/bootloader/interface/menus/slot_recovery.cpp @@ -0,0 +1,39 @@ +#include "slot_recovery.h" +#include + +Bootloader::SlotRecoveryMenu::SlotRecoveryMenu(USBData * usb) : Menu(KDColorBlack, KDColorWhite, Messages::recoveryTitle, Messages::mainTitle), m_data(usb) { + setup(); +} + +void Bootloader::SlotRecoveryMenu::setup() { + m_defaultColumns[0] = Column(Messages::recoveryMessage1, k_small_font, 0, true); + m_defaultColumns[1] = Column(Messages::recoveryMessage2, k_small_font, 0, true); + m_defaultColumns[2] = Column(Messages::recoveryMessage3, k_small_font, 0, true); + m_defaultColumns[3] = Column(Messages::recoveryMessage4, k_small_font, 0, true); + m_defaultColumns[4] = Column(Messages::recoveryMessage5, k_small_font, 0, true); + + m_columns[0] = ColumnBinder(&m_defaultColumns[0]); + m_columns[1] = ColumnBinder(&m_defaultColumns[1]); + m_columns[2] = ColumnBinder(&m_defaultColumns[2]); + m_columns[3] = ColumnBinder(&m_defaultColumns[3]); + m_columns[4] = ColumnBinder(&m_defaultColumns[4]); +} + +void Bootloader::SlotRecoveryMenu::postOpen() { + // We override the open method + for (;;) { + Ion::USB::enable(); + do { + uint64_t scan = Ion::Keyboard::scan(); + if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Back)) { + Ion::USB::disable(); + forceExit(); + return; + } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { + Ion::Power::standby(); + return; + } + } while (!Ion::USB::isEnumerated()); + Ion::USB::DFU(true, (void *)m_data); + } +} diff --git a/bootloader/interface/menus/slot_recovery.h b/bootloader/interface/menus/slot_recovery.h new file mode 100644 index 00000000000..4eba9284463 --- /dev/null +++ b/bootloader/interface/menus/slot_recovery.h @@ -0,0 +1,19 @@ +#ifndef _BOOTLOADER_INTERFACE_MENU_SLOT_RECOVERY_H +#define _BOOTLOADER_INTERFACE_MENU_SLOT_RECOVERY_H + +#include +#include + +namespace Bootloader { + class SlotRecoveryMenu : public Menu { + public: + SlotRecoveryMenu(USBData * usbData); + + void setup() override; + void postOpen() override; + private: + const USBData * m_data; + }; +} + +#endif diff --git a/bootloader/interface/menus/warning.cpp b/bootloader/interface/menus/warning.cpp new file mode 100644 index 00000000000..8cea95d5e48 --- /dev/null +++ b/bootloader/interface/menus/warning.cpp @@ -0,0 +1,35 @@ +#include "warning.h" +#include +#include + +Bootloader::WarningMenu::WarningMenu() : Menu(KDColorWhite, KDColorRed, Messages::epsilonWarningTitle, Messages::mainTitle, false, 3) { + setup(); +} + +bool proceed() { + Bootloader::Boot::bootSelectedSlot(); + return true; +} + +bool backoff() { + if (Bootloader::Boot::config()->slot() != nullptr) { + Bootloader::Boot::config()->clearSlot(); + } + return true; +} + +void Bootloader::WarningMenu::setup() { + m_defaultColumns[0] = Column(Messages::epsilonWarningMessage1, k_small_font, 0, true); + m_defaultColumns[1] = Column(Messages::epsilonWarningMessage2, k_small_font, 0, true); + m_defaultColumns[2] = Column(Messages::epsilonWarningMessage3, k_small_font, 0, true); + m_defaultColumns[3] = Column(Messages::epsilonWarningMessage4, k_small_font, 0, true); + m_defaultColumns[4] = Column(Messages::epsilonWarningMessage5, Ion::Keyboard::Key::EXE, k_small_font, 0, true, &proceed); + m_defaultColumns[5] = Column(Messages::epsilonWarningMessage6, Ion::Keyboard::Key::Back, k_small_font, 0, true, &backoff); + + m_columns[0] = ColumnBinder(&m_defaultColumns[0]); + m_columns[1] = ColumnBinder(&m_defaultColumns[1]); + m_columns[2] = ColumnBinder(&m_defaultColumns[2]); + m_columns[3] = ColumnBinder(&m_defaultColumns[3]); + m_columns[4] = ColumnBinder(&m_defaultColumns[4]); + m_columns[5] = ColumnBinder(&m_defaultColumns[5]); +} diff --git a/bootloader/interface/menus/warning.h b/bootloader/interface/menus/warning.h new file mode 100644 index 00000000000..f2763f203c5 --- /dev/null +++ b/bootloader/interface/menus/warning.h @@ -0,0 +1,17 @@ +#ifndef _BOOTLOADER_INTERFACE_MENUS_WARNING_H_ +#define _BOOTLOADER_INTERFACE_MENUS_WARNING_H_ + +#include +#include + +namespace Bootloader { + class WarningMenu : public Menu { + public: + WarningMenu(); + + void setup() override; + void postOpen() override {}; + }; +} + +#endif diff --git a/bootloader/interface/src/menu.cpp b/bootloader/interface/src/menu.cpp new file mode 100644 index 00000000000..c371bf01aa0 --- /dev/null +++ b/bootloader/interface/src/menu.cpp @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include + +const Ion::Keyboard::Key Bootloader::Menu::k_breaking_keys[]; + +void Bootloader::Menu::setup() { + // Here we add the columns to the menu. +} + +void Bootloader::Menu::open(bool noreturn) { + showMenu(); + + uint64_t scan = 0; + bool exit = false; + + postOpen(); + + while(!exit && !m_forced_exit) { + scan = Ion::Keyboard::scan(); + exit = !handleKey(scan); + if (noreturn) { + exit = false; + } + } +} + +int Bootloader::Menu::calculateCenterX(const char * text, int fontWidth) { + return (getScreen().width() - fontWidth * strlen(text)) / 2; +} + +void Bootloader::Menu::showMenu() { + KDContext * ctx = KDIonContext::sharedContext(); + ctx->fillRect(getScreen(), m_background); + Interface::drawComputer(ctx, 25); + int y = Interface::computerHeight() + 25 + 10; + int x = calculateCenterX(m_title, largeFontWidth()); + ctx->drawString(m_title, KDPoint(x, y), k_large_font, m_foreground, m_background); + y += largeFontHeight() + 10; + + //TODO: center the columns if m_centerY is true + + for (ColumnBinder column : m_columns) { + if (column.isNull()) { + continue; + } + if (column.type() == ColumnType::SLOT) { + y += ((SlotColumn *)column.getColumn())->draw(ctx, y, m_background, m_foreground) + m_margin; + } else if (column.type() == ColumnType::DEFAULT) { + y += ((Column *)column.getColumn())->draw(ctx, y, m_background, m_foreground) + m_margin; + } + } + + if (m_bottom != nullptr) { + y = getScreen().height() - smallFontHeight() - 10; + x = calculateCenterX(m_bottom, smallFontWidth()); + ctx->drawString(m_bottom, KDPoint(x, y), k_small_font, m_foreground, m_background); + } +} + +bool Bootloader::Menu::handleKey(uint64_t key) { + for (Ion::Keyboard::Key breaking : this->k_breaking_keys) { + if (Ion::Keyboard::State(breaking) == key) { + return false; + } + } + if (key == Ion::Keyboard::State(Ion::Keyboard::Key::OnOff)) { + Ion::Power::standby(); + return false; + } + for (ColumnBinder column : m_columns) { + if (column.isNull()) { + continue; + } else { + if (column.type() == ColumnType::SLOT) { + if (((SlotColumn *)column.getColumn())->didHandledEvent(key)) { + redraw(); + } + } else if (column.type() == ColumnType::DEFAULT) { + if (((Column *)column.getColumn())->didHandledEvent(key)) { + redraw(); + } + } + } + } + return true; +} + +bool Bootloader::Menu::Column::didHandledEvent(uint64_t key) { + if (isMyKey(key) && isClickable()) { + return m_callback(); + } + return false; +} + +int Bootloader::Menu::Column::draw(KDContext * ctx, int y, KDColor background, KDColor foreground) { + int x = m_extraX; + if (m_center) { + x += Bootloader::Menu::calculateCenterX(m_text, m_font->glyphSize().width()); + } + ctx->drawString(m_text, KDPoint(x, y), m_font, foreground, background); + return m_font->glyphSize().height(); +} + +int Bootloader::Menu::SlotColumn::draw(KDContext * ctx, int y, KDColor background, KDColor foreground) { + int x = m_extraX; + + int width = strlen(m_text); + if (m_kernalPatch != nullptr) { + width += strlen(m_kernalPatch) + m_font->glyphSize().width(); + } + if (m_osType != nullptr) { + width += strlen(m_osType) + m_font->glyphSize().width(); + } + if (m_center) { + x += Bootloader::Menu::getScreen().width() - width * m_font->glyphSize().width(); + } + ctx->drawString(m_text, KDPoint(x, y), m_font, foreground, background); + x += strlen(m_text) * m_font->glyphSize().width() + m_font->glyphSize().width(); + if (m_kernalPatch != nullptr) { + ctx->drawString(m_kernalPatch, KDPoint(x, y), m_font, foreground, background); + } + x += strlen(m_kernalPatch) * m_font->glyphSize().width() + m_font->glyphSize().width(); + if (m_osType != nullptr) { + ctx->drawString(m_osType, KDPoint(x, y), m_font, foreground, background); + } + x += strlen(m_osType) * m_font->glyphSize().width() + m_font->glyphSize().width(); + if (m_kernelVersion != nullptr) { + ctx->drawString(m_kernelVersion, KDPoint(x, y), m_font, foreground, background); + } + return m_font->glyphSize().height(); +} diff --git a/bootloader/interface/src/menu.h b/bootloader/interface/src/menu.h new file mode 100644 index 00000000000..0e88ba7c6e0 --- /dev/null +++ b/bootloader/interface/src/menu.h @@ -0,0 +1,126 @@ +#ifndef _BOOTLOADER_MENU_H_ +#define _BOOTLOADER_MENU_H_ + +#include +#include +#include + +namespace Bootloader { + class Menu { + public: + Menu() : Menu(KDColorBlack, KDColorWhite, Messages::mainTitle) { }; + Menu(KDColor foreground, KDColor background, const char * title) : Menu(foreground, background, title, nullptr) {}; + Menu(KDColor foreground, KDColor background, const char * title, const char * bottom) : Menu(foreground, background, title, bottom, false) {}; + Menu(KDColor foreground, KDColor background, const char * title, const char * bottom, bool centerY) : Menu(foreground, background, title, bottom, centerY, k_columns_margin) {}; + Menu(KDColor foreground, KDColor background, const char * title, const char * bottom, bool centerY, int margin) : m_columns(), m_defaultColumns(), m_slotColumns(), m_background(background), m_title(title), m_foreground(foreground), m_bottom(bottom), m_centerY(centerY), m_forced_exit(false), m_margin(margin) { + setup(); + } + static const int k_columns_margin = 5; + + virtual void setup() = 0; + virtual void postOpen() = 0; + + enum ColumnType { + DEFAULT, + SLOT + }; + + class Column { + public: + Column() : m_text(nullptr), m_key(Ion::Keyboard::Key::None), m_font(KDFont::SmallFont), m_extraX(0), m_center(false), m_callback(nullptr), m_clickable(false) {}; + + Column(const char * t, Ion::Keyboard::Key k, const KDFont * font, int extraX, bool center, bool(*pointer)()) : m_text(t), m_key(k), m_font(font), m_extraX(extraX), m_center(center), m_callback(pointer), m_clickable(true) {}; + Column(const char * t, const KDFont * font, int extraX, bool center) : m_text(t), m_key(Ion::Keyboard::Key::None), m_font(font), m_extraX(extraX), m_center(center), m_callback(nullptr), m_clickable(false) {}; + + bool isNull() const { return m_text == nullptr; }; + bool isClickable() const { return m_clickable; }; + bool didHandledEvent(uint64_t key); + virtual int draw(KDContext * ctx, int y, KDColor background, KDColor foreground); + virtual int columnType() { return ColumnType::DEFAULT; }; + + private: + bool isMyKey(uint64_t key) const { return Ion::Keyboard::State(m_key) == key; }; + protected: + const char * m_text; + Ion::Keyboard::Key m_key; + const KDFont * m_font; + int m_extraX; + bool m_center; + bool (*m_callback)(); + bool m_clickable; + }; + + class SlotColumn : public Column { + public: + SlotColumn() : Column(), m_kernalPatch(nullptr), m_osType(nullptr), m_kernelVersion(nullptr) {}; + + SlotColumn(const char * t, Ion::Keyboard::Key k, const KDFont * font, int extraX, bool center, bool(*pointer)()) : Column(t, k, font, extraX, center, pointer), m_kernalPatch(nullptr), m_osType(nullptr), m_kernelVersion(nullptr) {}; + SlotColumn(const char * t, const char * k, const char * o, const char * kernelV, Ion::Keyboard::Key key, const KDFont * font, int extraX, bool center, bool(*pointer)()) : Column(t, key, font, extraX, center, pointer), m_kernalPatch(k), m_osType(o), m_kernelVersion(kernelV) {}; + + int draw(KDContext * ctx, int y, KDColor background, KDColor foreground) override; + virtual int columnType() { return ColumnType::SLOT; }; + + private: + const char * m_kernalPatch; + const char * m_osType; + const char * m_kernelVersion; + }; + + class ColumnBinder { + public: + ColumnBinder() : m_pointer(nullptr), m_type(ColumnType::DEFAULT) {}; + ColumnBinder(Column * pointer) : m_pointer(pointer), m_type(ColumnType::DEFAULT) {}; + ColumnBinder(SlotColumn * pointer) : m_pointer(pointer), m_type(ColumnType::SLOT) {}; + + bool isNull() const { return m_pointer == nullptr; }; + void * getColumn() const { return m_pointer; }; + ColumnType type() const { return m_type; }; + private: + void * m_pointer; + ColumnType m_type; + }; + + void open(bool noreturn = false); + void redraw() { showMenu(); }; + + static int calculateCenterX(const char * text, int fontWidth); + + static constexpr const KDFont * k_small_font = KDFont::SmallFont; + static constexpr const KDFont * k_large_font = KDFont::LargeFont; + + static const KDRect getScreen() { return KDRect(0, 0, 320, 240); }; + + protected: + void forceExit() { m_forced_exit = true; }; + + private: + static const int k_max_columns = 6; + + static constexpr Ion::Keyboard::Key k_breaking_keys[] = {Ion::Keyboard::Key::Back, Ion::Keyboard::Key::Home}; + + int smallFontHeight() const { return k_small_font->glyphSize().height(); }; + int largeFontHeight() const { return k_large_font->glyphSize().height(); }; + + int smallFontWidth() const { return k_small_font->glyphSize().width(); }; + int largeFontWidth() const { return k_large_font->glyphSize().width(); }; + + bool handleKey(uint64_t key); + void showMenu(); + + protected: + ColumnBinder m_columns[k_max_columns]; + // Columns Storage + Column m_defaultColumns[k_max_columns]; + SlotColumn m_slotColumns[k_max_columns]; + KDColor m_background; + KDColor m_foreground; + const char * m_title; + const char * m_bottom; + bool m_centerY; + int m_margin; + private: + bool m_forced_exit; + }; +} + +#endif // _BOOTLOADER_MENU_H_ diff --git a/bootloader/interface/static/interface.cpp b/bootloader/interface/static/interface.cpp new file mode 100644 index 00000000000..c0e95db7316 --- /dev/null +++ b/bootloader/interface/static/interface.cpp @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include + +#include + +namespace Bootloader { + +void Interface::drawComputer(KDContext * ctx, int offset) { + const Image * image = ImageStore::Computer; + + const uint8_t * data; + size_t size; + size_t pixelBufferSize; + + if (image != nullptr) { + data = image->compressedPixelData(); + size = image->compressedPixelDataSize(); + pixelBufferSize = image->width() * image->height(); + } else { + return; + } + + KDColor pixelBuffer[4000]; + assert(pixelBufferSize <= 4000); + assert(Ion::stackSafe()); // That's a VERY big buffer we're allocating on the stack + + Ion::decompress( + data, + reinterpret_cast(pixelBuffer), + size, + pixelBufferSize * sizeof(KDColor) + ); + + KDRect bounds((320 - image->width()) / 2, offset, image->width(), image->height()); + + ctx->fillRectWithPixels(bounds, pixelBuffer, nullptr); +} + +KDCoordinate Interface::computerHeight() { + return ImageStore::Computer->height(); +} + +void Interface::drawFlasher() { + KDContext * ctx = KDIonContext::sharedContext(); + + ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); + drawComputer(ctx, 25); + + KDSize fontSize = KDFont::LargeFont->glyphSize(); + int initPos = (320 - fontSize.width() * strlen(Messages::mainTitle)) / 2; + ctx->drawString(Messages::mainTitle, KDPoint(initPos, ImageStore::Computer->height() + 25 + 10), KDFont::LargeFont, KDColorBlack, KDColorWhite); + + int y = ImageStore::Computer->height() + 25 + 10 + (KDFont::SmallFont->glyphSize().height() + 10); + initPos = (320 - KDFont::SmallFont->glyphSize().width() * strlen(Messages::dfuSlotsUpdate)) / 2; + ctx->drawString(Messages::dfuSlotsUpdate, KDPoint(initPos, y), KDFont::SmallFont, KDColorBlack, KDColorWhite); +} + +void Interface::drawLoading() { + KDContext * ctx = KDIonContext::sharedContext(); + ctx->fillRect(KDRect(0, 0, 320, 240), KDColorWhite); + drawComputer(ctx, 25); + Ion::Timing::msleep(250); + KDSize fontSize = KDFont::LargeFont->glyphSize(); + int initPos = (320 - fontSize.width() * strlen(Messages::mainTitle)) / 2; + + for (uint8_t i = 0; i < strlen(Messages::mainTitle); i++) { + char tmp[2] = {Messages::mainTitle[i], '\0'}; + ctx->drawString(tmp, KDPoint(initPos + i * (fontSize.width()), ImageStore::Computer->height() + 25 + 10), KDFont::LargeFont, KDColorBlack, KDColorWhite); + Ion::Timing::msleep(50); + } + Ion::Timing::msleep(500); +} + +} diff --git a/bootloader/interface/static/interface.h b/bootloader/interface/static/interface.h new file mode 100644 index 00000000000..ab44df846d3 --- /dev/null +++ b/bootloader/interface/static/interface.h @@ -0,0 +1,20 @@ +#ifndef BOOTLOADER_INTERFACE_STATIC_INTERFACE_H +#define BOOTLOADER_INTERFACE_STATIC_INTERFACE_H + +#include +#include +#include + +namespace Bootloader { +class Interface { + +public: + static void drawComputer(KDContext * ctx, int offset); + static KDCoordinate computerHeight(); + static void drawLoading(); + static void drawFlasher(); +}; + +} + +#endif diff --git a/bootloader/interface/static/messages.h b/bootloader/interface/static/messages.h new file mode 100644 index 00000000000..39a74413099 --- /dev/null +++ b/bootloader/interface/static/messages.h @@ -0,0 +1,86 @@ +#ifndef BOOTLOADER_INTERFACE_STATIC_MESSAGES_H +#define BOOTLOADER_INTERFACE_STATIC_MESSAGES_H + +namespace Bootloader { + +class Messages { +public: + constexpr static const char * mainTitle = "Upsilon Calculator"; + + // Home menu + constexpr static const char * homeTitle = "Select a slot"; + + // Slots OS Type + constexpr static const char * upsilonSlot = "-- Upsilon "; + constexpr static const char * khiSlot = "-- Khi "; + constexpr static const char * omegaSlot = "-- Omega "; + constexpr static const char * epsilonSlot = "-- Epsilon "; + constexpr static const char * invalidSlot = "X - Invalid slot"; + + // Home Submenu + constexpr static const char * homeSlotASubmenu = "1 - Slot A"; + constexpr static const char * homeSlotKhiSubmenu = "2 - Slot Khi"; + constexpr static const char * homeSlotBSubmenu = "3 - Slot B"; + constexpr static const char * homeInstallerSubmenu = "4 - Installer Mode"; + constexpr static const char * homeAboutSubmenu = "5 - About"; + constexpr static const char * homeRebootSubmenu = "6 - Reboot"; + + // DFU menu + constexpr static const char * dfuTitle = "Installer"; + + constexpr static const char * dfuSlotsUpdate = "Waiting for Slots..."; + constexpr static const char * dfuBootloaderUpdate = "Waiting for Bootloader..."; + + // Installer menu + constexpr static const char * installerTitle = "Installer mode"; + + constexpr static const char * installerText1 = "Please select a mode:"; + constexpr static const char * installerSlotsSubmenu = "1 - Flash Slots"; + constexpr static const char * installerBootloaderSubmenu = "2 - Flash Bootloader"; + + // Bootloader Crash Handler + constexpr static const char * bootloaderCrashTitle = "BOOTLOADER CRASH"; + + constexpr static const char * bootloaderCrashMessage1 = "The bootloader has crashed."; + constexpr static const char * bootloaderCrashMessage2 = "Please restart the calculator."; + + // Recovery menu + constexpr static const char * recoveryTitle = "Recovery mode"; + + constexpr static const char * recoveryMessage1 = "The bootloader has detected a crash."; + constexpr static const char * recoveryMessage2 = "Plug the calculator to a device capable of"; + constexpr static const char * recoveryMessage3 = "accessing the internal storage."; + constexpr static const char * recoveryMessage4 = "Press Back to continue."; + constexpr static const char * recoveryMessage5 = "(you will not be able to recover your data !)"; + + // Warning menu + constexpr static const char * epsilonWarningTitle = "Epsilon Slot"; + + constexpr static const char * epsilonWarningMessage1 = "!! WARNING !! "; + constexpr static const char * epsilonWarningMessage2 = "This version of Epsilon"; + constexpr static const char * epsilonWarningMessage3 = "can lock the calculator."; + constexpr static const char * epsilonWarningMessage4 = "Proceed the boot ?"; + constexpr static const char * epsilonWarningMessage5 = "EXE - Yes"; + constexpr static const char * epsilonWarningMessage6 = "BACK - No"; + + // About menu + constexpr static const char * aboutMenuTitle = "About"; + + constexpr static const char * aboutMessage1 = "This is the bootloader of"; + constexpr static const char * aboutMessage2 = "the Upsilon Calculator."; + constexpr static const char * aboutMessage3 = "It is used to install"; + constexpr static const char * aboutMessage4 = "and select the OS"; + constexpr static const char * aboutMessage5 = "to boot."; + + constexpr static const char * bootloaderVersion = "Version 1.0.7 - FREED0M.21.2"; + + //USB NAMES + constexpr static const char * usbUpsilonBootloader = "Upsilon Bootloader"; + constexpr static const char * usbUpsilonRecovery = "Upsilon Recovery"; + constexpr static const char * usbBootloaderUpdate = "Bootloader Update"; + +}; + +}; + +#endif diff --git a/bootloader/main.cpp b/bootloader/main.cpp index 8e07dc8a1c5..9452fc15829 100644 --- a/bootloader/main.cpp +++ b/bootloader/main.cpp @@ -3,6 +3,11 @@ #include #include +#include +#include +#include +#include +#include __attribute__ ((noreturn)) void ion_main(int argc, const char * const argv[]) { // Clear the screen @@ -10,32 +15,46 @@ __attribute__ ((noreturn)) void ion_main(int argc, const char * const argv[]) { // Initialize the backlight Ion::Backlight::init(); - // Set the mode to slot A if undefined - if (Bootloader::Boot::mode() == Bootloader::BootMode::Unknown) - Bootloader::Boot::setMode(Bootloader::BootMode::SlotA); - - // Handle rebooting to bootloader - if (Bootloader::Boot::mode() == Bootloader::BootMode::SlotABootloader) { - Bootloader::Boot::setMode(Bootloader::BootMode::SlotA); - Bootloader::Boot::bootloader(); - } else if (Bootloader::Boot::mode() == Bootloader::BootMode::SlotBBootloader) { - Bootloader::Boot::setMode(Bootloader::BootMode::SlotB); - Bootloader::Boot::bootloader(); + // We check if there is a slot in exam_mode + + bool isSlotA = Bootloader::Slot::isFullyValid(Bootloader::Slot::A()); + + if (isSlotA) { + Bootloader::ExamMode::ExamMode SlotAExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotAExamMode(Bootloader::Slot::A().userlandHeader()->version()); + if (SlotAExamMode != Bootloader::ExamMode::ExamMode::Off && SlotAExamMode != Bootloader::ExamMode::ExamMode::Unknown) { + // We boot the slot in exam_mode + Bootloader::Slot::A().boot(); + } + } + + bool isSlotB = Bootloader::Slot::isFullyValid(Bootloader::Slot::B()); + + if (isSlotB) { + Bootloader::ExamMode::ExamMode SlotBExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotBExamMode(Bootloader::Slot::B().userlandHeader()->version()); + if (SlotBExamMode != Bootloader::ExamMode::ExamMode::Off && SlotBExamMode != Bootloader::ExamMode::ExamMode::Unknown && isSlotB) { + // We boot the slot in exam_mode + Bootloader::Slot::B().boot(); + } } - uint64_t scan = Ion::Keyboard::scan(); - - // Reset+4 => Launch bootloader - if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Four)) { - Bootloader::Boot::bootloader(); - // Reset+1 => Launch slot A - } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::One)) { - Bootloader::Boot::setMode(Bootloader::BootMode::SlotA); - // Reset+2 => Launch slot B - } else if (scan == Ion::Keyboard::State(Ion::Keyboard::Key::Two)) { - Bootloader::Boot::setMode(Bootloader::BootMode::SlotB); + // I have no idea if this will work, but if Parisse did a good job, it should + + bool isKhiSlot = Bootloader::Slot::isFullyValid(Bootloader::Slot::Khi()); + + if (isKhiSlot) { + Bootloader::ExamMode::ExamMode KhiExamMode = (Bootloader::ExamMode::ExamMode)Bootloader::ExamMode::SlotsExamMode::FetchSlotKhiExamMode(Bootloader::Slot::Khi().userlandHeader()->version()); + if (KhiExamMode != Bootloader::ExamMode::ExamMode::Off && KhiExamMode != Bootloader::ExamMode::ExamMode::Unknown && isKhiSlot) { + // We boot the slot in exam_mode + Bootloader::Slot::Khi().boot(); + } } + if (Bootloader::Recovery::hasCrashed()) { + Bootloader::Recovery::recoverData(); + } + + Bootloader::Interface::drawLoading(); + // Boot the firmware Bootloader::Boot::boot(); } diff --git a/bootloader/recovery.cpp b/bootloader/recovery.cpp new file mode 100644 index 00000000000..8c849f781b7 --- /dev/null +++ b/bootloader/recovery.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +constexpr static uint32_t MagicStorage = 0xEE0BDDBA; + +void Bootloader::Recovery::crash_handler(const char *error) { + Ion::Device::Board::shutdownPeripherals(true); + Ion::Device::Board::initPeripherals(false); + Ion::Timing::msleep(100); + Ion::Backlight::init(); + Ion::Backlight::setBrightness(180); + + Ion::Display::pushRectUniform(KDRect(0,0,320,240), KDColorWhite); + CrashMenu menu(error); + menu.open(true); +} + +bool Bootloader::Recovery::hasCrashed() { + bool isA = Bootloader::Slot::A().kernelHeader()->isValid() && Bootloader::Slot::A().userlandHeader()->isValid(); + bool isB = Bootloader::Slot::B().kernelHeader()->isValid() && Bootloader::Slot::B().userlandHeader()->isValid(); + + bool isACrashed = false; + bool isBCrashed = false; + + if (isA) { + const uint32_t * storage = (uint32_t *)Bootloader::Slot::A().userlandHeader()->storageAddress(); + isACrashed = *storage == MagicStorage; + } + + if (isB) { + const uint32_t * storage = (uint32_t *)Bootloader::Slot::B().userlandHeader()->storageAddress(); + isBCrashed = *storage == MagicStorage; + } + + return (isACrashed || isBCrashed); +} + +Bootloader::Recovery::CrashedSlot Bootloader::Recovery::getSlotConcerned() { + bool isA = Bootloader::Slot::A().kernelHeader()->isValid() && Bootloader::Slot::A().userlandHeader()->isValid(); + bool isB = Bootloader::Slot::B().kernelHeader()->isValid() && Bootloader::Slot::B().userlandHeader()->isValid(); + + bool isACrashed = false; + bool isBCrashed = false; + + if (isA) { + const uint32_t * storage = (uint32_t *)Bootloader::Slot::A().userlandHeader()->storageAddress(); + isACrashed = *storage == MagicStorage; + } + + if (isB) { + const uint32_t * storage = (uint32_t *)Bootloader::Slot::B().userlandHeader()->storageAddress(); + isBCrashed = *storage == MagicStorage; + } + + assert(isACrashed || isBCrashed); + + if (isACrashed) { + return CrashedSlot(Bootloader::Slot::A().userlandHeader()->storageSize(), Bootloader::Slot::A().userlandHeader()->storageAddress()); + } else { + return CrashedSlot(Bootloader::Slot::B().userlandHeader()->storageSize(), Bootloader::Slot::B().userlandHeader()->storageAddress()); + } +} + +void Bootloader::Recovery::recoverData() { + Ion::Device::Board::initPeripherals(false); + Ion::Display::pushRectUniform(KDRect(0,0,320,240), KDColorWhite); + Ion::Backlight::init(); + + USBData udata = USBData::Recovery((uint32_t)getSlotConcerned().getStorageAddress(), (uint32_t)getSlotConcerned().getStorageSize()); + + SlotRecoveryMenu menu = SlotRecoveryMenu(&udata); + menu.open(); + + // Invalidate storage header + *(uint32_t *)(getSlotConcerned().getStorageAddress()) = (uint32_t)0x0; + +} diff --git a/bootloader/recovery.h b/bootloader/recovery.h new file mode 100644 index 00000000000..001f8eaab22 --- /dev/null +++ b/bootloader/recovery.h @@ -0,0 +1,31 @@ +#ifndef BOOTLOADER_RECOVERY_H_ +#define BOOTLOADER_RECOVERY_H_ + +#include +#include + +namespace Bootloader { +class Recovery { + public: + class CrashedSlot { + public: + CrashedSlot(const size_t size, const void * address) : m_storageAddress(address), m_storageSize(size) {} + + const size_t getStorageSize() const { return m_storageSize; } + const void * getStorageAddress() const { return m_storageAddress; } + + private: + const void * m_storageAddress; + const size_t m_storageSize; + }; + + static CrashedSlot getSlotConcerned(); + + static void crash_handler(const char * error); + static void recoverData(); + static bool hasCrashed(); + +}; +}; + +#endif //BOOTLOADER_RECOVERY_H_ diff --git a/bootloader/slot.cpp b/bootloader/slot.cpp deleted file mode 100644 index b23655aedef..00000000000 --- a/bootloader/slot.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include -#include - -extern "C" void jump_to_firmware(const uint32_t* stackPtr, const void(*startPtr)(void)); - -namespace Bootloader { - -const Slot Slot::A() { - return Slot(0x90000000); -} - -const Slot Slot::B() { - return Slot(0x90400000); -} - -const KernelHeader* Slot::kernelHeader() const { - return m_kernelHeader; -} - -const UserlandHeader* Slot::userlandHeader() const { - return m_userlandHeader; -} - -[[ noreturn ]] void Slot::boot() const { - // Configure the MPU for the booted firmware - Ion::Device::Board::bootloaderMPU(); - - // Jump - jump_to_firmware(kernelHeader()->stackPointer(), kernelHeader()->startPointer()); - for(;;); -} - -} diff --git a/bootloader/kernel_header.cpp b/bootloader/slots/kernel_header.cpp similarity index 60% rename from bootloader/kernel_header.cpp rename to bootloader/slots/kernel_header.cpp index 7dac97a06b5..c323046d9af 100644 --- a/bootloader/kernel_header.cpp +++ b/bootloader/slots/kernel_header.cpp @@ -1,4 +1,5 @@ -#include +#include +#include namespace Bootloader { @@ -22,4 +23,12 @@ const void(*KernelHeader::startPointer() const)() { return m_startPointer; } +const bool KernelHeader::isAboveVersion16 () const { + int sum = Utility::versionSum(m_version, 2); + char newVersion[] = "16"; + int min = Utility::versionSum(newVersion, 2); + return sum >= min; +} + + } diff --git a/bootloader/kernel_header.h b/bootloader/slots/kernel_header.h similarity index 79% rename from bootloader/kernel_header.h rename to bootloader/slots/kernel_header.h index 017036b62a8..835aa9338a9 100644 --- a/bootloader/kernel_header.h +++ b/bootloader/slots/kernel_header.h @@ -1,7 +1,8 @@ -#ifndef BOOTLOADER_KERNEL_HEADER_H -#define BOOTLOADER_KERNEL_HEADER_H +#ifndef BOOTLOADER_SLOTS_KERNEL_HEADER_H +#define BOOTLOADER_SLOTS_KERNEL_HEADER_H #include +#include namespace Bootloader { @@ -10,6 +11,7 @@ class KernelHeader { const char * version() const; const char * patchLevel() const; const bool isValid() const; + const bool isAboveVersion16() const; const uint32_t* stackPointer() const; const void(*startPointer() const)(); diff --git a/bootloader/slots/slot.cpp b/bootloader/slots/slot.cpp new file mode 100644 index 00000000000..2a3cebee21f --- /dev/null +++ b/bootloader/slots/slot.cpp @@ -0,0 +1,73 @@ +#include +#include +#include +#include +#include + +extern "C" void jump_to_firmware(const uint32_t* stackPtr, const void(*startPtr)(void)); + +namespace Bootloader { + +const Slot Slot::A() { + return Slot(0x90000000); +} + +const Slot Slot::B() { + return Slot(0x90400000); +} + +const Slot Slot::Khi() { + return Slot(0x90180000); +} + +const KernelHeader* Slot::kernelHeader() const { + return m_kernelHeader; +} + +const UserlandHeader* Slot::userlandHeader() const { + if (m_userlandHeader->isValid()) { + return m_userlandHeader; + } else if (m_userland2Header->isValid()) { + return m_userland2Header; + } else { + return m_userlandHeader; + } +} + +[[ noreturn ]] void Slot::boot() const { + + if (m_address == 0x90000000) { + // If we are booting from slot A, we need to lock the slot B + Ion::Device::Flash::LockSlotB(); + } else { + // If we are booting from slot B, we need to lock the slot A (and Khi) + Ion::Device::Flash::LockSlotA(); + } + + // Erase the bootloader integrated in slots in Epsilon 20 + if (m_userland2Header->isValid()) { + if (m_address == 0x90000000) { + // Check if bootloader is present in slot A + if (*(uint32_t*)0x90010000 != 0xFFFFFFFF) { + // Erase bootloader in slot A + Ion::Device::ExternalFlash::EraseSector(9); + } + } + else if (m_address == 0x90400000) { + // Check if bootloader is present in slot B + if (*(uint32_t*)0x90410000 != 0xFFFFFFFF) { + // Erase bootloader in slot B + Ion::Device::ExternalFlash::EraseSector(73); + } + } + } + + // Configure the MPU for the booted firmware + Ion::Device::Board::bootloaderMPU(); + + // Jump + jump_to_firmware(kernelHeader()->stackPointer(), kernelHeader()->startPointer()); + for(;;); +} + +} diff --git a/bootloader/slot.h b/bootloader/slots/slot.h similarity index 54% rename from bootloader/slot.h rename to bootloader/slots/slot.h index 15a883f3948..1d004660590 100644 --- a/bootloader/slot.h +++ b/bootloader/slots/slot.h @@ -1,5 +1,5 @@ -#ifndef BOOTLOADER_SLOT_H -#define BOOTLOADER_SLOT_H +#ifndef BOOTLOADER_SLOTS_SLOT_H +#define BOOTLOADER_SLOTS_SLOT_H #include @@ -13,18 +13,28 @@ class Slot { public: Slot(uint32_t address) : m_kernelHeader(reinterpret_cast(address)), - m_userlandHeader(reinterpret_cast(address + 64 * 1024)) { } + m_userlandHeader(reinterpret_cast(address + 64 * 1024)), + m_userland2Header(reinterpret_cast(address + 128 * 1024)), + m_address(address) {} const KernelHeader* kernelHeader() const; const UserlandHeader* userlandHeader() const; [[ noreturn ]] void boot() const; + const uint32_t address() const { return m_address; } static const Slot A(); static const Slot B(); + static const Slot Khi(); + + static bool isFullyValid(const Slot& slot) { + return slot.kernelHeader()->isValid() && slot.userlandHeader()->isValid(); + } private: const KernelHeader* m_kernelHeader; const UserlandHeader* m_userlandHeader; + const UserlandHeader* m_userland2Header; + const uint32_t m_address; }; diff --git a/bootloader/slots/slot_exam_mode.cpp b/bootloader/slots/slot_exam_mode.cpp new file mode 100644 index 00000000000..e532828afd0 --- /dev/null +++ b/bootloader/slots/slot_exam_mode.cpp @@ -0,0 +1,217 @@ +#include +#include +#include +#include + +namespace Bootloader { +namespace ExamMode { + +/* The exam mode is written in flash so that it is resilient to resets. + * We erase the dedicated flash sector (all bits written to 1) and, upon + * deactivating or activating standard, nosym or Dutch exam mode we write one, two or tree + * bits to 0. To determine in which exam mode we are, we count the number of + * leading 0 bits. If it is equal to: + * - 0[3]: the exam mode is off; + * - 1[3]: the standard exam mode is activated; + * - 2[3]: the NoSym exam mode is activated; + * - 3[3]: the Dutch exam mode is activated; + * - 4[3]: the NoSymNoText exam mode is activated. */ + +/* significantExamModeAddress returns the first uint32_t * in the exam mode + * flash sector that does not point to 0. If this flash sector has only 0s or + * if it has only one 1, it is erased (to 1) and significantExamModeAddress + * returns the start of the sector. */ + +constexpr static size_t numberOfBitsInByte = 8; + +// if i = 0b000011101, firstOneBitInByte(i) returns 5 +size_t numberOfBitsAfterLeadingZeroes(int i) { + int minShift = 0; + int maxShift = numberOfBitsInByte; + while (maxShift > minShift+1) { + int shift = (minShift + maxShift)/2; + int shifted = i >> shift; + if (shifted == 0) { + maxShift = shift; + } else { + minShift = shift; + } + } + return maxShift; +} + +uint8_t SlotsExamMode::FetchSlotExamMode(const char * version, const char * Slot) { + // Get start and end from version and slot + uint32_t start = 0; + uint32_t end = 0; + if (Slot == "A") { + // If version under 16 get old addresses + if (version[0] < '1' || (version[0] == '1' && version[1] < '6')) { + start = getSlotAStartExamAddress(0); + end = getSlotAEndExamAddress(0); + } + // Else get new addresses + else { + start = getSlotAStartExamAddress(1); + end = getSlotAEndExamAddress(1); + } + } + else if (Slot == "B") { + // If version under 16 get old + if (version[0] < '1' || (version[0] == '1' && version[1] < '6')) { + start = getSlotBStartExamAddress(0); + end = getSlotBEndExamAddress(0); + } + // Else get new + else { + start = getSlotBStartExamAddress(1); + end = getSlotBEndExamAddress(1); + } + } else if (Slot == "Khi") { + // We directly get the address of the Khi exam mode without checking the + // version, because on Khi, version is KhiCAS version, not the OS version + start = getSlotKhiStartExamAddress(); + end = getSlotKhiEndExamAddress(); + } + + if (strcmp("15.9.0", version) >= 0) { + return examFetch15(start, end); + } else if (strcmp("16.9.0", version) > 0) { + return examFetch16(start, end); + } else if (strcmp("19.0.0", version) > 0) { + return examFetch1718(start, end); + } else { + return examFetch19(start, end); + } +} + + + +uint8_t SlotsExamMode::FetchSlotAExamMode(const char* version) { + return FetchSlotExamMode(version, "A"); +} + +uint8_t SlotsExamMode::FetchSlotBExamMode(const char* version) { + return FetchSlotExamMode(version, "B"); +} + +uint8_t SlotsExamMode::FetchSlotKhiExamMode(const char* version) { + return FetchSlotExamMode(version, "Khi"); +} + +uint8_t SlotsExamMode::examFetch15(uint32_t start, uint32_t end) { + uint32_t * persitence_start_32 = (uint32_t *)start; + uint32_t * persitence_end_32 = (uint32_t *)end; + assert((persitence_end_32 - persitence_start_32) % 4 == 0); + while (persitence_start_32 < persitence_end_32 && *persitence_start_32 == 0x0) { + // Scan by groups of 32 bits to reach first non-zero bit + persitence_start_32++; + } + uint8_t * persitence_start_8 = (uint8_t *)persitence_start_32; + uint8_t * persitence_end_8 = (uint8_t *)persitence_end_32; + while (persitence_start_8 < persitence_end_8 && *persitence_start_8 == 0x0) { + // Scan by groups of 8 bits to reach first non-zero bit + persitence_start_8++; + } + if (persitence_start_8 == persitence_end_8 + // we can't toggle from 0[3] to 2[3] when there is only one 1 bit in the whole sector + || (persitence_start_8 + 1 == persitence_end_8 && *persitence_start_8 == 1)) { + assert(Ion::Device::Flash::SectorAtAddress(start) >= 0); + Ion::Device::Flash::EraseSector(start); + uint32_t nbOfZerosBefore = (((uint8_t*)start - (uint8_t*)start) * numberOfBitsInByte) % 4; + // Count the number of 0[3] at reading address + size_t numberOfLeading0 = (numberOfBitsInByte - numberOfBitsAfterLeadingZeroes(*(uint8_t*)start)) % 4; + return (nbOfZerosBefore + numberOfLeading0) % 4; + } + uint32_t nbOfZerosBefore = ((persitence_start_8 - (uint8_t*)start) * numberOfBitsInByte) % 4; + // Count the number of 0[3] at reading address + size_t numberOfLeading0 = (numberOfBitsInByte - numberOfBitsAfterLeadingZeroes(*persitence_start_8)) % 4; + return (nbOfZerosBefore + numberOfLeading0) % 4; +} + +uint8_t SlotsExamMode::examFetch16(uint32_t start, uint32_t end) { + uint8_t* persitence_start_8 = (uint8_t*)start; + uint8_t* persitence_end_8 = (uint8_t*)end; + while (persitence_start_8 + 1 <= persitence_end_8 && (*persitence_start_8 != 0xFF)) { + // Scan by groups of 8 bits to reach first non-zero bit + persitence_start_8++; + } + + return *(persitence_start_8 - 1); +} + +uint8_t SlotsExamMode::examFetch1718(uint32_t start, uint32_t end) { + uint8_t* persitence_start_8 = (uint8_t*)start; + uint8_t* persitence_end_8 = (uint8_t*)end; + while (persitence_start_8 + 1 <= persitence_end_8 && (*persitence_start_8 != 0xFF)) { + // Scan by groups of 8 bits to reach first non-zero bit + persitence_start_8++; +} + + return *(persitence_start_8 - 2); +} + +uint8_t SlotsExamMode::examFetch19(uint32_t start, uint32_t end) { + uint16_t* start16 = (uint16_t*)start; + uint16_t* end16 = (uint16_t*)end; + + for (uint16_t* i = end16 - 2; i > start16; i--) { + if (*i != 0xFFFF) { + uint8_t highByte = *i >> 8; + uint8_t lowByte = *i & 0xFF; + if (highByte > lowByte) { + return highByte; + } + else { + return lowByte; + } + } + } +} + +uint32_t SlotsExamMode::getSlotAStartExamAddress(int ExamVersion) { + if (ExamVersion == 0) { + return SlotAExamModeBufferStartOldVersions; + } + else { + return SlotAExamModeBufferStartNewVersions; + } +} + +uint32_t SlotsExamMode::getSlotAEndExamAddress(int ExamVersion) { + if (ExamVersion == 0) { + return SlotAExamModeBufferEndOldVersions; + } + else { + return SlotAExamModeBufferEndNewVersions;; + } +} + +uint32_t SlotsExamMode::getSlotBStartExamAddress(int ExamVersion) { + if (ExamVersion == 0) { + return SlotBExamModeBufferStartOldVersions; + } + else { + return SlotBExamModeBufferStartNewVersions; + } +} + +uint32_t SlotsExamMode::getSlotBEndExamAddress(int ExamVersion) { + if (ExamVersion == 0) { + return SlotBExamModeBufferEndOldVersions; + } + else { + return SlotBExamModeBufferEndNewVersions; + } +} + +uint32_t SlotsExamMode::getSlotKhiStartExamAddress() { + return SlotKhiExamModeBufferStart; +} + +uint32_t SlotsExamMode::getSlotKhiEndExamAddress() { + return SlotKhiExamModeBufferEnd; +} + +} +} diff --git a/bootloader/slots/slot_exam_mode.h b/bootloader/slots/slot_exam_mode.h new file mode 100644 index 00000000000..3c19ac7a90f --- /dev/null +++ b/bootloader/slots/slot_exam_mode.h @@ -0,0 +1,59 @@ +#ifndef BOOTLOADER_SLOTS_EXAM_MODE_H +#define BOOTLOADER_SLOTS_EXAM_MODE_H + +extern "C" { + #include + } + +namespace Bootloader { +namespace ExamMode { + +static const uint32_t SlotAExamModeBufferStartOldVersions = 0x90001000; +static const uint32_t SlotAExamModeBufferEndOldVersions = 0x90003000; + +static const uint32_t SlotAExamModeBufferStartNewVersions = 0x903f0000; +static const uint32_t SlotAExamModeBufferEndNewVersions = 0x90400000; + +static const uint32_t SlotBExamModeBufferStartOldVersions = 0x90401000; +static const uint32_t SlotBExamModeBufferEndOldVersions = 0x90403000; + +static const uint32_t SlotBExamModeBufferStartNewVersions = 0x907f0000; +static const uint32_t SlotBExamModeBufferEndNewVersions = 0x90800000; + +static const uint32_t SlotKhiExamModeBufferStart = 0x90181000; +static const uint32_t SlotKhiExamModeBufferEnd = 0x90183000; + +class SlotsExamMode{ + public: + static uint8_t FetchSlotExamMode(const char* version, const char* Slot); + static uint8_t FetchSlotAExamMode(const char* version); + static uint8_t FetchSlotBExamMode(const char* version); + static uint8_t FetchSlotKhiExamMode(const char* version); + + static uint32_t getSlotAStartExamAddress(int ExamVersion); + static uint32_t getSlotAEndExamAddress(int ExamVersion); + static uint32_t getSlotBStartExamAddress(int ExamVersion); + static uint32_t getSlotBEndExamAddress(int ExamVersion); + static uint32_t getSlotKhiStartExamAddress(); + static uint32_t getSlotKhiEndExamAddress(); + + static uint8_t examFetch15(uint32_t start, uint32_t end); + static uint8_t examFetch1718(uint32_t start, uint32_t end); + static uint8_t examFetch16(uint32_t start, uint32_t end); + static uint8_t examFetch19(uint32_t start, uint32_t end); + +}; + +enum class ExamMode : int8_t { + Unknown = -1, + Off = 0, + Standard = 1, + NoSym = 2, + NoSymNoText = 3, + Dutch = 4, + }; + +} +} + +#endif diff --git a/bootloader/userland_header.cpp b/bootloader/slots/userland_header.cpp similarity index 54% rename from bootloader/userland_header.cpp rename to bootloader/slots/userland_header.cpp index 5108b187dfa..a3e57afec7a 100644 --- a/bootloader/userland_header.cpp +++ b/bootloader/slots/userland_header.cpp @@ -1,4 +1,4 @@ -#include +#include namespace Bootloader { @@ -10,11 +10,13 @@ const char * UserlandHeader::version() const { } const bool UserlandHeader::isValid() const { - return m_header == Magic && m_footer == Magic; + // We only verify only the first Magic Number, to display version such as + // Epsilon 16 with older UserlandHeader layout + return m_header == Magic; } const bool UserlandHeader::isOmega() const { - return m_ohm_header == OmegaMagic && m_ohm_footer == OmegaMagic; + return m_omegaMagicHeader == OmegaMagic && m_omegaMagicFooter == OmegaMagic; } @@ -23,11 +25,19 @@ const char * UserlandHeader::omegaVersion() const { } const bool UserlandHeader::isUpsilon() const { - return m_ups_header == UpsilonMagic && m_ups_footer == UpsilonMagic; + return m_upsilonMagicHeader == UpsilonMagic && m_upsilonMagicHeader == UpsilonMagic; } const char * UserlandHeader::upsilonVersion() const { return m_UpsilonVersion; } +const void * UserlandHeader::storageAddress() const { + return m_storageAddressRAM; +} + +const size_t UserlandHeader::storageSize() const { + return m_storageSizeRAM; +} + } diff --git a/bootloader/userland_header.h b/bootloader/slots/userland_header.h similarity index 79% rename from bootloader/userland_header.h rename to bootloader/slots/userland_header.h index 97f4ff5e647..382e409b071 100644 --- a/bootloader/userland_header.h +++ b/bootloader/slots/userland_header.h @@ -1,5 +1,5 @@ -#ifndef BOOTLOADER_USERLAND_HEADER_H -#define BOOTLOADER_USERLAND_HEADER_H +#ifndef BOOTLOADER_SLOTS_USERLAND_HEADER_H +#define BOOTLOADER_SLOTS_USERLAND_HEADER_H #include #include @@ -15,6 +15,8 @@ class UserlandHeader { const char * omegaVersion() const; const bool isUpsilon() const; const char * upsilonVersion() const; + const void * storageAddress() const; + const size_t storageSize() const; private: UserlandHeader(); @@ -32,14 +34,14 @@ class UserlandHeader { uint32_t m_externalAppsRAMStart; uint32_t m_externalAppsRAMEnd; uint32_t m_footer; - uint32_t m_ohm_header; + uint32_t m_omegaMagicHeader; const char m_omegaVersion[16]; const volatile char m_username[16]; - uint32_t m_ohm_footer; - uint32_t m_ups_header; + uint32_t m_omegaMagicFooter; + uint32_t m_upsilonMagicHeader; const char m_UpsilonVersion[16]; uint32_t m_osType; - uint32_t m_ups_footer; + uint32_t m_upsilonMagicFooter; }; extern const UserlandHeader* s_userlandHeaderA; diff --git a/bootloader/trampoline.cpp b/bootloader/trampoline.cpp index 4d1013f3491..876db049652 100644 --- a/bootloader/trampoline.cpp +++ b/bootloader/trampoline.cpp @@ -36,6 +36,7 @@ void* CustomTrampolines[CUSTOM_TRAMPOLINES_COUNT] __attribute__((used)) = { (void*) Bootloader::Boot::mode, + // This function doesn't do anything ... (void*) Bootloader::Boot::setMode }; diff --git a/bootloader/usb_data.cpp b/bootloader/usb_data.cpp new file mode 100644 index 00000000000..bd88437918c --- /dev/null +++ b/bootloader/usb_data.cpp @@ -0,0 +1,39 @@ +#include +#include + +#include +#include +#include +#include + +extern "C" { +} + +static char data[255]; + +const char * Bootloader::USBData::buildStringDescriptor(StringHeader header, uint32_t startAddress, uint32_t size) { + strlcpy(data, header.getString(), sizeof(data)); + itoa((int32_t)startAddress, &data[strlen(header.getString())], 16); + data[strlen(header.getString()) + 8] = '/'; + data[strlen(header.getString()) + 8 + 1] = '0'; + data[strlen(header.getString()) + 8 + 2] = '1'; + data[strlen(header.getString()) + 8 + 3] = '*'; + data[strlen(header.getString()) + 8 + 4] = '0'; + itoa((int32_t)size/1024, &data[strlen(header.getString()) + 8 + 5], 10); + data[strlen(header.getString()) + 8 + 5 + 2] = 'K'; + data[strlen(header.getString()) + 8 + 5 + 2 + 1] = 'g'; + data[strlen(header.getString()) + 8 + 5 + 2 + 2] = '\0'; + return &data[0]; +} + +const Bootloader::USBData Bootloader::USBData::DEFAULT() { + return USBData("@Flash/0x90000000/08*004Kg,01*032Kg,63*064Kg,64*064Kg", Messages::usbUpsilonBootloader, ProtectionState(false, true)); +} + +const Bootloader::USBData Bootloader::USBData::BOOTLOADER_UPDATE() { + return USBData("@Flash/0x08000000/04*016Kg", Messages::usbBootloaderUpdate, ProtectionState(true, false)); +} + +Bootloader::USBData Bootloader::USBData::Recovery(uint32_t startAddress, uint32_t size) { + return USBData(buildStringDescriptor(StringHeader::SRAM(), startAddress, size), Messages::usbUpsilonRecovery, ProtectionState(false, false)); +} diff --git a/bootloader/usb_data.h b/bootloader/usb_data.h new file mode 100644 index 00000000000..a0477edbd78 --- /dev/null +++ b/bootloader/usb_data.h @@ -0,0 +1,56 @@ +#ifndef BOOTLOADER_USB_DATA_H_ +#define BOOTLOADER_USB_DATA_H_ + +#include +#include + +namespace Bootloader { + +class ProtectionState { + public: + ProtectionState(bool unlockInternal = false, bool unlockExternal = true) : m_protectInternal(!unlockInternal), m_protectExternal(!unlockExternal) {}; + + bool isProtectedInternal() const { return m_protectInternal; } + bool isProtectedExternal() const { return m_protectExternal; } + + private: + bool m_protectInternal; + bool m_protectExternal; +}; + +class USBData { + public: + class StringHeader{ + public: + StringHeader(const char * string) : m_string(string) {}; + + const char * getString() const { return m_string; } + + static const StringHeader Flash() { return StringHeader("@Flash/0x"); } + static const StringHeader SRAM() { return StringHeader("@SRAM/0x"); } + + private: + const char * m_string; + }; + + USBData(const char * desc, const char * name, ProtectionState data = ProtectionState()) : m_stringDescriptor(desc), m_name(name), m_data(data) {}; + + const char * stringDescriptor() const { return m_stringDescriptor; } + const char * getName() const { return m_name; } + ProtectionState getData() const { return m_data; } + + static const char * buildStringDescriptor(StringHeader header, uint32_t startAddress, uint32_t size); + + static const USBData DEFAULT(); + static const USBData BOOTLOADER_UPDATE(); + static USBData Recovery(uint32_t startAddress, uint32_t size); + + private: + const char * m_stringDescriptor; + const char * m_name; + ProtectionState m_data; + +}; +} + +#endif //BOOTLOADER_USB_DATA_H_ diff --git a/bootloader/utility.cpp b/bootloader/utility.cpp new file mode 100644 index 00000000000..786e8e0fc78 --- /dev/null +++ b/bootloader/utility.cpp @@ -0,0 +1,26 @@ +#include +#include + +// This function takes a pointer to a string (version) and the size of the +// string (versionSize) and returns an integer representing the version. +// Example: "1.2.3" will return 10203. +// "1.0.1-dev" will return 10001. +int Utility::versionSum(const char * version, int length) { + int sum = 0; + int currentNumber = 0; + // List of numbers that are allowed in a version + const char * allowedNumbers = "0123456789"; + for (int i = 0; i < length; i++) { + if (version[i] == '.') { + sum = sum * 100 + currentNumber; + currentNumber = 0; + } else if (strchr(allowedNumbers, version[i]) != nullptr) { + currentNumber = currentNumber * 10 + (version[i] - '0'); + } else { + // We found a character that is not a number or a dot, so we stop + break; + } + } + sum = sum * 100 + currentNumber; + return sum; +} diff --git a/bootloader/utility.h b/bootloader/utility.h new file mode 100644 index 00000000000..6a2c0d59014 --- /dev/null +++ b/bootloader/utility.h @@ -0,0 +1,8 @@ +#ifndef _BOOTLOADER_ITOA_H_ +#define _BOOTLOADER_ITOA_H_ + +namespace Utility { + extern int versionSum(const char * version, int length); +} + +#endif diff --git a/build/config.mak b/build/config.mak index 502989a91a1..cf91239fece 100644 --- a/build/config.mak +++ b/build/config.mak @@ -5,14 +5,16 @@ DEBUG ?= 0 HOME_DISPLAY_EXTERNALS ?= 1 EPSILON_VERSION ?= 15.5.0 -OMEGA_VERSION ?= 2.0.0 +OMEGA_VERSION ?= 2.0.2 +UPSILON_VERSION ?= 1.0.1-dev # OMEGA_USERNAME ?= N/A -OMEGA_STATE ?= public -EPSILON_APPS ?= calculation rpn graph code statistics probability solver atomic sequence regression settings external +OMEGA_STATE ?= dev +EPSILON_APPS ?= calculation graph rpn code statistics probability solver atomic sequence regression reader settings external SUBMODULES_APPS = atomic rpn EPSILON_I18N ?= en fr nl pt it de es hu EPSILON_COUNTRIES ?= WW CA DE ES FR GB IT NL PT US EPSILON_GETOPT ?= 0 ESCHER_LOG_EVENTS_BINARY ?= 0 -THEME_NAME ?= omega_light +THEME_NAME ?= upsilon_light THEME_REPO ?= local +INCLUDE_ULAB ?= 1 diff --git a/build/device/dfu.py b/build/device/dfu.py index bac05e3e5d7..7245632adfa 100644 --- a/build/device/dfu.py +++ b/build/device/dfu.py @@ -9,7 +9,7 @@ """This module implements enough functionality to program the STM32F4xx over DFU, without requiring dfu-util. See app note AN3156 for a description of the DFU protocol. -See document UM0391 for a dscription of the DFuse file. +See document UM0391 for a description of the DFuse file. """ from __future__ import print_function @@ -47,6 +47,7 @@ __DFU_CLRSTATUS = 4 __DFU_GETSTATE = 5 __DFU_ABORT = 6 +__DFU_UNLOCK = 11 # DFU status __DFU_STATE_APP_IDLE = 0x00 @@ -145,6 +146,15 @@ def abort_request(): __dev.ctrl_transfer(0x21, __DFU_ABORT, 0, __DFU_INTERFACE, None, __TIMEOUT) +def unlock_request(): + """Deactivate the protection""" + try: + __dev.ctrl_transfer(0x21, __DFU_UNLOCK, 0, __DFU_INTERFACE, None, + __TIMEOUT) + except usb.core.USBError: + print("Error when disabling protection, ignoring", file=sys.stderr) + + def clr_status(): """Clears any error status (perhaps left over from a previous session).""" __dev.ctrl_transfer(0x21, __DFU_CLRSTATUS, 0, __DFU_INTERFACE, @@ -587,6 +597,7 @@ def main(): elements = read_dfu_file(args.path) if not elements: return + unlock_request() print("Writing memory...") write_elements(elements, args.mass_erase, progress=cli_progress) diff --git a/build/device/elf2dfu.py b/build/device/elf2dfu.py index 6fc0523f0f5..cb62429bd5a 100644 --- a/build/device/elf2dfu.py +++ b/build/device/elf2dfu.py @@ -39,8 +39,9 @@ def generate_dfu_file(targets, usb_vid_pid, dfu_file): data += struct.pack(' 1: + ext_path = os.path.join(os.getcwd(), sys.argv[1]) + if not os.path.isfile(ext_path): + print("Error: File not found!") + sys.exit(-1) + file = open(ext_path, "r+b") + file.read(MAGIK_POS) + packet = bytearray(file.read(4)) + for b in packet: + if b != 0: + print("Error: Invalid file! (maybe already patched?)") + sys.exit(-1) + + for i in range(4): + packet[i] = MAGIK_CODE[i] + + file.seek(MAGIK_POS) + file.write(packet) + print("External bin patched!") \ No newline at end of file diff --git a/build/device/usb/_debug.py b/build/device/usb/_debug.py index a4ca7e82e4a..bd2abcc9fed 100644 --- a/build/device/usb/_debug.py +++ b/build/device/usb/_debug.py @@ -55,7 +55,7 @@ def decorator_logging(f): if not _enable_tracing: return f def do_trace(*args, **named_args): - # this if is just a optimization to avoid unecessary string formatting + # this if is just a optimization to avoid unnecessary string formatting if logging.DEBUG >= logger.getEffectiveLevel(): fn = type(args[0]).__name__ + '.' + f.__name__ _trace_function_call(logger, fn, *args[1:], **named_args) @@ -70,7 +70,7 @@ def decorator_logging(f): if not _enable_tracing: return f def do_trace(*args, **named_args): - # this if is just a optimization to avoid unecessary string formatting + # this if is just a optimization to avoid unnecessary string formatting if logging.DEBUG >= logger.getEffectiveLevel(): _trace_function_call(logger, f.__name__, *args, **named_args) return f(*args, **named_args) diff --git a/build/device/usb/backend/__init__.py b/build/device/usb/backend/__init__.py index f28e4940f6a..ca1ead987c7 100644 --- a/build/device/usb/backend/__init__.py +++ b/build/device/usb/backend/__init__.py @@ -35,7 +35,7 @@ IBackend - backend interface. Backends are Python objects which implement the IBackend interface. -The easiest way to do so is inherinting from IBackend. +The easiest way to do so is inheriting from IBackend. PyUSB already provides backends for libusb versions 0.1 and 1.0, and OpenUSB library. Backends modules included with PyUSB are required to @@ -87,7 +87,7 @@ class IBackend(_objfinalizer.AutoFinalizedObject): IBackend is the basic interface for backend implementations. By default, the methods of the interface raise a NotImplementedError exception. A - backend implementation should replace the methods to provide the funcionality + backend implementation should replace the methods to provide the functionality necessary. As Python is a dynamic typed language, you are not obligated to inherit from @@ -126,7 +126,7 @@ def get_configuration_descriptor(self, dev, config): r"""Return a configuration descriptor of the given device. The object returned is required to have all the Configuration Descriptor - fields acessible as member variables. They must be convertible (but + fields accessible as member variables. They must be convertible (but not required to be equal) to the int type. The dev parameter is the device identification object. @@ -156,7 +156,7 @@ def get_endpoint_descriptor(self, dev, ep, intf, alt, config): r"""Return an endpoint descriptor of the given device. The object returned is required to have all the Endpoint Descriptor - fields acessible as member variables. They must be convertible (but + fields accessible as member variables. They must be convertible (but not required to be equal) to the int type. The ep parameter is the endpoint logical index (not the bEndpointAddress @@ -246,7 +246,7 @@ def bulk_write(self, dev_handle, ep, intf, data, timeout): of the interface containing the endpoint. The data parameter is the data to be sent. It must be an instance of the array.array class. The timeout parameter specifies a time limit to the operation - in miliseconds. + in milliseconds. The method returns the number of bytes written. """ @@ -261,7 +261,7 @@ def bulk_read(self, dev_handle, ep, intf, buff, timeout): of the interface containing the endpoint. The buff parameter is the buffer to receive the data read, the length of the buffer tells how many bytes should be read. The timeout parameter - specifies a time limit to the operation in miliseconds. + specifies a time limit to the operation in milliseconds. The method returns the number of bytes actually read. """ @@ -276,7 +276,7 @@ def intr_write(self, dev_handle, ep, intf, data, timeout): of the interface containing the endpoint. The data parameter is the data to be sent. It must be an instance of the array.array class. The timeout parameter specifies a time limit to the operation - in miliseconds. + in milliseconds. The method returns the number of bytes written. """ @@ -291,7 +291,7 @@ def intr_read(self, dev_handle, ep, intf, size, timeout): of the interface containing the endpoint. The buff parameter is the buffer to receive the data read, the length of the buffer tells how many bytes should be read. The timeout parameter - specifies a time limit to the operation in miliseconds. + specifies a time limit to the operation in milliseconds. The method returns the number of bytes actually read. """ @@ -306,7 +306,7 @@ def iso_write(self, dev_handle, ep, intf, data, timeout): of the interface containing the endpoint. The data parameter is the data to be sent. It must be an instance of the array.array class. The timeout parameter specifies a time limit to the operation - in miliseconds. + in milliseconds. The method returns the number of bytes written. """ @@ -321,7 +321,7 @@ def iso_read(self, dev_handle, ep, intf, size, timeout): of the interface containing the endpoint. The buff parameter is buffer to receive the data read, the length of the buffer tells how many bytes should be read. The timeout parameter specifies - a time limit to the operation in miliseconds. + a time limit to the operation in milliseconds. The method returns the number of bytes actually read. """ @@ -347,7 +347,7 @@ def ctrl_transfer(self, IN requests it is the buffer to hold the data read. The number of bytes requested to transmit or receive is equal to the length of the array times the data.itemsize field. The timeout parameter - specifies a time limit to the operation in miliseconds. + specifies a time limit to the operation in milliseconds. Return the number of bytes written (for OUT transfers) or the data read (for IN transfers), as an array.array object. diff --git a/build/device/usb/backend/libusb1.py b/build/device/usb/backend/libusb1.py index 6615d68b780..65b17621391 100644 --- a/build/device/usb/backend/libusb1.py +++ b/build/device/usb/backend/libusb1.py @@ -300,7 +300,7 @@ def _setup_prototypes(lib): # void libusb_exit (struct libusb_context *ctx) lib.libusb_exit.argtypes = [c_void_p] - # ssize_t libusb_get_device_list (libusb_context *ctx, + # size_t libusb_get_device_list (libusb_context *ctx, # libusb_device ***list) lib.libusb_get_device_list.argtypes = [ c_void_p, diff --git a/build/device/usb/core.py b/build/device/usb/core.py index 7546de2bc2e..282e55afe07 100644 --- a/build/device/usb/core.py +++ b/build/device/usb/core.py @@ -735,7 +735,7 @@ class Device(_objfinalizer.AutoFinalizedObject): value for most devices) and then writes some data to the endpoint 0x01. Timeout values for the write, read and ctrl_transfer methods are specified - in miliseconds. If the parameter is omitted, Device.default_timeout value + in milliseconds. If the parameter is omitted, Device.default_timeout value will be used instead. This property can be set by the user at anytime. """ @@ -961,7 +961,7 @@ def write(self, endpoint, data, timeout = None): The data parameter should be a sequence like type convertible to the array type (see array module). - The timeout is specified in miliseconds. + The timeout is specified in milliseconds. The method returns the number of bytes written. """ @@ -993,7 +993,7 @@ def read(self, endpoint, size_or_buffer, timeout = None): tells how many bytes you want to read or supplies the buffer to receive the data (it *must* be an object of the type array). - The timeout is specified in miliseconds. + The timeout is specified in milliseconds. If the size_or_buffer parameter is the number of bytes to read, the method returns an array object with the data read. If the diff --git a/build/doc/Doxyfile b/build/doc/Doxyfile index 8f9c49eebbf..421deb4639e 100644 --- a/build/doc/Doxyfile +++ b/build/doc/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.8.18 +# Doxyfile 1.9.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -32,7 +32,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = Omega +PROJECT_NAME = Upsilon # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version @@ -227,6 +227,14 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. @@ -315,7 +323,10 @@ OPTIMIZE_OUTPUT_SLICE = NO # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. EXTENSION_MAPPING = @@ -449,6 +460,19 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -512,6 +536,13 @@ EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation @@ -549,11 +580,18 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# (including Cygwin) ands Mac users are advised to set this option to NO. +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. # The default value is: system dependent. CASE_SENSE_NAMES = NO @@ -792,7 +830,10 @@ WARN_IF_DOC_ERROR = NO WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. # The default value is: NO. WARN_AS_ERROR = NO @@ -836,8 +877,8 @@ INPUT = apps \ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: https://www.gnu.org/software/libiconv/) for the list of -# possible encodings. +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 @@ -850,13 +891,15 @@ INPUT_ENCODING = UTF-8 # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, # *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), -# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen -# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, -# *.vhdl, *.ucf, *.qsf and *.ice. +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -1114,6 +1157,44 @@ USE_HTAGS = NO VERBATIM_HEADERS = YES +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to +# YES then doxygen will add the directory of each input to the include path. +# The default value is: YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- @@ -1125,13 +1206,6 @@ VERBATIM_HEADERS = YES ALPHABETICAL_INDEX = YES -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored @@ -1302,10 +1376,11 @@ HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: https://developer.apple.com/xcode/), introduced with OSX -# 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. @@ -1347,8 +1422,8 @@ DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML @@ -1378,7 +1453,7 @@ CHM_FILE = HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). +# (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1423,7 +1498,8 @@ QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1431,8 +1507,8 @@ QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- -# folders). +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1440,16 +1516,16 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = @@ -1461,9 +1537,9 @@ QHP_CUST_FILTER_ATTRS = QHP_SECT_FILTER_ATTRS = -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = @@ -1544,8 +1620,8 @@ EXT_LINKS_IN_WINDOW = NO # tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see # https://inkscape.org) to generate formulas as SVG images instead of PNGs for # the HTML output. These images will generally look nicer at scaled resolutions. -# Possible values are: png The default and svg Looks nicer but requires the -# pdf2svg tool. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). # The default value is: png. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1590,7 +1666,7 @@ USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. @@ -1620,7 +1696,8 @@ MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1667,7 +1744,8 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). +# Xapian (see: +# https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1680,8 +1758,9 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). See the section "External Indexing and -# Searching" for details. +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. # This tag requires that the tag SEARCHENGINE is set to YES. SEARCHENGINE_URL = @@ -1845,9 +1924,11 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES, to get a -# higher quality PDF documentation. +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2279,7 +2360,7 @@ HIDE_UNDOC_RELATIONS = YES # http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent # Bell Labs. The other options in this section have no effect if this option is # set to NO -# The default value is: NO. +# The default value is: YES. HAVE_DOT = NO @@ -2358,10 +2439,32 @@ UML_LOOK = NO # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. +# This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 10 +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. @@ -2435,7 +2538,9 @@ DIRECTORY_GRAPH = YES # Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order # to make the SVG files visible in IE 9+ (other browsers do not have this # requirement). -# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd, +# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo, +# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo, # png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and # png:gdiplus:gdiplus. # The default value is: png. @@ -2551,9 +2656,11 @@ DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate # files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc and +# plantuml temporary files. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES diff --git a/build/doc/customdoxygen.css b/build/doc/customdoxygen.css index 40f9b1253d0..a11967f9616 100644 --- a/build/doc/customdoxygen.css +++ b/build/doc/customdoxygen.css @@ -1869,7 +1869,7 @@ u { } #titlearea { - background: #c53431; + background: #5E81AC; border: none; } @@ -2140,4 +2140,4 @@ div.line { padding-right: 12px; } -/* Custom ends HERE */ \ No newline at end of file +/* Custom ends HERE */ diff --git a/build/doc/logo.png b/build/doc/logo.png index 8a7615d6171..e178106cfbd 100644 Binary files a/build/doc/logo.png and b/build/doc/logo.png differ diff --git a/build/pimp.mak b/build/pimp.mak index db88c7458d4..afc96c862bf 100644 --- a/build/pimp.mak +++ b/build/pimp.mak @@ -1,15 +1,16 @@ # You gotta PIMP MY CALC ifndef NO_PIMP -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m ____ \n") -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m / __ \\____ ___ ___ ____ _____ _ \n") -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m / / / / __ \`__ \\/ _ \\/ __ \`/ __ \`/ \n") -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m / /_/ / / / / / / __/ /_/ / /_/ / \n") -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m \\____/_/ /_/ /_/\\___/\\__, /\\__,_/ \n") -PLS_IGNORE := $(shell >&2 printf "\e[91m\e[1m /____/ \n") -PLS_IGNORE := $(shell >&2 printf "\e[33m - Omega does what Epsilon't \e[0m\n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[36m __ __ ______ ____ ____ _____\n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[36m / / / /_____/ ,____/_/ / / __ \/'_ /\n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[36m / / / / __ '/ /____/ / / / / / / / / / \n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[36m / /_/ / /_/ /___, / / /__/ /_/ / / / / \n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[36m \____/ ____/______/_/____/\____/_/ /_/ \n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[36m / / \n") +PLS_IGNORE := $(shell >&2 printf "\e[91m\e[36m /_/ \n") +PLS_IGNORE := $(shell >&2 printf "\e[0;34m - When Epsiloff and Omegout \e[0m\n") PLS_IGNORE := $(shell >&2 printf "\n") -PLS_IGNORE := $(shell >&2 printf "\e[32mBuilding O$(OMEGA_VERSION)-E$(EPSILON_VERSION)\n") +PLS_IGNORE := $(shell >&2 printf "\e[32mBuilding U$(UPSILON_VERSION)-E$(EPSILON_VERSION)\n") ifeq (${PLATFORM},device) DISPLAY_TARGET = Numworks $(MODEL) @@ -21,7 +22,7 @@ endif DISPLAY_TARGET ?= "Undefined" -PLS_IGNORE := $(shell >&2 printf "\e[32m Targetting $(DISPLAY_TARGET)\n") +PLS_IGNORE := $(shell >&2 printf "\e[32m Targeting $(DISPLAY_TARGET)\n") ifeq ($(OS),Windows_NT) DISPLAY_OS = Windows diff --git a/build/platform.device.mak b/build/platform.device.mak index 34ca783d07f..a72e7aaafb6 100644 --- a/build/platform.device.mak +++ b/build/platform.device.mak @@ -1,4 +1,4 @@ -MODEL ?= n0110 +MODEL ?= bootloader USE_LIBA = 1 EXE = elf diff --git a/build/platform.simulator.3ds.mak b/build/platform.simulator.3ds.mak index d829e5e0a4a..dc35a659da1 100644 --- a/build/platform.simulator.3ds.mak +++ b/build/platform.simulator.3ds.mak @@ -1,5 +1,7 @@ TOOLCHAIN = devkitarm EXE = elf +EPSILON_TELEMETRY ?= 0 + HANDY_TARGETS_EXTENSIONS = 3dsx cia diff --git a/build/platform.simulator.fxcg.mak b/build/platform.simulator.fxcg.mak new file mode 100644 index 00000000000..fc30c12e842 --- /dev/null +++ b/build/platform.simulator.fxcg.mak @@ -0,0 +1,11 @@ +TOOLCHAIN = sh-elf-gcc +EXE = elf + +EPSILON_TELEMETRY ?= 0 + +HANDY_TARGETS_EXTENSIONS = g3a bin + +USE_LIBA = 0 +POINCARE_TREE_LOG = 0 + +SFLAGS := $(filter-out -fPIE, $(SFLAGS)) diff --git a/build/platform.simulator.ios.mak b/build/platform.simulator.ios.mak index b53ad508ad3..15ef783b1cb 100644 --- a/build/platform.simulator.ios.mak +++ b/build/platform.simulator.ios.mak @@ -3,7 +3,7 @@ EXE = bin APPLE_PLATFORM ?= ios APPLE_PLATFORM_MIN_VERSION = 8.0 -EPSILON_TELEMETRY ?= 1 +EPSILON_TELEMETRY ?= 0 ifeq ($(APPLE_PLATFORM),ios) ARCHS = arm64 armv7 diff --git a/build/platform.simulator.nspire.mak b/build/platform.simulator.nspire.mak new file mode 100644 index 00000000000..d158bbc09c9 --- /dev/null +++ b/build/platform.simulator.nspire.mak @@ -0,0 +1,11 @@ +TOOLCHAIN = nspire-gcc +EXE = elf + +EPSILON_TELEMETRY ?= 0 + +HANDY_TARGETS_EXTENSIONS = tns + +USE_LIBA = 0 +POINCARE_TREE_LOG = 0 + +SFLAGS := $(filter-out -fPIE, $(SFLAGS)) diff --git a/build/targets.device.bootloader.mak b/build/targets.device.bootloader.mak index 48466b778c3..030f95859e8 100644 --- a/build/targets.device.bootloader.mak +++ b/build/targets.device.bootloader.mak @@ -51,5 +51,7 @@ binpack: $(BUILD_DIR)/epsilon.onboarding.bin rm -rf $(BUILD_DIR)/binpack mkdir -p $(BUILD_DIR)/binpack cp $(BUILD_DIR)/epsilon.onboarding.bin $(BUILD_DIR)/binpack - cd $(BUILD_DIR) && for binary in epsilon.onboarding.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done + cp $(BUILD_DIR)/epsilon.onboarding.A.bin $(BUILD_DIR)/binpack + cp $(BUILD_DIR)/epsilon.onboarding.B.bin $(BUILD_DIR)/binpack + cd $(BUILD_DIR) && for binary in epsilon.onboarding*.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/* diff --git a/build/targets.device.mak b/build/targets.device.mak index 891b1d097ad..6a4af4c65a2 100644 --- a/build/targets.device.mak +++ b/build/targets.device.mak @@ -39,7 +39,7 @@ openocd: # The flasher target is defined here because otherwise $(%_src) has not been # fully filled -flasher_src = $(ion_src) $(ion_device_flasher_src) $(liba_src) $(kandinsky_src) +flasher_src = $(ion_src) $(ion_device_flasher_src) $(liba_src) $(simple_kandinsky_src) $(BUILD_DIR)/flasher.light.$(EXE): $(call flavored_object_for,$(flasher_src),light usbxip) $(BUILD_DIR)/flasher.verbose.$(EXE): $(call flavored_object_for,$(flasher_src),usbxip) $(BUILD_DIR)/flasher.verbose.flash.$(EXE): $(call flavored_object_for,$(flasher_src)) @@ -48,7 +48,7 @@ $(BUILD_DIR)/flasher.%.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/shared/ram.ld $(BUILD_DIR)/flasher.%.flash.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/$(MODEL)/internal_flash.ld #TODO Do not build all apps... Put elsewhere? -bench_src = $(ion_src) $(liba_src) $(kandinsky_src) $(poincare_src) $(libaxx_src) $(app_shared_src) $(ion_device_bench_src) +bench_src = $(ion_src) $(ion_device_bench_src) $(liba_src) $(simple_kandinsky_src) $(poincare_src) $(libaxx_src) $(app_shared_src) $(BUILD_DIR)/bench.ram.$(EXE): $(call flavored_object_for,$(bench_src),consoleuart usbxip) $(BUILD_DIR)/bench.ram.$(EXE): LDFLAGS += -Lion/src/$(PLATFORM)/bench $(BUILD_DIR)/bench.ram.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/shared/ram.ld diff --git a/build/targets.device.n0110.mak b/build/targets.device.n0110.mak index 687fb7a589f..fbd9a96fa8b 100644 --- a/build/targets.device.n0110.mak +++ b/build/targets.device.n0110.mak @@ -1,14 +1,14 @@ HANDY_TARGETS += test.external_flash.write test.external_flash.read bootloader $(BUILD_DIR)/test.external_flash.%.$(EXE): LDSCRIPT = ion/test/device/n0110/external_flash_tests.ld -test_external_flash_src = $(ion_src) $(liba_src) $(libaxx_src) $(kandinsky_src) $(poincare_src) $(ion_device_dfu_relogated_src) $(runner_src) +test_external_flash_src = $(ion_src) $(liba_src) $(libaxx_src) $(default_kandinsky_src) $(poincare_src) $(ion_device_dfu_relegated_src) $(runner_src) $(BUILD_DIR)/test.external_flash.read.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_read_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_read_src)) $(BUILD_DIR)/test.external_flash.write.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_write_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_write_src)) .PHONY: bootloader bootloader: $(BUILD_DIR)/bootloader.bin $(BUILD_DIR)/bootloader.$(EXE): $(call flavored_object_for,$(bootloader_src),usbxip) -$(BUILD_DIR)/bootloader.$(EXE): LDSCRIPT = ion/src/device/n0110/internal_flash.ld +$(BUILD_DIR)/bootloader.$(EXE): LDSCRIPT = ion/src/device/bootloader/internal_flash.ld .PHONY: %_flash %_flash: $(BUILD_DIR)/%.dfu diff --git a/build/targets.mak b/build/targets.mak index b570237f235..aa082ad908e 100644 --- a/build/targets.mak +++ b/build/targets.mak @@ -7,7 +7,7 @@ HANDY_TARGETS_EXTENSIONS ?= # Epsilon base target -base_src = $(ion_src) $(liba_src) $(kandinsky_src) $(escher_src) $(libaxx_src) $(poincare_src) $(python_src) +base_src = $(ion_src) $(liba_src) $(default_kandinsky_src) $(escher_src) $(libaxx_src) $(poincare_src) $(python_src) epsilon_src = $(base_src) $(apps_src) diff --git a/build/targets.simulator.3ds.mak b/build/targets.simulator.3ds.mak index d06cf52ad5d..23cbe0a3a77 100644 --- a/build/targets.simulator.3ds.mak +++ b/build/targets.simulator.3ds.mak @@ -1,7 +1,7 @@ %.smdh: ion/src/simulator/3ds/assets/logo.png $(Q) echo "SMDH $(notdir $@)" - $(Q) smdhtool --create "Epsilon" "A Numworks in your 3DS!" "Numworks" $< $@ + $(Q) smdhtool --create "Upsilon" "A Numworks in your 3DS!" "Numworks" $< $@ $(BUILD_DIR)/%.3dsx: $(BUILD_DIR)/%.elf $(BUILD_DIR)/%.smdh $(Q) echo "3DSX $(notdir $@)" diff --git a/build/targets.simulator.fxcg.mak b/build/targets.simulator.fxcg.mak new file mode 100644 index 00000000000..ec26dcc8174 --- /dev/null +++ b/build/targets.simulator.fxcg.mak @@ -0,0 +1,5 @@ +$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf + $(OBJCOPY) -O binary -R .bss -R .gint_bss $< $@ + +$(BUILD_DIR)/%.g3a: $(BUILD_DIR)/%.bin ion/src/simulator/fxcg/assets/icon-uns.png ion/src/simulator/fxcg/assets/icon-sel.png + $(FXGXA) --g3a --icon-uns=ion/src/simulator/fxcg/assets/icon-uns.png --icon-sel=ion/src/simulator/fxcg/assets/icon-sel.png -n Upsilon $< -o $@ diff --git a/build/targets.simulator.nspire.mak b/build/targets.simulator.nspire.mak new file mode 100644 index 00000000000..574bb4d24ef --- /dev/null +++ b/build/targets.simulator.nspire.mak @@ -0,0 +1,6 @@ +$(BUILD_DIR)/%.tns: $(BUILD_DIR)/%.elf +# comment one of these lines. For B&W old nspire, do not use the compress option + genzehn --compress --input $(BUILD_DIR)/epsilon.elf --output upsilon.tns --name "upsilon" --uses-lcd-blit true +# genzehn --input $(BUILD_DIR)/epsilon.elf --output upsilon.tns --name "upsilon" --uses-lcd-blit true + genzehn --info --input upsilon.tns + firebird-send upsilon.tns /ndless diff --git a/build/toolchain.devkitarm.mak b/build/toolchain.devkitarm.mak index 52fd7de7560..ce7b31e846f 100644 --- a/build/toolchain.devkitarm.mak +++ b/build/toolchain.devkitarm.mak @@ -31,7 +31,7 @@ LIBDIRS := $(DEVKITPRO)/libctru INCLUDE = $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ -I$(CURDIR)/$(BUILD) -CFLAGS += $(INCLUDE) -DARM11 -D_3DS +CFLAGS += $(INCLUDE) -DARM11 -D_3DS -D__3DS__ CXXFLAGS = $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 diff --git a/build/toolchain.emscripten.mak b/build/toolchain.emscripten.mak index aa85bed50f7..34aa5715d5b 100644 --- a/build/toolchain.emscripten.mak +++ b/build/toolchain.emscripten.mak @@ -12,6 +12,8 @@ EMFLAGS += -s SAFE_HEAP=1 EMFLAGS += -s STACK_OVERFLOW_CHECK=1 EMFLAGS += -s DEMANGLE_SUPPORT=1 EMFLAGS += -s MAIN_MODULE=1 +EMFLAGS += -g +EMFLAGS += -O3 else EMFLAGS += -s MAIN_MODULE=2 endif @@ -20,10 +22,11 @@ endif # Configure EMFLAGS EMFLAGS += -s WASM=1 -s SINGLE_FILE=1 +EMFLAGS += -Wno-unused-command-line-argument # Configure LDFLAGS EMSCRIPTEN_MODULARIZE ?= 1 LDFLAGS += -s MODULARIZE=$(EMSCRIPTEN_MODULARIZE) -s 'EXPORT_NAME="Epsilon"' --memory-init-file 0 SFLAGS += $(EMFLAGS) -LDFLAGS += $(EMFLAGS) -Oz -s EXPORTED_FUNCTIONS='["_main", "_IonSimulatorKeyboardKeyDown", "_IonSimulatorKeyboardKeyUp", "_IonSimulatorEventsPushEvent", "_IonSoftwareVersion", "_IonPatchLevel", "_IonDisplayForceRefresh"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["UTF8ToString"]' +LDFLAGS += $(EMFLAGS) -Oz -s EXPORTED_RUNTIME_METHODS='["UTF8ToString"]' -s EXPORTED_FUNCTIONS='["_main", "_IonSimulatorKeyboardKeyDown", "_IonSimulatorKeyboardKeyUp", "_IonSimulatorEventsPushEvent", "_IonSoftwareVersion", "_IonPatchLevel", "_IonDisplayForceRefresh"]' diff --git a/build/toolchain.nspire-gcc.mak b/build/toolchain.nspire-gcc.mak new file mode 100644 index 00000000000..fe8518942d9 --- /dev/null +++ b/build/toolchain.nspire-gcc.mak @@ -0,0 +1,9 @@ +CC = nspire-gcc +CXX = nspire-g++ +LD = nspire-g++ +GDB = gdb +OBJCOPY = nspire-objcopy +SIZE = nspire-size +AS = nspire-as + +SFLAGS += -DNSPIRE_NEWLIB -DSTRING_STORAGE diff --git a/build/toolchain.sh-elf-gcc.mak b/build/toolchain.sh-elf-gcc.mak new file mode 100644 index 00000000000..43432065c1b --- /dev/null +++ b/build/toolchain.sh-elf-gcc.mak @@ -0,0 +1,10 @@ +CC = sh-elf-gcc +CXX = sh-elf-g++ +LD = sh-elf-g++ +GDB = gdb +OBJCOPY = sh-elf-objcopy +SIZE = sh-elf-size +AS = sh-elf-as +FXGXA = fxgxa + +SFLAGS += -D_FXCG -D_BIG_ENDIAN -DSTRING_STORAGE diff --git a/build/utilities/translate.py b/build/utilities/translate.py new file mode 100755 index 00000000000..1bfc533e62b --- /dev/null +++ b/build/utilities/translate.py @@ -0,0 +1,436 @@ +#!/usr/bin/env python3 +"""Generate missing translations for i18n files.""" +# Build-in library imports +import os +import re +import sys +import argparse +from typing import Dict, List +# Deep translator import +try: + from deep_translator import GoogleTranslator +except ModuleNotFoundError: + print('Module deep_translator not found. Please install it with ', + '"pip3 install deep_translator"') + sys.exit(1) + + +# Initialize the parser +parser = argparse.ArgumentParser(description='Generate missing translations' + + ' for i18n files', epilog='You can execute' + + ' this script without arguments') +# Add arguments to the parser +parser.add_argument("--default-input-language", default="en", + help="The language code of the input language") +parser.add_argument("--base-path", default=".", + help="The base path of the i18n files") +parser.add_argument("--no-recursive", default=False, action="store_true", + help="Disable finding the i18n files recursively") +parser.add_argument("--dry-run", default=False, action="store_true", + help="Disable saving the i18n files") +# Parse command line arguments +args = parser.parse_args() + +# Save the user language into the global variable +DEFAULT_INPUT_LANGUAGE = args.default_input_language +# Save the base path into the global variable +BASE_PATH = args.base_path +# Save if i18n files can be found recursively +FIND_RECURSIVE = not args.no_recursive +# Save if dryrun is enabled +DRYRUN = args.dry_run + + +def translate(text: str, output_language: str, input_language: str = 'auto')\ + -> str: + """Translate the given text. + + Args: + text (str): The text to translate + output_language (str): The output language + input_language (str): The input language + + Returns: + str: The translated text + + """ + # Ensure of the text exists (not empty) + if not text: + return "" + # Initialize the translator + translator = GoogleTranslator(source=input_language, + target=output_language) + # Translate and return the translated text + return translator.translate(text) + + +def get_all_files_in_directory(directory: str) -> List[str]: + """Get all files in the given directory recursively. + + Args: + directory (str): The directory + + Returns: + Liststr]: The list of files in the directory + + """ + # Initialize the list of files + files = [] + # Iterate over all files in the directory + for i in os.listdir(directory): + # Add the current directory to the file name + i = directory + '/' + i + # If the file is a directory + if FIND_RECURSIVE and os.path.isdir(i): + # Iter the directory and add the files to the list + files.extend(get_all_files_in_directory(i)) + # Else the file is a file + else: + # Add the file to the list + files.append(i) + # Return the list of files + return files + + +def get_i18n_files(directory: str = '.') -> Dict[str, List[str]]: + """Get the list of i18n files in the given directory. + + Args: + directory (str, optional): The directory to find the i18n files. + Defaults to '.'. + + Returns: + Dict[List[str]]: The list of i18n files in a dictionary of languages. + + """ + # Get all files in the directory recursively + files = get_all_files_in_directory(directory) + # Initialize the dictionary + i18n_files_language: Dict[str, List[str]] = {} + # Iterate over all files in the directory + for i in files: + # If the file is an i18n file + if ".i18n" in i: + # Get the language of the file + file_language = i.split('.')[-2] + # If the dictionary already contains the language + if file_language in i18n_files_language: + # Append the file to the dictionary + i18n_files_language[file_language].append(i) + # Else add the language to the dictionary + else: + # Add the file to the dictionary + i18n_files_language[file_language] = [i] + # Return the dictionary + return i18n_files_language + + +def need_to_be_translated(keys: Dict[str, List[List[str]]])\ + -> Dict[str, List[List[str]]]: + """Return the key that needs to be translated by locale. + + Args: + keys (Dict[str, List[str]]): The keys of the i18n files + + Returns: + Dict[str, List[str]]: The keys that needs to be translated, + sorted by locale + + """ + # Initialize the list of keys + keys_list: List[List[str]] = [] + keys_to_translate: Dict[str, List[List[str]]] = {} + # Iterate over all locales + for value_ in keys.values(): + # Iterate on keys of the locale + for key in value_: + # Skip if the key is already in the list + if key[0] in keys_list: + continue + # Else add the key to the list + keys_list.append(key) + for locale, value in keys.items(): + # Initialize the list of keys in the locale + keys_in_locale: List[str] = [i[0] for i in value] + # Get the keys of keys that need to be translated + keys_to_translate_in_locale: List[List[str]] = [ + key for key in keys_list if key[0] not in keys_in_locale + ] + # Remove duplicates from the list + # Initialize the deduplicated list + keys_to_translate_in_locale_deduplicated: List[List[str]] = [] + # Iterate over the duplicated list + for item in keys_to_translate_in_locale: + # If the key is not in the deduplicated list, add it + if item not in keys_to_translate_in_locale_deduplicated: + keys_to_translate_in_locale_deduplicated.append(item) + # Else, ignore the key, because it is already in the list + # Save the deduplicated list into the dictionary + keys_to_translate[locale] = keys_to_translate_in_locale_deduplicated + return keys_to_translate + + +def get_keys_in_file(filename: str) -> List[List[str]]: + """Return a list of keys in the file. + + Args: + filename (str): The name of the file to read + + Returns: + List[str]: The keys in the file + + """ + # Initialize the list of keys in the file + keys = [] + # Open the file read only + with open(filename, 'r', encoding='utf-8') as file_read: + # Read the content of the file line by line + for line in file_read.readlines(): + # Ignore commented lines + if re.match(r"^#(.*)$", line): + continue + # Ignore lines without = + if '=' not in line: + continue + # Get the key by spliting the line by = + key = line.split("=")[0] + # Remove spaces from the start of the key + while key[0] == " ": + key = key[1:] + # Remove spaces from the end of the key + while key[-1] == " ": + key = key[:-1] + # Get generic filename into a list separated by dots + generic_filename_list = filename.split(".")[:-2] + # Get the filename as string with a trailing dot at the end + generic_filename = "".join(i + "." for i in generic_filename_list) + # Remove trailing dot from the end of the generic filename + generic_filename = generic_filename[:-1] + # Add the key and the generic filename to the list of keys + keys.append([key, generic_filename]) + return keys + + +def list_keys(i18n_files: Dict[str, List[str]]) -> Dict[str, List[List[str]]]: + """List all keys in the i18n files. + + Args: + i18n_files (Dict[str, List[str]]): I18n files list + + Returns: + Dict[str, List[str]]: The dictionnary of keys in the i18n files by + locale. + + """ + # Initialize the list of keys in the i18n files + keys_dict: Dict[str, List[List[str]]] = {} + # Iterate on the locales + for locale in i18n_files: + # Initialize the dictionary for the locale + keys_dict[locale] = [] + # Iterate on the locale's files + for actual_file in i18n_files[locale]: + # Get the keys in the file and add them to the list + keys_dict[locale].extend(get_keys_in_file(actual_file)) + # Return the dictionary of keys in the i18n files sorted by locale + return keys_dict + + +def get_value_from_key(key_to_find: str, filename_generic: str, locale: str)\ + -> str: + """Get the value of a key in the i18n files from the given locale. + + Args: + key (str): The key to fetch the value + filename_generic (str): The path to the file where the value will be + fetched + locale (str): The locale from the value will be retrieved + + Returns: + str: The value of the key from the given file + + """ + # Get the filename from the generic filename + file_path = filename_generic + '.' + locale + '.i18n' + # Exit if the file does not exist + if not os.path.exists(file_path): + return "" + # Open the file for reading + with open(file_path, 'r', encoding='utf-8') as file_read: + # Read the file line by line + for line in file_read.readlines(): + # Ignore commented lines + if re.match(r"^#(.*)$", line): + continue + # Ignore lines without = + if '=' not in line: + continue + # Get the key and value by spliting the line by = + key = line.split("=")[0] + value = "".join(i + " " for i in line.split("=")[1:]) + # print(key, value) + # Remove spaces from the start of the key + while key[0] == " ": + key = key[1:] + # Remove spaces from the end of the key + while key[-1] == " ": + key = key[:-1] + # Remove spaces from the start of the value + while value[0] == " ": + value = value[1:] + # Remove spaces from the end of the value + while value[-1] in [" ", "\n"]: + value = value[:-1] + # Remove the quotes from the value + value = value[1:][:-1] + if key == key_to_find: + # Add a space to the end of the value if the key is empty, + # because an empty value is parsed as a error + if not value: + value = " " + return value + # Return a empty string if no value were found because we can't use bools + # because it made that the linters crash + return "" + + +def translate_missing_keys(keys_to_translate: Dict[str, List[List[str]]], + input_language: str) -> Dict[str, List[List[str]]]: + """Get a dictionary of file with the keys and translations to add. + + Args: + keys_to_translate (Dict[str, List[List[str]]]): The list of keys to + translate + input_language (str): The language to get the text that will be + translated + + Returns: + Dict[str, List[str]]: The dictionary of files with translations + + """ + # Initialize the dictionary of translations + output: Dict[str, List[List[str]]] = {} + # Initialize the variable to store the number of translated keys + keys_translated: int = 0 + # Iterate over the locales of the dictionary of untranslated keys + for locale in keys_to_translate: + # Iterate over the untranslated keys in the locale + for key in keys_to_translate[locale]: + # Get the filename from the generic filename + filename = key[1] + '.' + locale + '.i18n' + # Get the value of the key + value = get_value_from_key(key[0], key[1], input_language) + # If the value was not found in the locale, try other languages + if not value: + print(f"Key '{key[0]}' not found in locale {input_language}") + print("Iterating over all known languages") + # Initialize the variable to store if a value was found to false + value_found = False + # Iterate over all the known languages + for language in keys_to_translate: + # Try to get the value from the language + value = get_value_from_key(key[0], key[1], language) + # If the value was found in the language, break + if value: + print(f"Value found in the language '{language}'") + # Mark the value as been found + value_found = True + # Save the language + input_language = language + break + # Else, continue + # Crash if the value is not available all the known languages + if not value_found: + raise ValueError(f"Key '{key[0]}' not found in all locales") + + # Restore the default value if a space have been + # inserted + if value == ' ': + value = '' + # Translate the value into the requested language + value_translated = translate(value, locale, input_language) + # If the file is already in the dictionary, append the translation + # to it + if filename in output: + output[filename].append([key[0], value_translated]) + # Else create the list for the file in the dictionary + else: + output[filename] = [[key[0], value_translated]] + # Print the key and value + print(key[0] + ' = "' + value_translated + '"') + # Increment the number of translated keys + keys_translated += 1 + # If no keys were translated, print a specific message + if not keys_translated: + print('No keys were translated') + # Else, if only one key was translated, print a specific message + elif keys_translated == 1: + print('One key was translated') + # Else, print a generic message + else: + print(f'{keys_translated} keys were translated') + # Return the dictionary of translations + return output + + +def save_translations(missing_keys_translation: Dict[str, List[List[str]]]): + """Save the translations. + + Args: + missing_keys_translation (Dict[str, List[List[str]]]): The dictionary + of translations + + """ + # Read the dictionary of files + for filename, keys_for_file in missing_keys_translation.items(): + # Initialize add new line variable to false + add_new_line = False + # Open the file for reading only if it exists + if os.path.exists(filename): + # Open the file for reading + with open(filename, 'r', encoding='utf-8') as file_read: + # Save content of the file into the content variable and + # ensure sure there is a first character (not a empty file) + if content := str(file_read.read()): + # If the last char isn't a newline + if content[-1] != '\n': + # Mark that a newline should be added + add_new_line = True + # Else, print of the file will be created + else: + print(f"{filename} is not found, creating an empty file") + # Open the file for writing + with open(filename, 'a', encoding='utf-8') as file_write: + # If add new line is true, add a new line at the end of the file + if add_new_line: + file_write.write('\n') + # Iterate on the values to be save in the file + for value in keys_for_file: + # Generate the in the style Key = "Value" + line = value[0] + ' = "' + value[1] + '"' + # Write the line at the end of the file + file_write.write(line + '\n') + + +def main(): + """Run the program.""" + # Get the list of i18n files + i18n_files = get_i18n_files(BASE_PATH) + # Get the list of keys in the i18n files + keys = list_keys(i18n_files) + # Delete universals keys from the list of keys + if 'universal' in keys: + del keys['universal'] + # Get the list of keys to translate in the i18n files + keys_to_translate = need_to_be_translated(keys) + # Translate the missings keys + missing_keys_translation = translate_missing_keys(keys_to_translate, + DEFAULT_INPUT_LANGUAGE) + # Save the translations + if not DRYRUN: + save_translations(missing_keys_translation) + else: + print("Dry run enabled, translations aren't saved") + + +main() diff --git a/build/utilities/translations_clean.py b/build/utilities/translations_clean.py new file mode 100644 index 00000000000..afee202faaa --- /dev/null +++ b/build/utilities/translations_clean.py @@ -0,0 +1,363 @@ +#!/usr/bin/env python3 +"""Remove all unused translations to save flash space.""" +# Builtins imports +import os +import re +import argparse + + +# Initialize the parser +parser = argparse.ArgumentParser( + description="Remove unused translations from the i18n file to save flash \ + space.", epilog="You can use this script without arguments" +) +# Add arguments to the parser +parser.add_argument("--base-path", default=".", + help="The base path of the files") +parser.add_argument("--no-recursive", default=False, action="store_true", + help="Disable finding the files recursively") +parser.add_argument("--dry-run", default=False, action="store_true", + help="Disable saving the i18n files") +# Parse command line arguments +args = parser.parse_args() + +# Initialize the settings +BASE_PATH = args.base_path +FIND_RECURSIVE = not args.no_recursive +DRYRUN = args.dry_run +# Don't add / at the end of the path +IGNORE_PATHS = [ + "./output", "./.git", "./.mypy_cache", + "./ion/src/simulator/external/sdl", +] +IGNORE_PATHS_CONTENTS = [ + "__pycache__", ".png", ".esc", ".ttf", ".ico", ".jpg", ".jar", ".icn", + ".bnr", ".i18n", ".cache" +] + +# If the key contain something that's in this list, keep it to prevent code breaking +KEEP_KEY_IF_CONTAIN = ["Language", "Country"] + + +def get_all_files_in_directory(directory: str)\ + -> list[str]: + """Get all files in the given directory recursively. + + Args: + directory (str): The directory + + Returns: + List[str]: The list of files in the directory + + """ + # Initialize the list of files + files = [] + # Iterate over all files in the directory + for i in os.listdir(directory): + # Add the current directory to the file name + i = f'{directory}/{i}' + # If the file is a directory + if FIND_RECURSIVE and os.path.isdir(i): + # Iter the directory and add the files to the list + files.extend(get_all_files_in_directory(i)) + # Else the file is a file + else: + # Add the file to the list + files.append(i) + # Return the list of files + return files + + +def get_files(directory: str) -> list[str]: + """Get all files in the directory recursively with ignoring list. + + Args: + directory (str): The start directory + + Returns: + List[str]: The list of files + """ + # Initialize the list of files + files = [] + # Iterate over the directory + for filename in os.listdir(directory): + # Add the full path to the directory/file + filename = f'{directory}/{filename}' + # Exclude path in IGNORE_PATHS + if filename in IGNORE_PATHS: + continue + # Exclude paths in IGNORE_PATHS_CONTENTS + if any(extension in filename for extension in IGNORE_PATHS_CONTENTS): + continue + # If it is a directory, find recursively the files into it + if os.path.isdir(filename): + files.extend(get_files(filename)) + else: + files.append(filename) + # Return the file list + return files + + +def get_keys_in_i18n_file(filename: str) -> list[list[str]]: + """Return a list of keys in the file. + + Args: + filename (str): The name of the file to read + + Returns: + List[list[str]]: The keys in the file + + """ + # Initialize the list of keys in the file + keys: list[list[str]] = [] + # Open the file read only + with open(filename, 'r', encoding='utf-8') as file_read: + # Read the content of the file line by line + for line in file_read.readlines(): + # Ignore commented lines + if re.match(r"^#(.*)$", line): + continue + # Ignore lines without = + if '=' not in line: + continue + # Get the key by spliting the line by = + key = line.split("=")[0] + # Remove spaces from the start of the key + while key[0] == " ": + key = key[1:] + # Remove spaces from the end of the key + while key[-1] == " ": + key = key[:-1] + # Get generic filename into a list separated by dots + generic_filename_list = filename.split(".")[:-2] + # Get if the locale is universal + locale = filename.split(".")[-2] + # Get the filename as string with a trailing dot at the end + generic_filename = "".join(f'{i}.' for i in generic_filename_list) + # Remove trailing dot from the end of the generic filename + generic_filename = generic_filename[:-1] + # Add the key and the generic filename to the list of keys + keys.append([key, generic_filename, locale]) + return keys + + +def list_keys_in_i18n_file_list(i18n_files: list[str]) -> list[list[str]]: + """List all keys in the i18n files. + + Args: + i18n_files (list[str]): I18n files list + + Returns: + List[list[str]]: The dictionnary of keys in the i18n files by + locale. + + """ + # Initialize the list of keys in the i18n files + keys_dict: list[list[str]] = [] + # Iterate on the file list + for actual_file in i18n_files: + # Get the keys in the file and add them to the list + keys_dict.extend(get_keys_in_i18n_file(actual_file)) + # Return the dictionary of keys in the i18n files sorted by locale + return keys_dict + + +def extract_keys_from_line(line: str) -> list[str]: + """Extract keys from a line. + + Args: + line (str): The line to extract keys from + + Returns: + list[str]: The extracted keys + """ + # Initialize the list of separator to separate the key from the part + keys_separator: list[str] = ["}", ":", ";", ",", " ", ")"] + # Initialize the list of keys + keys: list[str] = [] + # Split the line by :: + line_splitted: list[str] = line.split("::") + # Initialize loop variables + # The last part (used inside the loop) + last_part: str = "" + # Mark the next line contain the key + save_next_line: bool = False + # The key part + key_parts: list[str] = [] + # Iterate over the splitted line + for part in line_splitted: + # print(part) + if save_next_line: + key_parts.append(part) + save_next_line = False + # If the actual part is Message and the last part contain I18n + # (I18n::Message), the next part will contain the key name + # TODO: Improve catching + if part == "Message" and "I18n" in last_part: + save_next_line = True + # Save the current part into the last part + # (loop iteration is finished) + last_part = part + # Get the key from the key part + # TODO: Improve catching + # Iterate over all the keys in the line + for actual_key in key_parts: + # Initialize real key variable + key_real: str = actual_key + for separator_to_test in keys_separator: + key_separated: list[str] = key_real.split(separator_to_test) + # print(key_real, separator_to_test, key_separated) + # If the key was splitted, save the separated key + if len(key_separated) > 1: + key_real = key_separated[0] + if key_real: + keys.append(key_real) + return keys + + +def keys_from_file_list(files: list[str]) -> list[str]: + """Get an array of keys from files. + + Args: + files (list[str]): The list of files to read + + Returns: + list[str]: The keys + """ + # Initialize the list of keys from the files + keys: list[str] = [] + # Iterate over the file list + for filename in files: + # Read the file contents into "content" variable + with open(filename, 'r', encoding="utf-8") as file_obj: + # Store the contents of the file + content = file_obj.read() + # Iterate over the file contents + for line in content.split("\n"): + # Get if the line contains an I18n key + if "I18n::Message" in line: + # Extract the keys from the line + keys.extend(extract_keys_from_line(line)) + return keys + + +def get_i18n_files(directory: str = '.') -> list[str]: + """Get the list of i18n files in the given directory. + + Args: + directory (str, optional): The directory to find the i18n files. + Defaults to '.'. + + Returns: + list[str]: The list of i18n files in a dictionary of languages. + + """ + # Get all files in the directory recursively + files = get_all_files_in_directory(directory) + # Return only i18n files + return [i for i in files if ".i18n" in i] + + +def get_unused_keys(file_keys: list[str], i18n_keys: list[list[str]]) -> list[list[str]]: + """Get unused keys. + + Args: + file_keys (list[str]): The keys in the source files + i18n_keys (list[list[str]]): The keys in the i18n files + + Returns: + list[list[str]]: The unused keys + """ + # Initialize the list of unused keys + unused_keys: list[list[str]] = [] + # Iterate over the i18n key definitions + for key_i18n_actual in i18n_keys: + # Get if the key is used, and mark it as used if it is in the kepping + # list + key_used = next( + ( + True + for string_to_test in KEEP_KEY_IF_CONTAIN + if string_to_test in key_i18n_actual[0] + ), + any(key_i18n_actual[0] == file_key for file_key in file_keys), + ) + + # If the key is not used, add it to the list + if not key_used: + if key_i18n_actual not in unused_keys: + print(f"{key_i18n_actual[0]} unused") + unused_keys.append(key_i18n_actual) + return unused_keys + + +def remove_keys_from_i18n_files(unused_keys: list[list[str]]): + """Remove unused keys from i18n files. + + Args: + unused_keys (list[list[str]]): The list of keys to remove + """ + # Initialize the dictionary of files + # (to prevent intensive writing to disk) + files_to_write: dict[str, str] = {} + # Iterate over the keys to remove + for key in unused_keys: + key_name_to_remove = key[0] + filename_generic = key[1] + locale = key[2] + # Get the filename of from the generic filename + filename = f'{filename_generic}.{locale}.i18n' + # If the file is not in the dictionary, add it + if filename not in files_to_write: + # Save the file contents + with open(filename, 'r', encoding='utf8') as file_read: + files_to_write[filename] = file_read.read() + # Split the file by new lines + file_splitted = files_to_write[filename].split("\n") + # Iterate over the file contents + for line, value in enumerate(file_splitted): + # Ignore lines without = + if '=' not in value: + continue + # Get the key from the line + key_to_check: str = value.split("=")[0] + # Remove spaces from the start of the key + while key_to_check[0] == " ": + key_to_check = key_to_check[1:] + # Remove spaces from the end of the key + while key_to_check[-1] == " ": + key_to_check = key_to_check[:-1] + # If the key is the key to remove, remove it + if key_to_check == key_name_to_remove: + del file_splitted[line] + break + file_str = "".join(line + "\n" for line in file_splitted) + # Remove double line return + while file_str[-2:] == '\n\n': + file_str = file_str[:-1] + files_to_write[filename] = file_str + # When the loop is end, write the files + for actual_file, content_to_write in files_to_write.items(): + with open(actual_file, 'w', encoding='utf-8') as file_to_write: + file_to_write.write(content_to_write) + # print(actual_file, content_to_write) + + +def main(): + """Execute the program.""" + # Get the file list + file_list = get_files(BASE_PATH) + # Get the keys in the file list + files_keys = keys_from_file_list(file_list) + # Get i18n files list + i18n_files = get_i18n_files(BASE_PATH) + # Get keys from i18n files + i18n_files_keys = list_keys_in_i18n_file_list(i18n_files) + # Get unused keys + unused_keys = get_unused_keys(files_keys, i18n_files_keys) + # If dryrun is disabled, remove the keys definitions from the i18n files + if not DRYRUN: + remove_keys_from_i18n_files(unused_keys) + + +main() diff --git a/docs/architecture.svg b/docs/architecture.svg index 8a848854e92..59c37af9f3a 100644 --- a/docs/architecture.svg +++ b/docs/architecture.svg @@ -22,7 +22,7 @@ - Esher + Escher diff --git a/docs/build/index.md b/docs/build/index.md index 9827314db9b..dc3976b5a92 100644 --- a/docs/build/index.md +++ b/docs/build/index.md @@ -2,73 +2,326 @@ title: Installing the SDK breadcrumb: SDK --- -# Build and run your own version of Epsilon +# Build and run your own version of Upsilon -## Install the SDK +### Manual -### Windows + *You can refer to this [website](https://www.numworks.com/resources/engineering/software/build/) for the first step if you get errors.* -We recommend using the [Msys2](https://www.msys2.org/) environment to install most of the required tools. We support Windows 7 and up. Once Msys2 has been installed, launch the Msys2 terminal application, and enter the following commands + +### 1. Install SDK + +
+ +
+ +1.1 Linux + +
+ +
+ +Debian or Ubuntu + +
+ +You just have to install dependencies by running these command with superuser privileges in a Terminal: + +```bash +apt-get install build-essential git imagemagick libx11-dev libxext-dev libfreetype6-dev libpng-dev libjpeg-dev pkg-config gcc-arm-none-eabi binutils-arm-none-eabi ``` -pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config mingw-w64-x86_64-libusb git make python -echo "export PATH=/mingw64/bin:$PATH" >> .bashrc + +And there you can go to step 2! + +
+ +
+ +
+ +Fedora + +
+ +To install basics dev tools: + +```bash +dnf install make automake gcc gcc-c++ kernel-devel ``` -Last but not least, download and install the latest [GCC toolchain from ARM](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads). When prompted for an install location, choose `C:\msys64\home\User\gcc-arm\`. You'll then need to add this folder to your $PATH in Msys2 by running this command: `echo "export PATH=$PATH:$HOME/gcc-arm/bin" >> .bashrc` and restarting Msys2. +And then install required packages. -### macOS +```bash +install git ImageMagick libX11-devel libXext-devel freetype-devel libpng-devel libjpeg-devel pkg-config +``` -We recommend using [Homebrew](https://brew.sh) to install all dependencies. Once you have installed Homebrew, install all the dependencies with the following command: +Then, install GCC cross compiler for ARM. +```bash + dnf install arm-none-eabi-gcc-cs arm-none-eabi-gcc-cs-c++ ``` + +
+ +
+ +
+ +
+ +
+ +1.2 Mac + +
+ +It's recommended to use [Homebrew](https://brew.sh/). Once it's installed, just run: + +```bash brew install numworks/tap/epsilon-sdk ``` -### Debian or Ubuntu +and it will install all dependencies. + +
+ +And there you can go to step 2! + +
+ +
+ +
-Most of the required tools are available as apt packages: +1.3 Windows +
+ +
+ +With Msys2/Mingw (officialized by numworks but with a lot of bugs) + +[Msys2](https://www.msys2.org/) environment is recommended by Numworks to get most of the required tools on Windows easily. It's where you'll paste all the commands of this tutorial. Once it'sinstalled, paste these commands into the Msys2 terminal. + +```bash +pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config mingw-w64-x86_64-libusb git make python +echo "export PATH=/mingw64/bin:$PATH" >> .bashrc ``` -apt-get install build-essential git imagemagick libx11-dev libxext-dev libfreetype6-dev libpng-dev libjpeg-dev pkg-config + +Next, you'll need to install the [GCC toolchain for ARM](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads). When prompted for aninstall location, choose `C:\msys64\home\User\gcc-arm\`. You'll then need to add this folder to your $PATH. Just enter: + +```bash +echo "export PATH=$PATH:$HOME/gcc-arm/bin" >> .bashrc ``` +Just restart terminal and you can go to step 2! + +
+ +
+ +With WSL 2 -You'll also need to install the latest version of GCC and make it available in your $PATH: +You need a windows version >= 1903. -1. Download the [GCC toolchain distributed by ARM](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads). You should obtain a `gcc-arm-none-eabi-x-linux.tar.bz2` file. -2. Decompress that file with `tar xvfj gcc-arm-none-eabi-*-linux.tar.bz2` -3. Add the resulting folder to your $PATH. If you use bash, ``echo "export PATH=\$PATH:`find $(pwd)/gcc-arm-none-eabi-*-update/bin -type d`" >> ~/.bashrc`` should do what you need (you'll need to restart your terminal afterwards). +#### WSL Installation -Alternatively, on Debian 10 and later you can directly install a sufficiently modern cross-toolchain: +1. Use simultaneously win + X keys and then click on "admin powershell". +```powershell +dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux all /norestart ``` -apt-get install gcc-arm-none-eabi binutils-arm-none-eabi +This command activate WSL functionalities. + +```powershell +dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart ``` +This one allows virtual machines developed by Microsoft. + +2. Restart your computer. + +3. Download [this file](https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi) and follow instructions. -## Retrieve the source code +4. Now open powershell admin like before and type: +```powershell +wsl --set-default-version 2 + ``` +5. Download [Ubuntu](https://www.microsoft.com/store/apps/9n6svws3rx71) from Microsoft store. -The code is hosted on GitHub. You can retrieve it using the follwing command. +WSL is now installed. +### Usbipd installation to connect your calculator +If you want to connect to the calculator, you have to connect to install this [tool](https://github.com/dorssel/usbipd-win/releases/download/v1.3.0/usbipd-win_1.3.0.msi). This will allow you toconnect WSL to the calculator through internet. Follow the on screen information to install. +#### Ubuntu +1. In a WSL Ubuntu command prompt, type: +```bash +sudo apt install linux-tools-5.4.0-77-generic hwdata ``` -git clone https://github.com/numworks/epsilon.git +2. Edit /etc/sudoers so that root can find the usbip command. On Ubuntu, run this command. +```bash +sudo visudo ``` +3. Add `/usr/lib/linux-tools/5.4.0-77-generic` to the beginning of secure_path. After editing, the line should look similar to this. +`Defaults secure_path="/usr/lib/linux-tools/5.4.0-77-generic:/usr/local/sbin:..."` -## Run Epsilon on your computer +#### Debian -Once the SDK has been installed, just open your terminal (Msys2, Terminal.app, xterm…) and type the following commands: +1. If you use debian for your WSL distro, use this command instead: +```bash +sudo apt install usbip hwdata usbutils +``` +And that's all for installation and set up. +### To connect your calculator +1. Open an Admin powershell and type: +```powershell + usbipd wsl list ``` -make PLATFORM=simulator clean -make PLATFORM=simulator epsilon_run +This will list your usb devices connected. Look at the BUSID column and remember the one for your calculator (it should be called "Numworks Calculator"). +2. Now run this command replacing by your calculator's usb port id: +```powershell +usbipd wsl attach --busid ``` +It will ask you to type your wsl's password and will connect your calculator to WSL. + +You can now go to step 2! + +
+ +
+ +
+ -## Run Epsilon on your calculator +### 2. Set up repo -You can also update your NumWorks calculator easily. Note that you'll need to press the Reset button and that all data on your calculator will be lost. +Clone repo and use 'upsilon-dev' branch by pasting these two commands: + +```bash +git clone --recursive https://github.com/Lauryy06/Upsilon.git +cd Upsilon +git checkout upsilon-dev +``` +
+ + +### 3. Choose the target + + +
+ +Model n0100 + +(note: you can change the `EPSILON_I18N=en` flag to `fr`, `nl`, `pt`, `it`, `de`, `es` or `hu`). + +```bash +make MODEL=n0100 clean +make MODEL=n0100 EPSILON_I18N=en OMEGA_USERNAME="{Your name, max 15 characters}" -j4 ``` + +Now, run either: + +```bash +make MODEL=n0100 epsilon_flash +``` +to directly flash the calculator after pressing simultaneously `reset` and `6` buttons and plugging in. + +
+ +or: + +```bash +make MODEL=n0100 OMEGA_USERNAME="" binpack -j4 +``` +to make binpack which you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0100/). Binpacks are a great way to share a custom build of Upsilonto friends. + +
+ +
+ +Model n0110 + + +```bash make clean -make +make OMEGA_USERNAME="{Your name, max 15 characters}" -j4 +``` + +Now, run either: + +```bash make epsilon_flash ``` +to directly flash the calculator after pressing simultaneously `reset` and `6` buttons and plugging in. + +
+ +or: + +```bash +make OMEGA_USERNAME="" binpack -j4 +``` +to make binpack witch you can flash to the calculator from [Ti-planet's webDFU](https://ti-planet.github.io/webdfu_numworks/n0110/). Binpacks are a great way to share a custom build of Upsilon to friends. + +
+ +
+ +Web simulator + +First, install emsdk : + +```bash +git clone https://github.com/emscripten-core/emsdk.git +cd emsdk +./emsdk install latest-fastcomp +./emsdk activate latest-fastcomp +source emsdk_env.sh +``` + +Then, compile Upsilon : + +```bash +make clean +make PLATFORM=simulator TARGET=web OMEGA_USERNAME="{Your name, max 15 characters}" -j4 +``` + +The simulator is now in `output/release/simulator/web/simulator.zip` + +
+ +
+ +3DS Simulator + +You need devkitPro and devkitARM installed and in your path (instructions [here](https://devkitpro.org/wiki/Getting_Started)) + +```bash +git clone --recursive https://github.com/Lauryy06/Upsilon.git +cd Upsilon +git checkout --recursive upsilon-dev +make PLATFORM=simulator TARGET=3ds -j +``` +You can then put epsilon.3dsx on a SD card to run it from the HBC or use 3dslink to launch it over the network: + +```bash +3dslink output/release/simulator/3ds/epsilon.3dsx -a <3DS' IP ADDRESS> +``` + +
+ +
+ +Important: Don't forget the `--recursive` tag, because Upsilon relies on submodules. +Also, you can change the number of processes that run in parallel during the build by changing the value of the `-j` flag. +Don't forget to put your pseudo instead of `{your pseudo, max 15 char}`. If you don't want one, just remove the `OMEGA_USERNAME=""` argument. + +
+ +Congratulations, you're running your very own version of Upsilon! + +To build with a special theme, please refer to this [page](../../themes/README.md). + +If you need help, you can join our Discord server here : https://discord.gg/NFvzdCBTQn -Congratulations, you're running your very own version of Epsilon! +

Omega Banner Discord

diff --git a/docs/index.md b/docs/index.md index edc12868821..e5418a9e388 100644 --- a/docs/index.md +++ b/docs/index.md @@ -45,7 +45,7 @@ We're listing here all the topics you should be familiar with before being able The choice of a programming language is a controversial topic. Not all of them can be used to write an operating system, but quite a few can. We settled on C++ for several reasons: - It is a [system](https://en.wikipedia.org/wiki/System_programming_language) programming language, which is something we need since we have to write some low-level code. -- It has excellent tooling: several extremly high-quality compilers +- It has excellent tooling: several extremely high-quality compilers - It is used for several high-profile projects LLVM, WebKit, MySQL, Photoshop… This ensures a strong ecosystem of tools, code and documentation. - It easily allows Object-Oriented Programming, which is a convenient abstraction. @@ -57,7 +57,7 @@ If you want to contribute to Epsilon, you'll need to learn some C++. ### Working with limited memory -Our device has 256 KB of RAM. That's very little memory by today's standards. That being said, by writing code carefuly, a huge lot can be achieved in that space. After all, that's 64 times more memory than the computer of the Apollo mission! +Our device has 256 KB of RAM. That's very little memory by today's standards. That being said, by writing code carefully, a huge lot can be achieved in that space. After all, that's 64 times more memory than the computer of the Apollo mission! #### Stack memory diff --git a/escher/Makefile b/escher/Makefile index a8bb8491cc2..ca371fd33f7 100644 --- a/escher/Makefile +++ b/escher/Makefile @@ -21,6 +21,7 @@ endif escher_src += $(addprefix escher/src/,\ alternate_empty_view_controller.cpp \ + animation_timer.cpp \ app.cpp \ background_view.cpp \ bank_view_controller.cpp \ @@ -84,6 +85,7 @@ escher_src += $(addprefix escher/src/,\ selectable_table_view.cpp \ simple_list_view_data_source.cpp \ simple_table_view_data_source.cpp \ + slideable_message_text_view.cpp \ solid_color_view.cpp \ stack_view.cpp \ stack_view_controller.cpp \ diff --git a/escher/image/inliner.c b/escher/image/inliner.c index c2e9d489448..df31430dfd2 100644 --- a/escher/image/inliner.c +++ b/escher/image/inliner.c @@ -4,7 +4,7 @@ * * The inliner creates a .h and a .cpp file in the same directory as the input * file. The implementation file declares an Image in the ImageStore namespace, - * and the header exposes a pointer to this variable. The Image embedds the + * and the header exposes a pointer to this variable. The Image embedded the * bitmap data in the RGB565 format. */ #include diff --git a/escher/include/escher.h b/escher/include/escher.h index 62ceb7e1afc..1112a4d6ecf 100644 --- a/escher/include/escher.h +++ b/escher/include/escher.h @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include #include @@ -66,6 +68,7 @@ #include #include #include +#include #include #include #include @@ -83,6 +86,7 @@ #include #include #include +#include #include #include #include diff --git a/escher/include/escher/animated.h b/escher/include/escher/animated.h new file mode 100644 index 00000000000..ca92b4e3e9f --- /dev/null +++ b/escher/include/escher/animated.h @@ -0,0 +1,11 @@ +#ifndef APPS_ANIMATED_H +#define APPS_ANIMATED_H + +class Animated { +public: + virtual void willStartAnimation() {}; + virtual void didStopAnimation() {}; + virtual void animate() = 0; +}; + +#endif diff --git a/escher/include/escher/animation_timer.h b/escher/include/escher/animation_timer.h new file mode 100644 index 00000000000..9bb1b1f1bf7 --- /dev/null +++ b/escher/include/escher/animation_timer.h @@ -0,0 +1,25 @@ +#ifndef APPS_ANIMATION_TIMER_H +#define APPS_ANIMATION_TIMER_H + +#include +#include +#include + +class AnimationTimer : public Timer { +public: + AnimationTimer(): + Timer(1), + m_animated(nullptr) + {} + void setAnimated(Animated * animated); + void removeAnimated(Animated * animated=nullptr); +private: + bool fire() override { + assert(m_animated); + m_animated->animate(); + return true; + } + Animated * m_animated; +}; + +#endif diff --git a/escher/include/escher/app.h b/escher/include/escher/app.h index ab146713a7a..ce3883bef15 100644 --- a/escher/include/escher/app.h +++ b/escher/include/escher/app.h @@ -69,8 +69,6 @@ class App : public Responder { virtual void didBecomeActive(Window * window); virtual void willBecomeInactive(); View * modalView(); - virtual int numberOfTimers() { return 0; } - virtual Timer * timerAtIndex(int i) { assert(false); return nullptr; } virtual Poincare::Context * localContext() { return nullptr; } protected: App(Snapshot * snapshot, ViewController * rootViewController, I18n::Message warningMessage = (I18n::Message)0) : diff --git a/escher/include/escher/container.h b/escher/include/escher/container.h index 0070adb0285..02dbcd1a995 100644 --- a/escher/include/escher/container.h +++ b/escher/include/escher/container.h @@ -36,10 +36,6 @@ class Container : public RunLoop { static App * s_activeApp; private: void step(); - int numberOfTimers() override; - Timer * timerAtIndex(int i) override; - virtual int numberOfContainerTimers(); - virtual Timer * containerTimerAtIndex(int i); }; #endif diff --git a/escher/include/escher/input_event_handler.h b/escher/include/escher/input_event_handler.h index b5fed665fc5..72f444cad5b 100644 --- a/escher/include/escher/input_event_handler.h +++ b/escher/include/escher/input_event_handler.h @@ -10,7 +10,7 @@ class InputEventHandlerDelegate; class InputEventHandler { public: InputEventHandler(InputEventHandlerDelegate * inputEventHandlerdelegate) : m_inputEventHandlerDelegate(inputEventHandlerdelegate) {} - virtual bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) { return false; } + virtual bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false, bool shouldRemoveLastCharacter = false) { return false; } protected: bool handleBoxEvent(Ion::Events::Event event); InputEventHandlerDelegate * m_inputEventHandlerDelegate; diff --git a/escher/include/escher/input_view_controller.h b/escher/include/escher/input_view_controller.h index 5d0bd919706..2b89f6901e6 100644 --- a/escher/include/escher/input_view_controller.h +++ b/escher/include/escher/input_view_controller.h @@ -27,12 +27,14 @@ class InputViewController : public ModalViewController, InputEventHandlerDelegat void abortEditionAndDismiss(); /* TextFieldDelegate */ + void textFieldDidReceiveNoneXNTEvent() override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; bool textFieldDidAbortEditing(TextField * textField) override; /* LayoutFieldDelegate */ + void layoutFieldDidReceiveNoneXNTEvent() override; bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) override; bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) override; bool layoutFieldDidFinishEditing(LayoutField * layoutField, Poincare::Layout layoutR, Ion::Events::Event event) override; diff --git a/escher/include/escher/layout_field.h b/escher/include/escher/layout_field.h index bd36e8b14a3..3d6a4c76a65 100644 --- a/escher/include/escher/layout_field.h +++ b/escher/include/escher/layout_field.h @@ -45,7 +45,7 @@ class LayoutField : public ScrollableView, public ScrollViewDataSource, public E } /* Responder */ - bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; + bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false, bool shouldRemoveLastCharacter = false) override; bool handleEvent(Ion::Events::Event event) override; // TODO: factorize with TextField (see TODO of EditableField) bool shouldFinishEditing(Ion::Events::Event event) override; diff --git a/escher/include/escher/layout_field_delegate.h b/escher/include/escher/layout_field_delegate.h index ce9dfaf02ff..080cfc47c0b 100644 --- a/escher/include/escher/layout_field_delegate.h +++ b/escher/include/escher/layout_field_delegate.h @@ -9,6 +9,7 @@ class LayoutField; class LayoutFieldDelegate : public ContextProvider{ public: + virtual void layoutFieldDidReceiveNoneXNTEvent() {}; virtual bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) = 0; virtual bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) = 0; virtual bool layoutFieldDidFinishEditing(LayoutField * layoutField, Poincare::Layout layoutR, Ion::Events::Event event) { return false; } diff --git a/escher/include/escher/message_table_cell.h b/escher/include/escher/message_table_cell.h index c43985d6dfa..65638e851ae 100644 --- a/escher/include/escher/message_table_cell.h +++ b/escher/include/escher/message_table_cell.h @@ -2,9 +2,11 @@ #define ESCHER_MESSAGE_TABLE_CELL_H #include +#include #include #include +template class MessageTableCell : public TableCell { public: MessageTableCell(I18n::Message label = (I18n::Message)0, const KDFont * font = KDFont::SmallFont, Layout layout = Layout::HorizontalLeftOverlap); @@ -17,7 +19,7 @@ class MessageTableCell : public TableCell { protected: KDColor backgroundColor() const override { return m_backgroundColor; } private: - MessageTextView m_messageTextView; + T m_messageTextView; KDColor m_backgroundColor; }; diff --git a/escher/include/escher/message_table_cell_with_buffer.h b/escher/include/escher/message_table_cell_with_buffer.h index 64866cce267..2b40e26be86 100644 --- a/escher/include/escher/message_table_cell_with_buffer.h +++ b/escher/include/escher/message_table_cell_with_buffer.h @@ -4,7 +4,7 @@ #include #include -class MessageTableCellWithBuffer : public MessageTableCell { +class MessageTableCellWithBuffer : public MessageTableCell<> { public: MessageTableCellWithBuffer(I18n::Message message = (I18n::Message)0, const KDFont * font = KDFont::SmallFont, const KDFont * accessoryFont = KDFont::LargeFont, KDColor accessoryTextColor = Palette::PrimaryText); View * accessoryView() const override; diff --git a/escher/include/escher/message_table_cell_with_chevron.h b/escher/include/escher/message_table_cell_with_chevron.h index 2611346d491..d29b68ea664 100644 --- a/escher/include/escher/message_table_cell_with_chevron.h +++ b/escher/include/escher/message_table_cell_with_chevron.h @@ -4,7 +4,8 @@ #include #include -class MessageTableCellWithChevron : public MessageTableCell { +template +class MessageTableCellWithChevron : public MessageTableCell { public: MessageTableCellWithChevron(I18n::Message message = (I18n::Message)0, const KDFont * font = KDFont::SmallFont); View * accessoryView() const override; diff --git a/escher/include/escher/message_table_cell_with_chevron_and_buffer.h b/escher/include/escher/message_table_cell_with_chevron_and_buffer.h index 86657ba92ee..1f952076ca1 100644 --- a/escher/include/escher/message_table_cell_with_chevron_and_buffer.h +++ b/escher/include/escher/message_table_cell_with_chevron_and_buffer.h @@ -4,7 +4,7 @@ #include #include -class MessageTableCellWithChevronAndBuffer : public MessageTableCellWithChevron { +class MessageTableCellWithChevronAndBuffer : public MessageTableCellWithChevron<> { public: MessageTableCellWithChevronAndBuffer(const KDFont * labelFont = KDFont::SmallFont, const KDFont * subAccessoryFont = KDFont::SmallFont); View * subAccessoryView() const override; diff --git a/escher/include/escher/message_table_cell_with_chevron_and_expression.h b/escher/include/escher/message_table_cell_with_chevron_and_expression.h index f94cac89bca..72b4f205934 100644 --- a/escher/include/escher/message_table_cell_with_chevron_and_expression.h +++ b/escher/include/escher/message_table_cell_with_chevron_and_expression.h @@ -4,7 +4,7 @@ #include #include -class MessageTableCellWithChevronAndExpression : public MessageTableCellWithChevron { +class MessageTableCellWithChevronAndExpression : public MessageTableCellWithChevron<> { public: MessageTableCellWithChevronAndExpression(I18n::Message message = (I18n::Message)0, const KDFont * font = KDFont::SmallFont); View * subAccessoryView() const override; diff --git a/escher/include/escher/message_table_cell_with_chevron_and_message.h b/escher/include/escher/message_table_cell_with_chevron_and_message.h index 976dc69ada6..041b49df324 100644 --- a/escher/include/escher/message_table_cell_with_chevron_and_message.h +++ b/escher/include/escher/message_table_cell_with_chevron_and_message.h @@ -3,7 +3,7 @@ #include -class MessageTableCellWithChevronAndMessage : public MessageTableCellWithChevron { +class MessageTableCellWithChevronAndMessage : public MessageTableCellWithChevron<> { public: MessageTableCellWithChevronAndMessage(const KDFont * labelFont = KDFont::SmallFont, const KDFont * contentFont = KDFont::SmallFont); View * subAccessoryView() const override; diff --git a/escher/include/escher/message_table_cell_with_editable_text.h b/escher/include/escher/message_table_cell_with_editable_text.h index 54e0e19c65c..ceb16e7ea1f 100644 --- a/escher/include/escher/message_table_cell_with_editable_text.h +++ b/escher/include/escher/message_table_cell_with_editable_text.h @@ -6,7 +6,7 @@ #include #include -class MessageTableCellWithEditableText : public Responder, public MessageTableCell { +class MessageTableCellWithEditableText : public Responder, public MessageTableCell<> { public: MessageTableCellWithEditableText(Responder * parentResponder = nullptr, InputEventHandlerDelegate * inputEventHandlerDelegate = nullptr, TextFieldDelegate * textFieldDelegate = nullptr, I18n::Message message = (I18n::Message)0); View * accessoryView() const override; diff --git a/escher/include/escher/message_table_cell_with_expression.h b/escher/include/escher/message_table_cell_with_expression.h index c01d8af76e3..c5778393af5 100644 --- a/escher/include/escher/message_table_cell_with_expression.h +++ b/escher/include/escher/message_table_cell_with_expression.h @@ -4,9 +4,9 @@ #include #include -class MessageTableCellWithExpression : public MessageTableCell { +class MessageTableCellWithExpression : public MessageTableCell<> { public: - MessageTableCellWithExpression(I18n::Message message = (I18n::Message)0, const KDFont * font = KDFont::SmallFont); + MessageTableCellWithExpression(I18n::Message message = (I18n::Message)0, const KDFont * font = KDFont::LargeFont); View * accessoryView() const override; void setHighlighted(bool highlight) override; void setLayout(Poincare::Layout layout); diff --git a/escher/include/escher/message_table_cell_with_gauge.h b/escher/include/escher/message_table_cell_with_gauge.h index 8dfddf59911..527ce948e29 100644 --- a/escher/include/escher/message_table_cell_with_gauge.h +++ b/escher/include/escher/message_table_cell_with_gauge.h @@ -4,7 +4,7 @@ #include #include -class MessageTableCellWithGauge : public MessageTableCell { +class MessageTableCellWithGauge : public MessageTableCell<> { public: MessageTableCellWithGauge(I18n::Message message = (I18n::Message)0, const KDFont * font = KDFont::SmallFont); View * accessoryView() const override; diff --git a/escher/include/escher/message_table_cell_with_message.h b/escher/include/escher/message_table_cell_with_message.h index 02257f96e6b..243b3be9224 100644 --- a/escher/include/escher/message_table_cell_with_message.h +++ b/escher/include/escher/message_table_cell_with_message.h @@ -3,9 +3,10 @@ #include -class MessageTableCellWithMessage : public MessageTableCell { +template +class MessageTableCellWithMessage : public MessageTableCell { public: - MessageTableCellWithMessage(I18n::Message message = (I18n::Message)0, Layout layout = Layout::Vertical); + MessageTableCellWithMessage(I18n::Message message = (I18n::Message)0, TableCell::Layout layout = TableCell::Layout::Vertical); View * accessoryView() const override; void setHighlighted(bool highlight) override; void setAccessoryMessage(I18n::Message textBody); diff --git a/escher/include/escher/message_table_cell_with_switch.h b/escher/include/escher/message_table_cell_with_switch.h index 78e27ef07a0..1a334872a1b 100644 --- a/escher/include/escher/message_table_cell_with_switch.h +++ b/escher/include/escher/message_table_cell_with_switch.h @@ -4,7 +4,7 @@ #include #include -class MessageTableCellWithSwitch : public MessageTableCell { +class MessageTableCellWithSwitch : public MessageTableCell<> { public: MessageTableCellWithSwitch(I18n::Message message = (I18n::Message)0, const KDFont * font = KDFont::SmallFont); View * accessoryView() const override; diff --git a/escher/include/escher/metric.h b/escher/include/escher/metric.h index f514cb68c0a..0f19f221eda 100644 --- a/escher/include/escher/metric.h +++ b/escher/include/escher/metric.h @@ -23,7 +23,7 @@ class Metric { constexpr static KDCoordinate ScrollStep = 10; constexpr static KDCoordinate PopUpLeftMargin = 27; constexpr static KDCoordinate PopUpRightMargin = 27; - constexpr static KDCoordinate PopUpTopMargin = 50; + constexpr static KDCoordinate PopUpTopMargin = 15; constexpr static KDCoordinate ExamPopUpTopMargin = 27; constexpr static KDCoordinate ExamPopUpBottomMargin = 55; constexpr static KDCoordinate StoreRowHeight = 50; diff --git a/escher/include/escher/nested_menu_controller.h b/escher/include/escher/nested_menu_controller.h index 44ac41eca47..41333398f4c 100644 --- a/escher/include/escher/nested_menu_controller.h +++ b/escher/include/escher/nested_menu_controller.h @@ -41,7 +41,7 @@ class NestedMenuController : public StackViewController, public ListViewDataSour int depth() const; void resetStack(); private: - constexpr static int k_maxModelTreeDepth = 3; + constexpr static int k_maxModelTreeDepth = 4; State m_statesStack[k_maxModelTreeDepth]; }; @@ -65,7 +65,8 @@ class NestedMenuController : public StackViewController, public ListViewDataSour bool handleEventForRow(Ion::Events::Event event, int selectedRow); virtual bool selectSubMenu(int selectedRow); virtual bool returnToPreviousMenu(); - virtual bool selectLeaf(int selectedRow) = 0; + virtual bool selectLeaf(int selectedRow, bool quitToolbox) = 0; + virtual bool canStayInMenu() { return false; }; virtual int stackRowOffset() const { return 0; } InputEventHandler * sender() { return m_sender; } virtual HighlightCell * leafCellAtIndex(int index) = 0; diff --git a/escher/include/escher/run_loop.h b/escher/include/escher/run_loop.h index 2ef4b9207d2..26ddc0ad60b 100644 --- a/escher/include/escher/run_loop.h +++ b/escher/include/escher/run_loop.h @@ -9,13 +9,14 @@ class RunLoop { RunLoop(); void run(); void runWhile(bool (*callback)(void * ctx), void * ctx); + void addTimer(Timer * timer); + void removeTimer(Timer * timer); protected: virtual bool dispatchEvent(Ion::Events::Event e) = 0; - virtual int numberOfTimers(); - virtual Timer * timerAtIndex(int i); private: bool step(); int m_time; + Timer * m_firstTimer; }; #endif diff --git a/escher/include/escher/slideable_message_text_view.h b/escher/include/escher/slideable_message_text_view.h new file mode 100644 index 00000000000..fa9ae4e561a --- /dev/null +++ b/escher/include/escher/slideable_message_text_view.h @@ -0,0 +1,26 @@ + +#ifndef ESCHER_SLIDEABLE_MESSAGE_TEXT_VIEW_H +#define ESCHER_SLIDEABLE_MESSAGE_TEXT_VIEW_H + +#include +#include + +class SlideableMessageTextView : public MessageTextView, public Animated { +public: + SlideableMessageTextView(const KDFont * font = KDFont::LargeFont, I18n::Message message = (I18n::Message)0, float horizontalAlignment = 0.0f, float verticalAlignment = 0.0f, + KDColor textColor = Palette::PrimaryText, KDColor backgroundColor = Palette::ListCellBackground); + void willStartAnimation() override; + void didStopAnimation() override; + void animate() override; + + /* TextView */ + void drawRect(KDContext * ctx, KDRect rect) const override; + +private: + static constexpr uint8_t k_numberOfSpaces = 3; + KDCoordinate m_textOffset; + bool m_goingLeft; // true if we are going left, false if we are going right + bool m_paused; +}; + +#endif diff --git a/escher/include/escher/text_area.h b/escher/include/escher/text_area.h index e8aa34aa514..110268b3f94 100644 --- a/escher/include/escher/text_area.h +++ b/escher/include/escher/text_area.h @@ -16,7 +16,7 @@ class TextArea : public TextInput, public InputEventHandler { TextArea(Responder * parentResponder, View * contentView, const KDFont * font = KDFont::LargeFont); void setDelegates(InputEventHandlerDelegate * inputEventHandlerDelegate, TextAreaDelegate * delegate) { m_inputEventHandlerDelegate = inputEventHandlerDelegate; m_delegate = delegate; } bool handleEvent(Ion::Events::Event event) override; - bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; + bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false, bool shouldRemoveLastCharacter = false) override; void setText(char * textBuffer, size_t textBufferSize); protected: @@ -60,7 +60,7 @@ class TextArea : public TextInput, public InputEventHandler { class Position { /* column and line correspond to the visual column and line. The glyph at * the kth column is not the the glyph of kth code point, because of - * combining code points that do not fave a personnal glyph. */ + * combining code points that do not fave a personal glyph. */ public: Position(int column, int line) : m_column(column), m_line(line) {} int column() const { return m_column; } @@ -111,7 +111,7 @@ class TextArea : public TextInput, public InputEventHandler { m_cursorLocation = m_text.text(); } void drawRect(KDContext * ctx, KDRect rect) const override; - void drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor) const; + void drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor, bool isItalic = false) const; virtual void drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn, const char * selectionStart, const char * selectionEnd) const = 0; virtual void clearRect(KDContext * ctx, KDRect rect) const = 0; KDSize minimalSizeForOptimalDisplay() const override; @@ -120,7 +120,8 @@ class TextArea : public TextInput, public InputEventHandler { const char * editedText() const override { return m_text.text(); } size_t editedTextLength() const override { return m_text.textLength(); } const Text * getText() const { return &m_text; } - bool insertTextAtLocation(const char * text, char * location, int textLength = -1) override; + bool isAbleToInsertTextAt(int textLength, const char * location, bool shouldRemoveLastCharacter) const override; + void insertTextAtLocation(const char * text, char * location, int textLength = -1) override; void moveCursorGeo(int deltaX, int deltaY); bool removePreviousGlyph() override; bool removeEndOfLine() override; diff --git a/escher/include/escher/text_area_delegate.h b/escher/include/escher/text_area_delegate.h index c16c5ffa051..9c907a98746 100644 --- a/escher/include/escher/text_area_delegate.h +++ b/escher/include/escher/text_area_delegate.h @@ -5,6 +5,7 @@ class TextArea; class TextAreaDelegate { public: + virtual void textAreaDidReceiveNoneXNTEvent() {}; virtual bool textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) = 0; }; diff --git a/escher/include/escher/text_field.h b/escher/include/escher/text_field.h index f7901b7f8af..055c7ed3be1 100644 --- a/escher/include/escher/text_field.h +++ b/escher/include/escher/text_field.h @@ -34,7 +34,7 @@ class TextField : public TextInput, public EditableField { void setText(const char * text); void setEditing(bool isEditing) override { m_contentView.setEditing(isEditing); } CodePoint XNTCodePoint(CodePoint defaultXNTCodePoint) override; - bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; + bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false, bool shouldRemoveLastCharacter = false) override; bool handleEvent(Ion::Events::Event event) override; constexpr static int maxBufferSize() { return ContentView::k_maxBufferSize; @@ -71,10 +71,11 @@ class TextField : public TextInput, public EditableField { void reinitDraftTextBuffer(); void setDraftTextBufferSize(size_t size) { assert(size <= k_maxBufferSize); m_draftTextBufferSize = size; } size_t draftTextBufferSize() const { return m_draftTextBufferSize; } + bool isAbleToInsertTextAt(int textLength, const char * location, bool shouldRemoveLastCharacter) const override; /* If the text to be appended is too long to be added without overflowing the * buffer, nothing is done (not even adding few letters from the text to reach * the maximum buffer capacity) and false is returned. */ - bool insertTextAtLocation(const char * text, char * location, int textLength = -1) override; + void insertTextAtLocation(const char * text, char * location, int textLength = -1) override; KDSize minimalSizeForOptimalDisplay() const override; bool removePreviousGlyph() override; bool removeEndOfLine() override; diff --git a/escher/include/escher/text_field_delegate.h b/escher/include/escher/text_field_delegate.h index 4be1741e68a..790cc75e9f8 100644 --- a/escher/include/escher/text_field_delegate.h +++ b/escher/include/escher/text_field_delegate.h @@ -7,6 +7,7 @@ class TextField; class TextFieldDelegate { public: + virtual void textFieldDidReceiveNoneXNTEvent() {}; virtual bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) = 0; virtual bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) = 0; virtual bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { return false; } diff --git a/escher/include/escher/text_input.h b/escher/include/escher/text_input.h index 0de69538cc3..96648be9630 100644 --- a/escher/include/escher/text_input.h +++ b/escher/include/escher/text_input.h @@ -49,7 +49,8 @@ class TextInput : public ScrollableView, public ScrollViewDataSource { // Virtual text get/add/remove virtual const char * text() const = 0; - virtual bool insertTextAtLocation(const char * text, char * location, int textLength = -1) = 0; + virtual bool isAbleToInsertTextAt(int textLength, const char * location, bool shouldRemoveLastCharacter) const = 0; + virtual void insertTextAtLocation(const char * text, char * location, int textLength) = 0; virtual bool removePreviousGlyph() = 0; virtual bool removeEndOfLine() = 0; @@ -92,7 +93,7 @@ class TextInput : public ScrollableView, public ScrollViewDataSource { /* If the text to be appended is too long to be added without overflowing the * buffer, nothing is done (not even adding few letters from the text to reach * the maximum buffer capacity) and false is returned. */ - bool insertTextAtLocation(const char * textBuffer, char * location); + void insertTextAtLocation(const char * textBuffer, char * location, int textLength); bool removeEndOfLine(); ContentView * contentView() { return const_cast(nonEditableContentView()); diff --git a/escher/include/escher/timer.h b/escher/include/escher/timer.h index bc2a06088c6..7c9f4cb16df 100644 --- a/escher/include/escher/timer.h +++ b/escher/include/escher/timer.h @@ -3,26 +3,24 @@ #include -/* Timers we'll need - * - Blink cursor timer - * - Dim Screen timer - * - Power down timer - * - Watchdog timer ? - * - Battery level timer - * - LED blink timer +/** + * A timer that can be used to schedule events. + * We organize timers in a linked list. */ - class Timer { public: static constexpr int TickDuration = 300; // In Miliseconds Timer(uint32_t period); // Period is in ticks bool tick(); - void reset(); + void reset(uint32_t NewPeriod = -1); + void setNext(Timer * next) { m_next = next; } + Timer * next() { return m_next; } protected: virtual bool fire() = 0; -private: uint32_t m_period; uint32_t m_numberOfTicksBeforeFire; +private: + Timer * m_next; }; #endif diff --git a/escher/include/escher/timer_manager.h b/escher/include/escher/timer_manager.h new file mode 100644 index 00000000000..c7e92cfec04 --- /dev/null +++ b/escher/include/escher/timer_manager.h @@ -0,0 +1,13 @@ +#ifndef ESCHER_TIMER_MANAGER_H +#define ESCHER_TIMER_MANAGER_H + +#include + +class TimerManager { +public: + // We need this file to not create dependece between apps and escher. + static void AddTimer(Timer * timer); + static void RemoveTimer(Timer * timer); +}; + +#endif diff --git a/escher/include/escher/toolbox.h b/escher/include/escher/toolbox.h index f128f54ebb9..9a9ff09642c 100644 --- a/escher/include/escher/toolbox.h +++ b/escher/include/escher/toolbox.h @@ -28,8 +28,8 @@ class Toolbox : public NestedMenuController { /* indexAfterFork is called when a fork-node is encountered to choose which * of its children should be selected, based on external context. */ virtual int indexAfterFork() const { assert(false); return 0; }; - MessageTableCellWithMessage * leafCellAtIndex(int index) override = 0; - MessageTableCellWithChevron * nodeCellAtIndex(int index) override = 0; + MessageTableCellWithMessage * leafCellAtIndex(int index) override = 0; + MessageTableCellWithChevron * nodeCellAtIndex(int index) override = 0; mutable const ToolboxMessageTree * m_messageTreeModel; /* m_messageTreeModel points at the messageTree of the tree (describing the * whole model) where we are located. It enables to know which rows are leaves diff --git a/escher/include/escher/toolbox_message_tree.h b/escher/include/escher/toolbox_message_tree.h index ba8ac5005a3..f99db10627d 100644 --- a/escher/include/escher/toolbox_message_tree.h +++ b/escher/include/escher/toolbox_message_tree.h @@ -5,14 +5,16 @@ class ToolboxMessageTree : public MessageTree { public: - constexpr static ToolboxMessageTree Leaf(I18n::Message label, I18n::Message text = (I18n::Message)0, bool stripInsertedText = true, I18n::Message insertedText = (I18n::Message)0) { + constexpr static ToolboxMessageTree Leaf(I18n::Message label, I18n::Message text = (I18n::Message)0, bool stripInsertedText = true, I18n::Message insertedText = (I18n::Message)0, bool multiLine = false, uint8_t numberOfLines = 1) { return ToolboxMessageTree( label, text, (insertedText == (I18n::Message)0) ? label : insertedText, static_cast(0), 0, - stripInsertedText); + stripInsertedText, + multiLine, + numberOfLines); }; template constexpr static ToolboxMessageTree Node(I18n::Message label, const ToolboxMessageTree (&children)[N], bool fork = false) { @@ -41,22 +43,28 @@ class ToolboxMessageTree : public MessageTree { I18n::Message insertedText() const { return m_insertedText; } bool stripInsertedText() const { return m_stripInsertedText; } bool isFork() const { return numberOfChildren() < 0; } + bool isMultiLine() const { return m_multiLine; } + uint8_t numberOfLines() const { return m_numberOfLines; } private: - constexpr ToolboxMessageTree(I18n::Message label, I18n::Message text, I18n::Message insertedText, const ToolboxMessageTree * children, int numberOfChildren, bool stripInsertedText) : + constexpr ToolboxMessageTree(I18n::Message label, I18n::Message text, I18n::Message insertedText, const ToolboxMessageTree * children, int numberOfChildren, bool stripInsertedText, bool multiLine = false, uint8_t numberOfLines = 1) : MessageTree(label, numberOfChildren), m_children(children), m_text(text), m_insertedText(insertedText), m_stripInsertedText(stripInsertedText), - m_childrenConsecutive(true) + m_childrenConsecutive(true), + m_multiLine(multiLine), + m_numberOfLines(numberOfLines) {} - constexpr ToolboxMessageTree(I18n::Message label, I18n::Message text, I18n::Message insertedText, const ToolboxMessageTree ** children, int numberOfChildren, bool stripInsertedText) : + constexpr ToolboxMessageTree(I18n::Message label, I18n::Message text, I18n::Message insertedText, const ToolboxMessageTree ** children, int numberOfChildren, bool stripInsertedText, bool multiLine = false, uint8_t numberOfLines = 1) : MessageTree(label, numberOfChildren), m_children(children), m_text(text), m_insertedText(insertedText), m_stripInsertedText(stripInsertedText), - m_childrenConsecutive(false) + m_childrenConsecutive(false), + m_multiLine(multiLine), + m_numberOfLines(numberOfLines) {} union Children { @@ -71,6 +79,8 @@ class ToolboxMessageTree : public MessageTree { I18n::Message m_insertedText; bool m_stripInsertedText; const bool m_childrenConsecutive; + bool m_multiLine; + uint8_t m_numberOfLines; }; #endif diff --git a/escher/include/escher/view_controller.h b/escher/include/escher/view_controller.h index 60243a8e038..ffee1f1a01a 100644 --- a/escher/include/escher/view_controller.h +++ b/escher/include/escher/view_controller.h @@ -8,7 +8,7 @@ extern "C" { #include } -/* ViewControllers are reponsible for +/* ViewControllers are responsible for * - Building the view hierarchy * - Handling user input * @@ -23,7 +23,7 @@ extern "C" { * - willExitResponderChain * - willResignFirstResponder * - * Both methods are always called after setting a view and laying its subwiews + * Both methods are always called after setting a view and laying its subviews * out. * * The method initView is called before setting a View (or often sets itself) diff --git a/escher/src/alternate_empty_view_controller.cpp b/escher/src/alternate_empty_view_controller.cpp index be61f67f2e3..2d64ae2d79e 100644 --- a/escher/src/alternate_empty_view_controller.cpp +++ b/escher/src/alternate_empty_view_controller.cpp @@ -57,8 +57,7 @@ const char * AlternateEmptyViewController::title() { bool AlternateEmptyViewController::handleEvent(Ion::Events::Event event) { if (m_contentView.alternateEmptyViewDelegate()->isEmpty()) { if (event != Ion::Events::Home && event != Ion::Events::OnOff) { - m_contentView.alternateEmptyViewDelegate()->defaultController()->handleEvent(Ion::Events::Back); - return true; + return m_contentView.alternateEmptyViewDelegate()->defaultController()->handleEvent(Ion::Events::Back); } return false; } diff --git a/escher/src/animation_timer.cpp b/escher/src/animation_timer.cpp new file mode 100644 index 00000000000..017589895e2 --- /dev/null +++ b/escher/src/animation_timer.cpp @@ -0,0 +1,16 @@ +#include +#include +#include + + +void AnimationTimer::setAnimated(Animated * animated) { + m_animated = animated; + TimerManager::AddTimer(this); +} + +void AnimationTimer::removeAnimated(Animated * animated) { + if (m_animated == animated || animated == nullptr) { + m_animated = nullptr; + TimerManager::RemoveTimer(this); + } +} diff --git a/escher/src/button_row_controller.cpp b/escher/src/button_row_controller.cpp index 021c0003813..94a2e67f930 100644 --- a/escher/src/button_row_controller.cpp +++ b/escher/src/button_row_controller.cpp @@ -37,7 +37,7 @@ int ButtonRowController::ContentView::numberOfSubviews() const { } View * ButtonRowController::ContentView::subviewAtIndex(int index) { - /* Warning: the order of the subviews is important for drity tracking. + /* Warning: the order of the subviews is important for dirty tracking. * Indeed, when a child is redrawn, the redrawn area is the smallest * rectangle unioniong the dirty rectangle and the previous redrawn area. * As the main view is more likely to be bigger, we prefer to set it as the @@ -217,7 +217,7 @@ void ButtonRowController::initView() { void ButtonRowController::viewWillAppear() { /* We need to layout subviews at first appearance because the number of - * buttons might have changed between 2 appearences. */ + * buttons might have changed between 2 appearances. */ m_contentView.layoutSubviews(); m_contentView.mainViewController()->viewWillAppear(); } diff --git a/escher/src/container.cpp b/escher/src/container.cpp index a307e4cfc94..731ad5d0f12 100644 --- a/escher/src/container.cpp +++ b/escher/src/container.cpp @@ -55,23 +55,3 @@ void Container::run() { window()->redraw(); RunLoop::run(); } - -int Container::numberOfTimers() { - return s_activeApp->numberOfTimers() + numberOfContainerTimers(); -} - -Timer * Container::timerAtIndex(int i) { - if (i < s_activeApp->numberOfTimers()) { - return s_activeApp->timerAtIndex(i); - } - return containerTimerAtIndex(i-s_activeApp->numberOfTimers()); -} - -int Container::numberOfContainerTimers() { - return 0; -} - -Timer * Container::containerTimerAtIndex(int i) { - assert(false); - return nullptr; -} diff --git a/escher/src/icon_view.cpp b/escher/src/icon_view.cpp index 0637d62c67d..1c1db37fecc 100644 --- a/escher/src/icon_view.cpp +++ b/escher/src/icon_view.cpp @@ -1,6 +1,7 @@ #include extern "C" { #include +#include } #include #include @@ -43,9 +44,16 @@ void IconView::drawRect(KDContext * ctx, KDRect rect) const { iconBufferSize * sizeof(KDColor) ); + // If we are on a big-endian CPU, we need to swap the bytes + #if _BIG_ENDIAN + for (uint32_t i = 0; i < iconBufferSize; i++) { + pixelBuffer[i] = KDColor::RGB16(__builtin_bswap16(pixelBuffer[i])); + } + #endif + //We push the first 6 lines of the image so that they are truncated on the sides ctx->fillRectWithPixels(KDRect(6, 0, m_frame.width()-12, 1),pixelBuffer+6, nullptr); - ctx->fillRectWithPixels(KDRect(4, 1, m_frame.width()-8, 1),pixelBuffer+6+55, nullptr); + ctx->fillRectWithPixels(KDRect(4, 1, m_frame.width()-8, 1),pixelBuffer+4+55, nullptr); ctx->fillRectWithPixels(KDRect(3, 2, m_frame.width()-6, 1),pixelBuffer+3+(2*55), nullptr); ctx->fillRectWithPixels(KDRect(2, 3, m_frame.width()-4, 1),pixelBuffer+2+(3*55), nullptr); ctx->fillRectWithPixels(KDRect(1, 4, m_frame.width()-2, 1),pixelBuffer+1+(4*55), nullptr); @@ -54,7 +62,7 @@ void IconView::drawRect(KDContext * ctx, KDRect rect) const { //Then we push the rectangular part of the image ctx->fillRectWithPixels(KDRect(0, 6, m_frame.width(), 44),pixelBuffer+(6*55), nullptr); - //Finaly we push the last 5 lines of the image so that they are truncated on the sides + //Finally we push the last 5 lines of the image so that they are truncated on the sides ctx->fillRectWithPixels(KDRect(1, 50, m_frame.width()-2, 1),pixelBuffer+1+(50*55), nullptr); ctx->fillRectWithPixels(KDRect(1, 51, m_frame.width()-2, 1),pixelBuffer+1+(51*55), nullptr); ctx->fillRectWithPixels(KDRect(2, 52, m_frame.width()-4, 1),pixelBuffer+2+(52*55), nullptr); diff --git a/escher/src/image_view.cpp b/escher/src/image_view.cpp index 7cb4ee39e1c..5062ef3f7a9 100644 --- a/escher/src/image_view.cpp +++ b/escher/src/image_view.cpp @@ -1,6 +1,7 @@ #include extern "C" { #include +#include } #include @@ -50,6 +51,13 @@ void ImageView::drawRect(KDContext * ctx, KDRect rect) const { pixelBufferSize * sizeof(KDColor) ); + // If we are on a big-endian CPU, we need to swap the bytes + #if _BIG_ENDIAN + for (uint32_t i = 0; i < pixelBufferSize; i++) { + pixelBuffer[i] = KDColor::RGB16(__builtin_bswap16(pixelBuffer[i])); + } + #endif + ctx->fillRectWithPixels(bounds(), pixelBuffer, nullptr); } diff --git a/escher/src/input_view_controller.cpp b/escher/src/input_view_controller.cpp index 2947054a7df..ada1a0c4498 100644 --- a/escher/src/input_view_controller.cpp +++ b/escher/src/input_view_controller.cpp @@ -58,6 +58,14 @@ bool InputViewController::textFieldDidAbortEditing(TextField * textField) { return true; } +void InputViewController::textFieldDidReceiveNoneXNTEvent() { + m_textFieldDelegate->textFieldDidReceiveNoneXNTEvent(); +} + +void InputViewController::layoutFieldDidReceiveNoneXNTEvent() { + m_layoutFieldDelegate->layoutFieldDidReceiveNoneXNTEvent(); +} + bool InputViewController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) { return m_textFieldDelegate->textFieldDidReceiveEvent(textField, event); } diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index cfdbea00cae..8fd02835805 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -355,7 +355,7 @@ void LayoutField::reload(KDSize previousSize) { markRectAsDirty(bounds()); } -bool LayoutField::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) { +bool LayoutField::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText, bool shouldRemoveLastCharacter) { /* The text here can be: * - the result of a key pressed, such as "," or "cos(•)" * - the text added after a toolbox selection @@ -417,6 +417,9 @@ bool LayoutField::handleEventWithText(const char * text, bool indentation, bool if (currentNumberOfLayouts + resultLayout.numberOfDescendants(true) >= k_maxNumberOfLayouts) { return true; } + if (shouldRemoveLastCharacter) { + cursor->performBackspace(); + } insertLayoutAtCursor(resultLayout, resultExpression, forceCursorRightOfText); } return true; @@ -431,6 +434,9 @@ bool LayoutField::shouldFinishEditing(Ion::Events::Event event) { } bool LayoutField::handleEvent(Ion::Events::Event event) { + if (m_delegate && event != Ion::Events::XNT) { + m_delegate->layoutFieldDidReceiveNoneXNTEvent(); + } bool didHandleEvent = false; KDSize previousSize = minimalSizeForOptimalDisplay(); bool shouldRecomputeLayout = m_contentView.cursor()->showEmptyLayoutIfNeeded(); diff --git a/escher/src/message_table_cell.cpp b/escher/src/message_table_cell.cpp index 648ef906873..af52d0098ac 100644 --- a/escher/src/message_table_cell.cpp +++ b/escher/src/message_table_cell.cpp @@ -1,39 +1,65 @@ #include #include +#include #include -MessageTableCell::MessageTableCell(I18n::Message label, const KDFont * font, Layout layout) : +template +MessageTableCell::MessageTableCell(I18n::Message label, const KDFont * font, Layout layout) : TableCell(layout), m_messageTextView(font, label, 0, 0.5, Palette::PrimaryText, Palette::ListCellBackground), m_backgroundColor(KDColorWhite) { } -View * MessageTableCell::labelView() const { +template +View * MessageTableCell::labelView() const { return (View *)&m_messageTextView; } -void MessageTableCell::setHighlighted(bool highlight) { +template<> +void MessageTableCell::setHighlighted(bool highlight) { HighlightCell::setHighlighted(highlight); KDColor backgroundColor = highlight? Palette::ListCellBackgroundSelected : Palette::ListCellBackground; m_messageTextView.setBackgroundColor(backgroundColor); + static AnimationTimer s_animationTimer = AnimationTimer(); + if (highlight) { + m_messageTextView.willStartAnimation(); + s_animationTimer.setAnimated(&m_messageTextView); + } else { + s_animationTimer.removeAnimated(&m_messageTextView); + m_messageTextView.didStopAnimation(); + } } -void MessageTableCell::setMessage(I18n::Message text) { +template<> +void MessageTableCell::setHighlighted(bool highlight) { + HighlightCell::setHighlighted(highlight); + KDColor backgroundColor = highlight? Palette::ListCellBackgroundSelected : Palette::ListCellBackground; + m_messageTextView.setBackgroundColor(backgroundColor); +} + +template +void MessageTableCell::setMessage(I18n::Message text) { m_messageTextView.setMessage(text); layoutSubviews(); } -void MessageTableCell::setTextColor(KDColor color) { +template +void MessageTableCell::setTextColor(KDColor color) { m_messageTextView.setTextColor(color); } -void MessageTableCell::setMessageFont(const KDFont * font) { +template +void MessageTableCell::setMessageFont(const KDFont * font) { m_messageTextView.setFont(font); layoutSubviews(); } -void MessageTableCell::setBackgroundColor(KDColor color) { +template +void MessageTableCell::setBackgroundColor(KDColor color) { m_backgroundColor = color; m_messageTextView.setBackgroundColor(color); } + +template class MessageTableCell; +template class MessageTableCell; diff --git a/escher/src/message_table_cell_with_chevron.cpp b/escher/src/message_table_cell_with_chevron.cpp index 0f80c80343a..9711d2f79de 100644 --- a/escher/src/message_table_cell_with_chevron.cpp +++ b/escher/src/message_table_cell_with_chevron.cpp @@ -1,12 +1,24 @@ #include -MessageTableCellWithChevron::MessageTableCellWithChevron(I18n::Message message, const KDFont * font) : - MessageTableCell(message, font), +template<> +MessageTableCellWithChevron::MessageTableCellWithChevron(I18n::Message message, const KDFont * font) : + MessageTableCell(message, font), m_accessoryView() { } -View * MessageTableCellWithChevron::accessoryView() const { +template<> +MessageTableCellWithChevron::MessageTableCellWithChevron(I18n::Message message, const KDFont * font) : + MessageTableCell(message, font,TableCell::Layout::HorizontalRightOverlap), + m_accessoryView() +{ +} + +template +View * MessageTableCellWithChevron::accessoryView() const { return (View *)&m_accessoryView; } +template class MessageTableCellWithChevron; +template class MessageTableCellWithChevron; + diff --git a/escher/src/message_table_cell_with_message.cpp b/escher/src/message_table_cell_with_message.cpp index 16cfd3c6ed5..5c3d43aa47c 100644 --- a/escher/src/message_table_cell_with_message.cpp +++ b/escher/src/message_table_cell_with_message.cpp @@ -2,38 +2,47 @@ #include #include -MessageTableCellWithMessage::MessageTableCellWithMessage(I18n::Message message, Layout layout) : - MessageTableCell(message, KDFont::SmallFont, layout), +template +MessageTableCellWithMessage::MessageTableCellWithMessage(I18n::Message message, TableCell::Layout layout) : + MessageTableCell(message, KDFont::SmallFont, layout), m_accessoryView(KDFont::SmallFont, (I18n::Message)0, 0.0f, 0.5f) { - if (layout != Layout::Vertical) { + if (layout != TableCell::Layout::Vertical) { m_accessoryView.setAlignment(1.0f, 0.5f); } } -void MessageTableCellWithMessage::setAccessoryMessage(I18n::Message textBody) { +template +void MessageTableCellWithMessage::setAccessoryMessage(I18n::Message textBody) { m_accessoryView.setMessage(textBody); - reloadCell(); + this->reloadCell(); } -View * MessageTableCellWithMessage::accessoryView() const { +template +View * MessageTableCellWithMessage::accessoryView() const { if (strlen(m_accessoryView.text()) == 0) { return nullptr; } return (View *)&m_accessoryView; } -void MessageTableCellWithMessage::setHighlighted(bool highlight) { - MessageTableCell::setHighlighted(highlight); - KDColor backgroundColor = isHighlighted()? Palette::ListCellBackgroundSelected : Palette::ListCellBackground; +template +void MessageTableCellWithMessage::setHighlighted(bool highlight) { + MessageTableCell::setHighlighted(highlight); + KDColor backgroundColor = this->isHighlighted()? Palette::ListCellBackgroundSelected : Palette::ListCellBackground; m_accessoryView.setBackgroundColor(backgroundColor); } -void MessageTableCellWithMessage::setTextColor(KDColor color) { +template +void MessageTableCellWithMessage::setTextColor(KDColor color) { m_accessoryView.setTextColor(color); - MessageTableCell::setTextColor(color); + MessageTableCell::setTextColor(color); } -void MessageTableCellWithMessage::setAccessoryTextColor(KDColor color) { +template +void MessageTableCellWithMessage::setAccessoryTextColor(KDColor color) { m_accessoryView.setTextColor(color); } + +template class MessageTableCellWithMessage; +template class MessageTableCellWithMessage; diff --git a/escher/src/modal_view_empty_controller.cpp b/escher/src/modal_view_empty_controller.cpp index 99757cfda28..f8f7056dfa9 100644 --- a/escher/src/modal_view_empty_controller.cpp +++ b/escher/src/modal_view_empty_controller.cpp @@ -30,7 +30,7 @@ void ModalViewEmptyController::ModalViewEmptyView::setMessages(I18n::Message * m void ModalViewEmptyController::ModalViewEmptyView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(bounds(), k_backgroundColor); - drawBorderOfRect(ctx, bounds(), Palette::GrayBright); + drawBorderOfRect(ctx, bounds(), Palette::ListCellBorder); } int ModalViewEmptyController::ModalViewEmptyView::numberOfSubviews() const { diff --git a/escher/src/nested_menu_controller.cpp b/escher/src/nested_menu_controller.cpp index 735f5a32c6f..3911ffcc3b4 100644 --- a/escher/src/nested_menu_controller.cpp +++ b/escher/src/nested_menu_controller.cpp @@ -156,8 +156,11 @@ bool NestedMenuController::handleEventForRow(Ion::Events::Event event, int rowIn if ((event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) && typeAtLocation(0, selectedRow()) == NodeCellType) { return selectSubMenu(rowIndex); } + if (canStayInMenu() && ((event == Ion::Events::ShiftOK || event == Ion::Events::ShiftEXE) && typeAtLocation(0, selectedRow()) == LeafCellType)) { + return selectLeaf(rowIndex, false); + } if ((event == Ion::Events::OK || event == Ion::Events::EXE) && typeAtLocation(0, selectedRow()) == LeafCellType) { - return selectLeaf(rowIndex); + return selectLeaf(rowIndex, true); } return false; } diff --git a/escher/src/run_loop.cpp b/escher/src/run_loop.cpp index f5d8c3b9734..bc2f02a0901 100644 --- a/escher/src/run_loop.cpp +++ b/escher/src/run_loop.cpp @@ -3,16 +3,9 @@ #include RunLoop::RunLoop() : - m_time(0) { -} - -int RunLoop::numberOfTimers() { - return 0; -} - -Timer * RunLoop::timerAtIndex(int i) { - assert(false); - return nullptr; + m_time(0), + m_firstTimer(nullptr) +{ } void RunLoop::run() { @@ -45,11 +38,12 @@ bool RunLoop::step() { if (m_time >= Timer::TickDuration) { m_time -= Timer::TickDuration; - for (int i=0; itick()) { dispatchEvent(Ion::Events::TimerFire); } + timer = timer->next(); } } @@ -65,7 +59,7 @@ bool RunLoop::step() { #endif if (event != Ion::Events::None) { -#if !PLATFORM_DEVICE +#if !PLATFORM_DEVICEdidStopAnimation if (event == Ion::Events::ExternalText && !KDFont::CanBeWrittenWithGlyphs(event.text())) { return true; } @@ -75,3 +69,27 @@ bool RunLoop::step() { return event != Ion::Events::Termination; } + +void RunLoop::addTimer(Timer * timer) { + if (m_firstTimer == nullptr) { + m_firstTimer = timer; + } else { + Timer * actual = m_firstTimer; + while (actual->next()) { + actual = actual->next(); + } + actual->setNext(timer); + } +} + +void RunLoop::removeTimer(Timer * timer) { + if (m_firstTimer == timer) { + m_firstTimer = timer->next(); + } else { + Timer * actual = m_firstTimer; + while (actual->next() != timer) { + actual = actual->next(); + } + actual->setNext(timer->next()); + } +} diff --git a/escher/src/selectable_table_view.cpp b/escher/src/selectable_table_view.cpp index 44804fcb51b..226ed1b0958 100644 --- a/escher/src/selectable_table_view.cpp +++ b/escher/src/selectable_table_view.cpp @@ -93,7 +93,7 @@ bool SelectableTableView::selectCellAtLocation(int i, int j, bool setFirstRespon * ExpressionModelListController needs to update its memoized cell before * being able to scroll; * - after scrolling: for instance, the calculation history table might - * change its cell content when selected (outup toggling, ellipsis toggling) + * change its cell content when selected (output toggling, ellipsis toggling) * and thus need to access the right used cell - which is defined only * after scrolling. */ diff --git a/escher/src/slideable_message_text_view.cpp b/escher/src/slideable_message_text_view.cpp new file mode 100644 index 00000000000..d81ad7ba607 --- /dev/null +++ b/escher/src/slideable_message_text_view.cpp @@ -0,0 +1,62 @@ +#include + +SlideableMessageTextView::SlideableMessageTextView(const KDFont * font, I18n::Message message, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor backgroundColor) : + MessageTextView(font, message, horizontalAlignment, verticalAlignment, textColor, backgroundColor), + m_textOffset(0) +{ + +} + +void SlideableMessageTextView::willStartAnimation() { + m_textOffset = 0; + m_goingLeft = true; + m_paused = true; +} + +void SlideableMessageTextView::didStopAnimation() { + m_textOffset = 0; +} + +void SlideableMessageTextView::animate() { + if (m_paused) { + m_paused = false; + return; + } + + if (text() == nullptr) { + return; + } + + KDSize textSize = m_font->stringSize(text()); + + if (textSize.width() <= bounds().width()) { + return; + } + + KDCoordinate glyphWidth = m_font->glyphSize().width(); + m_textOffset += glyphWidth * (m_goingLeft ? -1 : 1); + + if (m_goingLeft && textSize.width() + m_textOffset < bounds().width()) { + m_goingLeft = false; + m_textOffset = bounds().width() - textSize.width(); + m_paused = true; + } else if (!m_goingLeft && m_textOffset > 0) { + m_goingLeft = true; + m_textOffset = 0; + m_paused = true; + } + + markRectAsDirty(bounds()); +} + +void SlideableMessageTextView::drawRect(KDContext * ctx, KDRect rect) const { + if (text() == nullptr) { + return; + } + KDSize textSize = m_font->stringSize(text()); + KDPoint origin( + m_horizontalAlignment * (m_frame.width() - textSize.width()) + m_textOffset, + m_verticalAlignment * (m_frame.height() - textSize.height())); + ctx->fillRect(bounds(), m_backgroundColor); + ctx->drawString(text(), origin, m_font, m_textColor, m_backgroundColor); +} diff --git a/escher/src/text_area.cpp b/escher/src/text_area.cpp index 7c88640d051..5427ed5b652 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -33,7 +33,7 @@ static inline void InsertSpacesAtLocation(int spacesCount, char * buffer, int bu } } -bool TextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) { +bool TextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText, bool shouldRemoveLastCharacter) { if (*text == 0) { return false; } @@ -123,11 +123,19 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for return false; } - // Insert the text - if (!insertTextAtLocation(text, insertionPosition)) { + int textLength = strlen(text); + if (!contentView()->isAbleToInsertTextAt(textLength, insertionPosition, shouldRemoveLastCharacter)) { return true; } + if (shouldRemoveLastCharacter) { + removePreviousGlyph(); + insertionPosition = const_cast(cursorLocation()); + } + + // Insert the text + insertTextAtLocation(text, insertionPosition, textLength); + // Insert the indentation if (indentation) { UTF8Helper::PerformAtCodePoints( @@ -160,6 +168,9 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for } bool TextArea::handleEvent(Ion::Events::Event event) { + if (m_delegate != nullptr && event != Ion::Events::XNT) { + m_delegate->textAreaDidReceiveNoneXNTEvent(); + } if (m_delegate != nullptr && m_delegate->textAreaDidReceiveEvent(this, event)) { return true; } @@ -513,21 +524,29 @@ void TextArea::ContentView::drawRect(KDContext * ctx, KDRect rect) const { } } -void TextArea::ContentView::drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor) const { +void TextArea::ContentView::drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor, bool isItalic) const { if (length < 0) { return; } - KDSize glyphSize = m_font->glyphSize(); + + const KDFont * usedFont = m_font; + if (isItalic) { + usedFont = m_font->toItalic(); + } + + KDSize glyphSize = usedFont->glyphSize(); bool drawSelection = selectionStart != nullptr && selectionEnd > text && selectionStart < text + length; + KDPoint nextPoint = ctx->drawString( - text, - KDPoint(column*glyphSize.width(), line*glyphSize.height()), - m_font, - textColor, - backgroundColor, - drawSelection ? (selectionStart >= text ? std::min(length, selectionStart - text) : 0) : length - ); + text, + KDPoint(column*glyphSize.width(), line*glyphSize.height()), + usedFont, + textColor, + backgroundColor, + drawSelection ? (selectionStart >= text ? std::min(length, selectionStart - text) : 0) : length + ); + if (!drawSelection) { return; } @@ -537,7 +556,7 @@ void TextArea::ContentView::drawStringAt(KDContext * ctx, int line, int column, nextPoint = ctx->drawString( highlightedDrawStart, nextPoint, - m_font, + usedFont, textColor, backgroundHighlightColor, highlightedDrawLength); @@ -546,7 +565,7 @@ void TextArea::ContentView::drawStringAt(KDContext * ctx, int line, int column, ctx->drawString( notHighlightedDrawStart, nextPoint, - m_font, + usedFont, textColor, backgroundColor, length - (notHighlightedDrawStart - text)); @@ -567,12 +586,21 @@ void TextArea::ContentView::setText(char * textBuffer, size_t textBufferSize) { m_cursorLocation = text(); } -bool TextArea::ContentView::insertTextAtLocation(const char * text, char * location, int textLength) { +bool TextArea::ContentView::isAbleToInsertTextAt(int textLength, const char * location, bool shouldRemoveLastCharacter) const { + int removedCharacters = 0; + if (shouldRemoveLastCharacter) { + UTF8Decoder decoder(m_text.text(), location); + const char * previousGlyphPos = decoder.previousGlyphPosition(); + assert(previousGlyphPos != nullptr); + removedCharacters = location - previousGlyphPos; + } + return m_text.textLength() + textLength - removedCharacters < m_text.bufferSize() && textLength != 0; +} + +void TextArea::ContentView::insertTextAtLocation(const char * text, char * location, int textLength) { int textLen = textLength < 0 ? strlen(text) : textLength; assert(textLen < 0 || textLen <= strlen(text)); - if (m_text.textLength() + textLen >= m_text.bufferSize() || textLen == 0) { - return false; - } + assert(isAbleToInsertTextAt(textLen, location, false)); // Scan for \n bool lineBreak = UTF8Helper::HasCodePoint(text, '\n', text + textLen); @@ -581,7 +609,6 @@ bool TextArea::ContentView::insertTextAtLocation(const char * text, char * locat // Replace System parentheses (used to keep layout tree structure) by normal parentheses Poincare::SerializationHelper::ReplaceSystemParenthesesByUserParentheses(location, textLen); reloadRectFromPosition(location, lineBreak); - return true; } bool TextArea::ContentView::removePreviousGlyph() { diff --git a/escher/src/text_field.cpp b/escher/src/text_field.cpp index 4411f12a46c..4389658acc2 100644 --- a/escher/src/text_field.cpp +++ b/escher/src/text_field.cpp @@ -119,14 +119,23 @@ void TextField::ContentView::reinitDraftTextBuffer() { setCursorLocation(s_draftTextBuffer); } -bool TextField::ContentView::insertTextAtLocation(const char * text, char * location, int textLen) { +bool TextField::ContentView::isAbleToInsertTextAt(int textLength, const char * location, bool shouldRemoveLastCharacter) const { + int removedCharacters = 0; + if (shouldRemoveLastCharacter) { + UTF8Decoder decoder(s_draftTextBuffer, location); + const char * previousGlyphPos = decoder.previousGlyphPosition(); + assert(previousGlyphPos != nullptr); + removedCharacters = location - previousGlyphPos; + } + return m_currentDraftTextLength + textLength - removedCharacters < m_draftTextBufferSize && textLength != 0; +} + +void TextField::ContentView::insertTextAtLocation(const char * text, char * location, int textLen) { assert(m_isEditing); size_t textLength = textLen < 0 ? strlen(text) : (size_t)textLen; // TODO when paste fails because of a too big message, create a pop-up - if (m_currentDraftTextLength + textLength >= m_draftTextBufferSize || textLength == 0) { - return false; - } + assert(isAbleToInsertTextAt(textLength, location, false)); memmove(location + textLength, location, (s_draftTextBuffer + m_currentDraftTextLength + 1) - location); @@ -139,7 +148,6 @@ bool TextField::ContentView::insertTextAtLocation(const char * text, char * loca m_currentDraftTextLength += copySize-1; // Do no count the null-termination reloadRectFromPosition(m_horizontalAlignment == 0.0f ? location : s_draftTextBuffer); - return true; } KDSize TextField::ContentView::minimalSizeForOptimalDisplay() const { @@ -155,7 +163,7 @@ bool TextField::ContentView::removePreviousGlyph() { assert(m_isEditing); if (m_horizontalAlignment > 0.0f) { - /* Reload the view. If we do it later, the text beins supposedly shorter, we + /* Reload the view. If we do it later, the text beings supposedly shorter, we * will not clean the first char. */ reloadRectFromPosition(s_draftTextBuffer); } @@ -290,7 +298,7 @@ bool TextField::privateHandleEvent(Ion::Events::Event event) { return true; } if (isEditing() && shouldFinishEditing(event)) { - /* If textFieldDidFinishEditing displays a pop-up (because of an unvalid + /* If textFieldDidFinishEditing displays a pop-up (because of an invalid * text for instance), the text field will call willResignFirstResponder. * This will call textFieldDidAbortEditing if the textfield is still editing, * which we do not want, as we are not really aborting edition, just @@ -416,6 +424,9 @@ CodePoint TextField::XNTCodePoint(CodePoint defaultXNTCodePoint) { bool TextField::handleEvent(Ion::Events::Event event) { assert(m_delegate != nullptr); + if (event != Ion::Events::XNT) { + m_delegate->textFieldDidReceiveNoneXNTEvent(); + } size_t previousTextLength = strlen(text()); bool didHandleEvent = false; if (privateHandleMoveEvent(event)) { @@ -486,7 +497,7 @@ bool TextField::privateHandleSelectEvent(Ion::Events::Event event) { return false; } -bool TextField::handleEventWithText(const char * eventText, bool indentation, bool forceCursorRightOfText) { +bool TextField::handleEventWithText(const char * eventText, bool indentation, bool forceCursorRightOfText, bool shouldRemoveLastCharacter) { size_t previousTextLength = strlen(text()); if (!isEditing()) { @@ -518,7 +529,13 @@ bool TextField::handleEventWithText(const char * eventText, bool indentation, bo // Replace System parentheses (used to keep layout tree structure) by normal parentheses Poincare::SerializationHelper::ReplaceSystemParenthesesByUserParentheses(buffer); - if (insertTextAtLocation(buffer, const_cast(cursorLocation()))) { + int textLength = strlen(buffer); + if (contentView()->isAbleToInsertTextAt(textLength, cursorLocation(), shouldRemoveLastCharacter)) { + if (shouldRemoveLastCharacter) { + removePreviousGlyph(); + } + + insertTextAtLocation(buffer, const_cast(cursorLocation()), textLength); /* The cursor position depends on the text as we sometimes want to position * the cursor at the end of the text and sometimes after the first * parenthesis. */ diff --git a/escher/src/text_input.cpp b/escher/src/text_input.cpp index da741f62c9d..608a289b809 100644 --- a/escher/src/text_input.cpp +++ b/escher/src/text_input.cpp @@ -170,15 +170,13 @@ void TextInput::setAlignment(float horizontalAlignment, float verticalAlignment) contentView()->setAlignment(horizontalAlignment, verticalAlignment); } -bool TextInput::insertTextAtLocation(const char * text, char * location) { - if (contentView()->insertTextAtLocation(text, location)) { - /* We layout the scrollable view before scrolling to cursor because the - * content size might have changed. */ - layoutSubviews(); - scrollToCursor(); - return true; - } - return false; +void TextInput::insertTextAtLocation(const char * text, char * location, int textLength) { + assert(contentView()->isAbleToInsertTextAt(textLength, location, false)); + contentView()->insertTextAtLocation(text, location, textLength); + /* We layout the scrollable view before scrolling to cursor because the + * content size might have changed. */ + layoutSubviews(); + scrollToCursor(); } bool TextInput::removeEndOfLine() { diff --git a/escher/src/timer.cpp b/escher/src/timer.cpp index 0e9f5a5eac8..742634fbf1a 100644 --- a/escher/src/timer.cpp +++ b/escher/src/timer.cpp @@ -16,6 +16,9 @@ bool Timer::tick() { return false; } -void Timer::reset() { +void Timer::reset(uint32_t NewPeriod) { + if(NewPeriod != -1){ + m_period = NewPeriod; + } m_numberOfTicksBeforeFire = m_period; } diff --git a/escher/src/toolbox.cpp b/escher/src/toolbox.cpp index 29f22a0412b..14bc47014a6 100644 --- a/escher/src/toolbox.cpp +++ b/escher/src/toolbox.cpp @@ -27,15 +27,16 @@ int Toolbox::reusableCellCount(int type) { void Toolbox::willDisplayCellForIndex(HighlightCell * cell, int index) { ToolboxMessageTree * messageTree = (ToolboxMessageTree *)m_messageTreeModel->childAtIndex(index); if (messageTree->numberOfChildren() == 0) { - MessageTableCellWithMessage * myCell = (MessageTableCellWithMessage *)cell; + MessageTableCellWithMessage * myCell = (MessageTableCellWithMessage *)cell; myCell->setMessage(messageTree->label()); myCell->setAccessoryMessage(messageTree->text()); myCell->setAccessoryTextColor(Palette::SecondaryText); return; + } else { + MessageTableCell<> * myCell = (MessageTableCell<> *)cell; + myCell->setMessage(messageTree->label()); + myCell->reloadCell(); } - MessageTableCell * myCell = (MessageTableCell *)cell; - myCell->setMessage(messageTree->label()); - myCell->reloadCell(); } int Toolbox::typeAtLocation(int i, int j) { diff --git a/ion/Makefile b/ion/Makefile index 8bcef599e72..96797fba26f 100644 --- a/ion/Makefile +++ b/ion/Makefile @@ -18,11 +18,11 @@ include ion/src/$(PLATFORM)/Makefile include ion/src/shared/tools/Makefile # We need to work around a GCC bug (concerning versions < 5.1). It is valid in -# C++11 to initialize a character array by providing a string litteral (e.g. +# C++11 to initialize a character array by providing a string literal (e.g. # char test[4]= "ab"; is valid and should initialize test to 'a','b',0,0). # Older versions of GCC are not conformant so we resort to an initializer list. initializer_list = $(shell echo $(1) | sed "s/\(.\)/'\1',/g")0 -$(call object_for,ion/src/simulator/platform_info.cpp ion/src/device/n0100/platform_info.cpp ion/src/device/n0110/platform_info.cpp ion/src/device/bootloader/platform_info.cpp ion/src/simulator/shared/platform_info.cpp): SFLAGS += -DPATCH_LEVEL="$(call initializer_list,$(PATCH_LEVEL))" -DEPSILON_VERSION="$(call initializer_list,$(EPSILON_VERSION))" -DOMEGA_VERSION="$(call initializer_list,$(OMEGA_VERSION))" -DOMEGA_USERNAME="$(call initializer_list,$(OMEGA_USERNAME))" +$(call object_for,ion/src/simulator/platform_info.cpp ion/src/device/n0100/platform_info.cpp ion/src/device/n0110/platform_info.cpp ion/src/device/bootloader/platform_info.cpp ion/src/simulator/shared/platform_info.cpp): SFLAGS += -DPATCH_LEVEL="$(call initializer_list,$(PATCH_LEVEL))" -DEPSILON_VERSION="$(call initializer_list,$(EPSILON_VERSION))" -DOMEGA_VERSION="$(call initializer_list,$(OMEGA_VERSION))" -DUPSILON_VERSION="$(call initializer_list,$(UPSILON_VERSION))" -DOMEGA_USERNAME="$(call initializer_list,$(OMEGA_USERNAME))" ion_src += $(addprefix ion/src/shared/, \ console_line.cpp \ @@ -34,6 +34,7 @@ ion_src += $(addprefix ion/src/shared/, \ rtc.cpp \ stack_position.cpp \ storage.cpp \ + internal_storage.cpp \ unicode/utf8_decoder.cpp\ unicode/utf8_helper.cpp\ ) diff --git a/ion/include/ion.h b/ion/include/ion.h index ce44f84a36c..fb935183a41 100644 --- a/ion/include/ion.h +++ b/ion/include/ion.h @@ -35,11 +35,13 @@ namespace Ion { const char * serialNumber(); const volatile char * username(); const char * softwareVersion(); +const char * upsilonVersion(); const char * omegaVersion(); const char * patchLevel(); const char * fccId(); const char * pcbVersion(); void updateSlotInfo(); +const void * storageAddress(); // CRC32 : non xor-ed, non reversed, direct, polynomial 4C11DB7 uint32_t crc32Word(const uint32_t * data, size_t length); // Only accepts whole 32bit values diff --git a/ion/include/ion/backlight.h b/ion/include/ion/backlight.h index 89a19687c08..cd69ee88822 100644 --- a/ion/include/ion/backlight.h +++ b/ion/include/ion/backlight.h @@ -12,7 +12,6 @@ bool isInitialized(); void shutdown(); void setBrightness(uint8_t b); uint8_t brightness(); -const int NumberOfStepsPerShortcut = 4; } } diff --git a/ion/include/ion/display.h b/ion/include/ion/display.h index 1b7f32508c5..0abd90c1673 100644 --- a/ion/include/ion/display.h +++ b/ion/include/ion/display.h @@ -23,8 +23,15 @@ void pullRect(KDRect r, KDColor * pixels); bool waitForVBlank(); +#ifndef _FXCG constexpr int Width = 320; constexpr int Height = 240; +#else +constexpr int Width = 396; +constexpr int Height = 224; +#endif + +// TODO: Adjust this on the Casio calculator constexpr int WidthInTenthOfMillimeter = 576; constexpr int HeightInTenthOfMillimeter = 432; diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index 1764eb5952d..e7ae2feb2b1 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -141,6 +141,8 @@ constexpr Event ShiftLeft = Event::ShiftKey(Keyboard::Key::Left); constexpr Event ShiftRight = Event::ShiftKey(Keyboard::Key::Right); constexpr Event ShiftUp = Event::ShiftKey(Keyboard::Key::Up); constexpr Event ShiftDown = Event::ShiftKey(Keyboard::Key::Down); +constexpr Event ShiftOK = Event::ShiftKey(Keyboard::Key::OK); +constexpr Event ShiftBack = Event::ShiftKey(Keyboard::Key::Back); constexpr Event AlphaLock = Event::ShiftKey(Keyboard::Key::Alpha); constexpr Event Cut = Event::ShiftKey(Keyboard::Key::XNT); @@ -169,7 +171,7 @@ constexpr Event DoubleParenthesis = Event::ShiftKey(Keyboard::Key::LeftParenthes constexpr Event ShiftZero = Event::ShiftKey(Keyboard::Key::Zero); constexpr Event ShiftDot = Event::ShiftKey(Keyboard::Key::Dot); -constexpr Event ShiftEE = Event::PlainKey(Keyboard::Key::EE); +constexpr Event ShiftEE = Event::ShiftKey(Keyboard::Key::EE); constexpr Event ShiftOne = Event::ShiftKey(Keyboard::Key::One); constexpr Event ShiftTwo = Event::ShiftKey(Keyboard::Key::Two); @@ -183,6 +185,9 @@ constexpr Event ShiftSeven = Event::ShiftKey(Keyboard::Key::Seven); constexpr Event ShiftEight = Event::ShiftKey(Keyboard::Key::Eight); constexpr Event ShiftNine = Event::ShiftKey(Keyboard::Key::Nine); +constexpr Event ShiftAns = Event::ShiftKey(Keyboard::Key::Ans); +constexpr Event ShiftEXE = Event::ShiftKey(Keyboard::Key::EXE); + // Alpha constexpr Event AlphaLeft = Event::AlphaKey(Keyboard::Key::Left); diff --git a/ion/include/ion/internal_storage.h b/ion/include/ion/internal_storage.h new file mode 100644 index 00000000000..026ae5dc04d --- /dev/null +++ b/ion/include/ion/internal_storage.h @@ -0,0 +1,222 @@ +#ifndef ION_INTERNAL_STORAGE_H +#define ION_INTERNAL_STORAGE_H + +#include +#include +#include "storage.h" + +namespace Ion { + +/* Storage : | Magic | Record1 | Record2 | ... | Magic | + * | Magic | Size1(uint16_t) | FullName1 | Body1 | Size2(uint16_t) | FullName2 | Body2 | ... | Magic | + * + * A record's fullName is baseName.extension. */ + +class StorageDelegate; + +class InternalStorage { +public: + typedef uint16_t record_size_t; + + static constexpr char eqExtension[] = "eq"; + static constexpr char expExtension[] = "exp"; + static constexpr char funcExtension[] = "func"; + static constexpr char seqExtension[] = "seq"; +#ifdef _FXCG + constexpr static size_t k_storageSize = 65500; +#else + constexpr static size_t k_storageSize = 60000; +#endif + static_assert(UINT16_MAX >= k_storageSize, "record_size_t not big enough"); + + constexpr static char k_dotChar = '.'; + + class Record { + /* A Record is identified by the CRC32 on its fullName because: + * - A record is identified by its fullName, which is unique + * - We cannot keep the address pointing to the fullName because if another + * record is modified, it might alter our record's fullName address. + * Keeping a buffer with the fullNames will waste memory as we cannot + * forsee the size of the fullNames. */ + friend class Storage; + public: + enum class ErrorStatus { + None = 0, + NameTaken = 1, + NonCompliantName = 2, + NotEnoughSpaceAvailable = 3, + RecordDoesNotExist = 4 + }; + struct Data { + const void * buffer; + size_t size; + }; + Record(const char * fullName = nullptr); + Record(const char * basename, const char * extension); + bool operator==(const Record & other) const { + return m_fullNameCRC32 == other.m_fullNameCRC32; + } + bool operator!=(const Record & other) const { + return !(*this == other); + } +#if ION_STORAGE_LOG + void log(); +#endif + uint32_t checksum(); + bool isNull() const { + return m_fullNameCRC32 == 0; + } + const char * fullName() const; + ErrorStatus setBaseNameWithExtension(const char * baseName, const char * extension); + ErrorStatus setName(const char * fullName); + Data value() const; + ErrorStatus setValue(Data data); + void destroy(); + private: + Record(const char * basename, int basenameLength, const char * extension, int extensionLength); + uint32_t m_fullNameCRC32; + }; + +#if ION_STORAGE_LOG + void log(); +#endif + + size_t availableSize(); + size_t putAvailableSpaceAtEndOfRecord(Record r); + void getAvailableSpaceFromEndOfRecord(Record r, size_t recordAvailableSpace); + uint32_t checksum(); + + // Delegate + void setDelegate(StorageDelegate * delegate) { m_delegate = delegate; } + void notifyChangeToDelegate(const Record r = Record()) const; + Record::ErrorStatus notifyFullnessToDelegate() const; + + int numberOfRecordsWithExtension(const char * extension); + static bool FullNameHasExtension(const char * fullName, const char * extension, size_t extensionLength); + + // Record creation + Record::ErrorStatus createRecordWithFullName(const char * fullName, const void * data, size_t size); + Record::ErrorStatus createRecordWithExtension(const char * baseName, const char * extension, const void * data, size_t size); + + // Record getters + bool hasRecord(Record r) { return pointerOfRecord(r) != nullptr; } + Record recordWithExtensionAtIndex(const char * extension, int index); + Record recordNamed(const char * fullName); + Record recordBaseNamedWithExtension(const char * baseName, const char * extension); + Record recordBaseNamedWithExtensions(const char * baseName, const char * const extension[], size_t numberOfExtensions); + const char * extensionOfRecordBaseNamedWithExtensions(const char * baseName, int baseNameLength, const char * const extension[], size_t numberOfExtensions); + + // Record destruction + void destroyAllRecords(); + void destroyRecordWithBaseNameAndExtension(const char * baseName, const char * extension); + void destroyRecordsWithExtension(const char * extension); + + // Useful + static bool FullNameCompliant(const char * name); + + // Used by Python OS module + int numberOfRecords(); + Record recordAtIndex(int index); +protected: + InternalStorage(); + /* Getters on address in buffer */ + char * pointerOfRecord(const Record record) const; + record_size_t sizeOfRecordStarting(char * start) const; + const char * fullNameOfRecordStarting(char * start) const; + const void * valueOfRecordStarting(char * start) const; + void destroyRecord(const Record record); + + class RecordIterator { + public: + RecordIterator(char * start) : m_recordStart(start) {} + char * operator*() { return m_recordStart; } + RecordIterator& operator++(); + bool operator!=(const RecordIterator& it) const { return m_recordStart != it.m_recordStart; } + private: + char * m_recordStart; + }; + RecordIterator begin() const { + if (sizeOfRecordStarting((char *)m_buffer) == 0) { + return nullptr; + } + return RecordIterator((char *)m_buffer); + }; + RecordIterator end() const { return RecordIterator(nullptr); } + +private: + constexpr static uint32_t Magic = 0xEE0BDDBA; + constexpr static size_t k_maxRecordSize = (1 << sizeof(record_size_t)*8); + + /* Getters/Setters on recordID */ + const char * fullNameOfRecord(const Record record); + Record::ErrorStatus setFullNameOfRecord(const Record record, const char * fullName); + Record::ErrorStatus setBaseNameWithExtensionOfRecord(const Record record, const char * baseName, const char * extension); + Record::Data valueOfRecord(const Record record); + Record::ErrorStatus setValueOfRecord(const Record record, Record::Data data); + + /* Overriders */ + size_t overrideSizeAtPosition(char * position, record_size_t size); + size_t overrideFullNameAtPosition(char * position, const char * fullName); + size_t overrideBaseNameWithExtensionAtPosition(char * position, const char * baseName, const char * extension); + size_t overrideValueAtPosition(char * position, const void * data, record_size_t size); + + bool isFullNameTaken(const char * fullName, const Record * recordToExclude = nullptr); + bool isBaseNameWithExtensionTaken(const char * baseName, const char * extension, Record * recordToExclude = nullptr); + bool isNameOfRecordTaken(Record r, const Record * recordToExclude); + char * endBuffer(); + size_t sizeOfBaseNameAndExtension(const char * baseName, const char * extension) const; + size_t sizeOfRecordWithBaseNameAndExtension(const char * baseName, const char * extension, size_t size) const; + size_t sizeOfRecordWithFullName(const char * fullName, size_t size) const; + bool slideBuffer(char * position, int delta); + + Record privateRecordAndExtensionOfRecordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions, const char * * extensionResult = nullptr, int baseNameLength = -1); + + uint32_t m_magicHeader; + char m_buffer[k_storageSize]; + uint32_t m_magicFooter; + StorageDelegate * m_delegate; +protected: + mutable Record m_lastRecordRetrieved; + mutable char * m_lastRecordRetrievedPointer; +}; + +/* Some apps memoize records and need to be notified when a record might have + * become invalid. For instance in the Graph app, if f(x) = A+x, and A changed, + * f(x) memoization which stores the reduced expression of f is outdated. + * We could have computed and compared the checksum of the storage to detect + * storage invalidity, but profiling showed that this slows down the execution + * (for example when scrolling the functions list). + * We thus decided to notify a delegate when the storage changes. */ + +class StorageDelegate { +public: + virtual void storageDidChangeForRecord(const InternalStorage::Record record) = 0; + virtual void storageIsFull() = 0; +}; + +// emscripten read and writes must be aligned. +class StorageHelper { +public: + static uint16_t unalignedShort(char * address) { +#if (defined __EMSCRIPTEN__) || (defined _FXCG) || defined NSPIRE_NEWLIB + uint8_t f1 = *(address); + uint8_t f2 = *(address+1); + uint16_t f = (uint16_t)f1 + (((uint16_t)f2)<<8); + return f; +#else + return *(uint16_t *)address; +#endif + } + static void writeUnalignedShort(uint16_t value, char * address) { +#if (defined __EMSCRIPTEN__) || (defined _FXCG) || defined NSPIRE_NEWLIB + *((uint8_t *)address) = (uint8_t)(value & ((1 << 8) - 1)); + *((uint8_t *)address+1) = (uint8_t)(value >> 8); +#else + *((uint16_t *)address) = value; +#endif + } +}; + +} + +#endif diff --git a/ion/include/ion/keyboard.h b/ion/include/ion/keyboard.h index ba056ffd2a5..7d448991c7e 100644 --- a/ion/include/ion/keyboard.h +++ b/ion/include/ion/keyboard.h @@ -26,6 +26,12 @@ constexpr Key ValidKeys[] = { constexpr int NumberOfKeys = 54; constexpr int NumberOfValidKeys = 46; +enum class ModSimState : uint8_t { + None, + ForceOn, + ForceOff, +}; + class State { public: constexpr State(uint64_t s = 0) : @@ -50,8 +56,25 @@ class State { void clearKey(Key k) { m_bitField &= ~((uint64_t)1 << (uint8_t)k); } + void setSimulatedShift(ModSimState s) { + m_simulateShiftState = s; + } + ModSimState simulatedShift() const { + return m_simulateShiftState; + } + void setSimulatedAlpha(ModSimState s) { + m_simulateAlphaState = s; + } + ModSimState simulatedAlpha() const { + return m_simulateAlphaState; + } private: uint64_t m_bitField; + + // Simulated key states + // These override the real key states and are used to map keys to keys under modifiers + ModSimState m_simulateShiftState = ModSimState::None; + ModSimState m_simulateAlphaState = ModSimState::None; }; State scan(); diff --git a/ion/include/ion/storage.h b/ion/include/ion/storage.h index 10513517738..5385f4608d3 100644 --- a/ion/include/ion/storage.h +++ b/ion/include/ion/storage.h @@ -1,228 +1,50 @@ #ifndef ION_STORAGE_H #define ION_STORAGE_H -#include -#include +#include "internal_storage.h" namespace Ion { -/* Storage : | Magic | Record1 | Record2 | ... | Magic | - * | Magic | Size1(uint16_t) | FullName1 | Body1 | Size2(uint16_t) | FullName2 | Body2 | ... | Magic | - * - * A record's fullName is baseName.extension. */ - -class StorageDelegate; - -class Storage { +class Storage : public InternalStorage { public: - typedef uint16_t record_size_t; + using InternalStorage::Record; - constexpr static size_t k_storageSize = 32768; - static_assert(UINT16_MAX >= k_storageSize, "record_size_t not big enough"); + using InternalStorage::expExtension; + using InternalStorage::funcExtension; + using InternalStorage::seqExtension; + using InternalStorage::eqExtension; static Storage * sharedStorage(); - constexpr static char k_dotChar = '.'; - - static constexpr char eqExtension[] = "eq"; - static constexpr char expExtension[] = "exp"; - static constexpr char funcExtension[] = "func"; - static constexpr char seqExtension[] = "seq"; - - class Record { - /* A Record is identified by the CRC32 on its fullName because: - * - A record is identified by its fullName, which is unique - * - We cannot keep the address pointing to the fullName because if another - * record is modified, it might alter our record's fullName address. - * Keeping a buffer with the fullNames will waste memory as we cannot - * forsee the size of the fullNames. */ - friend class Storage; - public: - enum class ErrorStatus { - None = 0, - NameTaken = 1, - NonCompliantName = 2, - NotEnoughSpaceAvailable = 3, - RecordDoesNotExist = 4 - }; - struct Data { - const void * buffer; - size_t size; - }; - Record(const char * fullName = nullptr); - Record(const char * basename, const char * extension); - bool operator==(const Record & other) const { - return m_fullNameCRC32 == other.m_fullNameCRC32; - } - bool operator!=(const Record & other) const { - return !(*this == other); - } -#if ION_STORAGE_LOG - void log(); -#endif - uint32_t checksum(); - bool isNull() const { - return m_fullNameCRC32 == 0; - } - const char * fullName() const { - return Storage::sharedStorage()->fullNameOfRecord(*this); - } - ErrorStatus setBaseNameWithExtension(const char * baseName, const char * extension) { - return Storage::sharedStorage()->setBaseNameWithExtensionOfRecord(*this, baseName, extension); - } - ErrorStatus setName(const char * fullName) { - return Storage::sharedStorage()->setFullNameOfRecord(*this, fullName); - } - Data value() const { - return Storage::sharedStorage()->valueOfRecord(*this); - } - ErrorStatus setValue(Data data) { - return Storage::sharedStorage()->setValueOfRecord(*this, data); - } - void destroy() { - return Storage::sharedStorage()->destroyRecord(*this); - } - private: - Record(const char * basename, int basenameLength, const char * extension, int extensionLength); - uint32_t m_fullNameCRC32; - }; - -#if ION_STORAGE_LOG - void log(); -#endif size_t availableSize(); size_t putAvailableSpaceAtEndOfRecord(Record r); void getAvailableSpaceFromEndOfRecord(Record r, size_t recordAvailableSpace); - uint32_t checksum(); - - // Delegate - void setDelegate(StorageDelegate * delegate) { m_delegate = delegate; } - void notifyChangeToDelegate(const Record r = Record()) const; - Record::ErrorStatus notifyFullnessToDelegate() const; int numberOfRecordsWithExtension(const char * extension); - static bool FullNameHasExtension(const char * fullName, const char * extension, size_t extensionLength); - // Record creation Record::ErrorStatus createRecordWithFullName(const char * fullName, const void * data, size_t size); Record::ErrorStatus createRecordWithExtension(const char * baseName, const char * extension, const void * data, size_t size); - // Record getters - bool hasRecord(Record r) { return pointerOfRecord(r) != nullptr; } + bool hasRecord(Record r); + Record recordWithExtensionAtIndex(const char * extension, int index); Record recordNamed(const char * fullName); Record recordBaseNamedWithExtension(const char * baseName, const char * extension); Record recordBaseNamedWithExtensions(const char * baseName, const char * const extension[], size_t numberOfExtensions); const char * extensionOfRecordBaseNamedWithExtensions(const char * baseName, int baseNameLength, const char * const extension[], size_t numberOfExtensions); - // Record destruction - void destroyAllRecords(); - void destroyRecordWithBaseNameAndExtension(const char * baseName, const char * extension); - void destroyRecordsWithExtension(const char * extension); - - // Useful - static bool FullNameCompliant(const char * name); - - // User by Python OS module int numberOfRecords(); Record recordAtIndex(int index); + void destroyRecord(Record record); -private: - constexpr static uint32_t Magic = 0xEE0BDDBA; - constexpr static size_t k_maxRecordSize = (1 << sizeof(record_size_t)*8); - - Storage(); - - /* Getters/Setters on recordID */ - const char * fullNameOfRecord(const Record record); - Record::ErrorStatus setFullNameOfRecord(const Record record, const char * fullName); - Record::ErrorStatus setBaseNameWithExtensionOfRecord(const Record record, const char * baseName, const char * extension); - Record::Data valueOfRecord(const Record record); - Record::ErrorStatus setValueOfRecord(const Record record, Record::Data data); - void destroyRecord(const Record record); - - /* Getters on address in buffer */ - char * pointerOfRecord(const Record record) const; - record_size_t sizeOfRecordStarting(char * start) const; - const char * fullNameOfRecordStarting(char * start) const; - const void * valueOfRecordStarting(char * start) const; - - /* Overriders */ - size_t overrideSizeAtPosition(char * position, record_size_t size); - size_t overrideFullNameAtPosition(char * position, const char * fullName); - size_t overrideBaseNameWithExtensionAtPosition(char * position, const char * baseName, const char * extension); - size_t overrideValueAtPosition(char * position, const void * data, record_size_t size); - - bool isFullNameTaken(const char * fullName, const Record * recordToExclude = nullptr); - bool isBaseNameWithExtensionTaken(const char * baseName, const char * extension, Record * recordToExclude = nullptr); - bool isNameOfRecordTaken(Record r, const Record * recordToExclude); - char * endBuffer(); - size_t sizeOfBaseNameAndExtension(const char * baseName, const char * extension) const; - size_t sizeOfRecordWithBaseNameAndExtension(const char * baseName, const char * extension, size_t size) const; - size_t sizeOfRecordWithFullName(const char * fullName, size_t size) const; - bool slideBuffer(char * position, int delta); - class RecordIterator { - public: - RecordIterator(char * start) : m_recordStart(start) {} - char * operator*() { return m_recordStart; } - RecordIterator& operator++(); - bool operator!=(const RecordIterator& it) const { return m_recordStart != it.m_recordStart; } - private: - char * m_recordStart; - }; - RecordIterator begin() const { - if (sizeOfRecordStarting((char *)m_buffer) == 0) { - return nullptr; - } - return RecordIterator((char *)m_buffer); - }; - RecordIterator end() const { return RecordIterator(nullptr); } - - Record privateRecordAndExtensionOfRecordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions, const char * * extensionResult = nullptr, int baseNameLength = -1); - - uint32_t m_magicHeader; - char m_buffer[k_storageSize]; - uint32_t m_magicFooter; - StorageDelegate * m_delegate; - mutable Record m_lastRecordRetrieved; - mutable char * m_lastRecordRetrievedPointer; -}; - -/* Some apps memoize records and need to be notified when a record might have - * become invalid. For instance in the Graph app, if f(x) = A+x, and A changed, - * f(x) memoization which stores the reduced expression of f is outdated. - * We could have computed and compared the checksum of the storage to detect - * storage invalidity, but profiling showed that this slows down the execution - * (for example when scrolling the functions list). - * We thus decided to notify a delegate when the storage changes. */ + void reinsertTrash(const char * extension); + void emptyTrash(); -class StorageDelegate { -public: - virtual void storageDidChangeForRecord(const Storage::Record record) = 0; - virtual void storageIsFull() = 0; -}; +private: + Storage(): + InternalStorage() {} -// emscripten read and writes must be aligned. -class StorageHelper { -public: - static uint16_t unalignedShort(char * address) { -#if __EMSCRIPTEN__ - uint8_t f1 = *(address); - uint8_t f2 = *(address+1); - uint16_t f = (uint16_t)f1 + (((uint16_t)f2)<<8); - return f; -#else - return *(uint16_t *)address; -#endif - } - static void writeUnalignedShort(uint16_t value, char * address) { -#if __EMSCRIPTEN__ - *((uint8_t *)address) = (uint8_t)(value & ((1 << 8) - 1)); - *((uint8_t *)address+1) = (uint8_t)(value >> 8); -#else - *((uint16_t *)address) = value; -#endif - } + Record m_trashRecord; }; } diff --git a/ion/include/ion/timing.h b/ion/include/ion/timing.h index 523232f8ec8..45d7b6496a3 100644 --- a/ion/include/ion/timing.h +++ b/ion/include/ion/timing.h @@ -9,7 +9,7 @@ namespace Timing { void usleep(uint32_t us); void msleep(uint32_t ms); -/* millis is the number of milliseconds ellapsed since a random epoch. +/* millis is the number of milliseconds elapsed since a random epoch. * On the device, epoch is the boot time. */ uint64_t millis(); diff --git a/ion/include/ion/unicode/utf8_helper.h b/ion/include/ion/unicode/utf8_helper.h index edc3793259c..a3ce96df81f 100644 --- a/ion/include/ion/unicode/utf8_helper.h +++ b/ion/include/ion/unicode/utf8_helper.h @@ -19,17 +19,17 @@ class TextPair { bool m_removeParenthesesExtention; }; -// Returns the number of occurences of a code point in a string +// Returns the number of occurrences of a code point in a string int CountOccurrences(const char * s, CodePoint c); -/* Returns the first occurence of a code point in a string, the position of the +/* Returns the first occurrence of a code point in a string, the position of the * null terminating char otherwise. */ const char * CodePointSearch(const char * s, CodePoint c, const char * stoppingPosition = nullptr); // Returns true if the text had the code point bool HasCodePoint(const char * s, CodePoint c, const char * stoppingPosition = nullptr); -/* Returns the first occurence of a code point that is not c in a string, +/* Returns the first occurrence of a code point that is not c in a string, * stopping at the null-terminating char or the start of string. */ const char * NotCodePointSearch(const char * s, CodePoint c, bool goingLeft = false, const char * initialPosition = nullptr); @@ -41,9 +41,9 @@ bool CopyAndRemoveCodePoints(char * dst, size_t dstSize, const char * src, CodeP * points where removed before it. Ensure null-termination of dst. */ void RemoveCodePoint(char * buffer, CodePoint c, const char * * indexToUpdate = nullptr, const char * stoppingPosition = nullptr); -/* Slides a string by a number of chars. If slidingSize < 0, the string is slided +/* Slides a string by a number of chars. If slidingSize < 0, the string is slid * to the left losing the first chars. Returns true if successful. - * Exemples : + * Examples : * SlideStringByNumberOfChar("12345", 2, 7) gives "1212345" * SlideStringByNumberOfChar("12345", 2, 5) gives "12123" * SlideStringByNumberOfChar("12345", -2, 5) gives "34545"*/ @@ -120,6 +120,7 @@ size_t StringGlyphLength(const char * s, int maxSize = -1); const char * BeginningOfWord(const char * text, const char * word); // Returns the position of the first following char ' ', '\n' or 0 const char * EndOfWord(const char * word); +const char * EndOfWord(const char * word, const char * end); // On a line, count number of glyphs before and after locations void countGlyphsInLine(const char * text, int * before, int * after, const char * beforeLocation, const char *afterLocation = nullptr); diff --git a/ion/include/ion/usb.h b/ion/include/ion/usb.h index e5760bb947a..6650d79c377 100644 --- a/ion/include/ion/usb.h +++ b/ion/include/ion/usb.h @@ -8,7 +8,7 @@ bool isPlugged(); bool isEnumerated(); // Speed-enumerated, to be accurate void clearEnumerationInterrupt(); -void DFU(bool exitWithKeyboard = true); +void DFU(bool exitWithKeyboard = true, void * data = nullptr); void enable(); void disable(); diff --git a/ion/src/device/bootloader/drivers/board.cpp b/ion/src/device/bootloader/drivers/board.cpp index 03e47bc2cdb..a10d766c1df 100644 --- a/ion/src/device/bootloader/drivers/board.cpp +++ b/ion/src/device/bootloader/drivers/board.cpp @@ -418,6 +418,9 @@ bool pcbVersionIsLocked() { return *reinterpret_cast(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex)) == 0; } + +void jumpToInternalBootloader() {} + } } } diff --git a/ion/src/device/bootloader/drivers/external_flash.cpp b/ion/src/device/bootloader/drivers/external_flash.cpp index 612efc01796..5e38449852e 100644 --- a/ion/src/device/bootloader/drivers/external_flash.cpp +++ b/ion/src/device/bootloader/drivers/external_flash.cpp @@ -89,10 +89,15 @@ class ExternalFlashStatusRegister { public: using Register8::Register8; REGS_BOOL_FIELD_R(BUSY, 0); + REGS_BOOL_FIELD(BP, 2); + REGS_BOOL_FIELD(BP1, 3); + REGS_BOOL_FIELD(BP2, 4); + REGS_BOOL_FIELD(TB, 5); }; class StatusRegister2 : public Register8 { public: using Register8::Register8; + REGS_BOOL_FIELD(SRP1, 0); REGS_BOOL_FIELD(QE, 1); }; }; @@ -428,6 +433,46 @@ void unlockFlash() { wait(); } +void LockSlotA() { + unset_memory_mapped_mode(); + unlockFlash(); + send_command(Command::WriteEnable); + wait(); + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + ExternalFlashStatusRegister::StatusRegister2 currentStatusRegister2(0); + send_read_command(Command::ReadStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(¤tStatusRegister2), sizeof(currentStatusRegister2)); + statusRegister2.setQE(currentStatusRegister2.getQE()); + statusRegister2.setSRP1(true); + statusRegister1.setTB(true); + statusRegister1.setBP2(true); + statusRegister1.setBP1(true); + uint8_t registers[] = {statusRegister1.get(), statusRegister2.get()}; + send_write_command(Command::WriteStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(registers), sizeof(registers), sOperatingModes101); + wait(); + set_as_memory_mapped(); +} + +void LockSlotB() { + unset_memory_mapped_mode(); + unlockFlash(); + send_command(Command::WriteEnable); + wait(); + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + ExternalFlashStatusRegister::StatusRegister2 currentStatusRegister2(0); + send_read_command(Command::ReadStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(¤tStatusRegister2), sizeof(currentStatusRegister2)); + statusRegister2.setQE(currentStatusRegister2.getQE()); + statusRegister2.setSRP1(true); + statusRegister1.setTB(false); + statusRegister1.setBP2(true); + statusRegister1.setBP1(true); + uint8_t registers[] = {statusRegister1.get(), statusRegister2.get()}; + send_write_command(Command::WriteStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(registers), sizeof(registers), sOperatingModes101); + wait(); + set_as_memory_mapped(); +} + void MassErase() { if (Config::NumberOfSectors == 0) { return; diff --git a/ion/src/device/bootloader/drivers/external_flash_tramp.cpp b/ion/src/device/bootloader/drivers/external_flash_tramp.cpp index 2116c5034ff..6ead8bc270d 100644 --- a/ion/src/device/bootloader/drivers/external_flash_tramp.cpp +++ b/ion/src/device/bootloader/drivers/external_flash_tramp.cpp @@ -381,6 +381,12 @@ void EraseSector(int i) { asm("cpsie if"); } +void LockSlotA() { +} + +void LockSlotB() { +} + } } } diff --git a/bootloader/usb_desc.cpp b/ion/src/device/bootloader/drivers/usb_desc.cpp similarity index 100% rename from bootloader/usb_desc.cpp rename to ion/src/device/bootloader/drivers/usb_desc.cpp diff --git a/ion/src/device/bootloader/internal_flash.ld b/ion/src/device/bootloader/internal_flash.ld new file mode 100644 index 00000000000..6a2555c3567 --- /dev/null +++ b/ion/src/device/bootloader/internal_flash.ld @@ -0,0 +1,131 @@ +/* Same as flash.ld but everything is linked in internal flash */ + +MEMORY { + INTERNAL_FLASH (rx) : ORIGIN = 0x00200000, LENGTH = 64K + SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K +} + +STACK_SIZE = 32K; +TRAMPOLINES_OFFSET = 0xE000; +CUSTOM_TRAMPOLINES_OFFSET = 64K - 64; + +SECTIONS { + .isr_vector_table ORIGIN(INTERNAL_FLASH) : { + /* When booting, the STM32F412 fetches the content of address 0x0, and + * extracts from it various key infos: the initial value of the PC register + * (program counter), the initial value of the stack pointer, and various + * entry points to interrupt service routines. This data is called the ISR + * vector table. + * + * Note that address 0x0 is always an alias. It points to the beginning of + * Flash, SRAM, or integrated bootloader depending on the boot mode chosen. + * (This mode is chosen by setting the BOOTn pins on the chip). + * + * We're generating the ISR vector table in code because it's very + * convenient: using function pointers, we can easily point to the service + * routine for each interrupt. */ + KEEP(*(.isr_vector_table)) + } >INTERNAL_FLASH + + .header : { + KEEP(*(.header)) + } >INTERNAL_FLASH + + .rodata : { + . = ALIGN(4); + *(.rodata) + *(.rodata.*) + } >INTERNAL_FLASH + + .exam_mode_buffer : { + _exam_mode_buffer_start = .; + KEEP(*(.exam_mode_buffer)) + /* We don't set it because we will not use it */ + /* . = ORIGIN(INTERNAL_FLASH) + FLASH_SECOND_SECTOR_OFFSET + FLASH_SECOND_SECTOR_SIZE; */ + _exam_mode_buffer_end = .; + } >INTERNAL_FLASH + + .fake_isr_function : { + . = ALIGN(4); + _fake_isr_function_start = .; + KEEP(*(.fake_isr_function)) + KEEP(*(.fake_isr_function.*)) + _fake_isr_function_end = .; + } + + .text : { + . = ALIGN(4); + *(.text) + *(.text.*) + } >INTERNAL_FLASH + + .init_array : { + . = ALIGN(4); + _init_array_start = .; + KEEP (*(.init_array*)) + _init_array_end = .; + } >INTERNAL_FLASH + + .data : { + /* The data section is written to Flash but linked as if it were in RAM. + * + * This is required because its initial value matters (so it has to be in + * persistant memory in the first place), but it is a R/W area of memory + * so it will have to live in RAM upon execution (in linker lingo, that + * translates to the data section having a LMA in Flash and a VMA in RAM). + * + * This means we'll have to copy it from Flash to RAM on initialization. + * To do this, we'll need to know the source location of the data section + * (in Flash), the target location (in RAM), and the size of the section. + * That's why we're defining three symbols that we'll use in the initial- + * -ization routine. */ + . = ALIGN(4); + _data_section_start_flash = LOADADDR(.data); + _data_section_start_ram = .; + *(.data) + *(.data.*) + _data_section_end_ram = .; + } >SRAM AT> INTERNAL_FLASH + + .trampolines_table : { + . = ORIGIN(INTERNAL_FLASH) + TRAMPOLINES_OFFSET; + KEEP(*(.trampolines_table)); + } > INTERNAL_FLASH + + .custom_trampolines_table : { + . = ORIGIN(INTERNAL_FLASH) + CUSTOM_TRAMPOLINES_OFFSET; + KEEP(*(.custom_trampolines_table)); + } > INTERNAL_FLASH + + .bss : { + /* The bss section contains data for all uninitialized variables + * So like the .data section, it will go in RAM, but unlike the data section + * we don't care at all about an initial value. + * + * Before execution, crt0 will erase that section of memory though, so we'll + * need pointers to the beginning and end of this section. */ + . = ALIGN(4); + _bss_section_start_ram = .; + *(.bss) + *(.bss.*) + /* The compiler may choose to allocate uninitialized global variables as + * COMMON blocks. This can be disabled with -fno-common if needed. */ + *(COMMON) + _bss_section_end_ram = .; + } >SRAM + + .heap : { + _heap_start = .; + /* Note: We don't increment "." here, we set it. */ + . = (ORIGIN(SRAM) + LENGTH(SRAM) - STACK_SIZE); + _heap_end = .; + } >SRAM + + .stack : { + . = ALIGN(8); + _stack_end = .; + . += (STACK_SIZE - 8); + . = ALIGN(8); + _stack_start = .; + } >SRAM +} diff --git a/ion/src/device/bootloader/platform_info.cpp b/ion/src/device/bootloader/platform_info.cpp index c1f83e36a7f..3b08c186033 100644 --- a/ion/src/device/bootloader/platform_info.cpp +++ b/ion/src/device/bootloader/platform_info.cpp @@ -13,6 +13,10 @@ #error This file expects OMEGA_VERSION to be defined #endif +#ifndef UPSILON_VERSION +#error This file expects UPSILON_VERSION to be defined +#endif + namespace Ion { extern char staticStorageArea[]; } @@ -57,37 +61,54 @@ class UserlandHeader { m_externalAppsRAMStart(0xFFFFFFFF), m_externalAppsRAMEnd(0xFFFFFFFF), m_footer(Magic), - m_ohm_header(OmegaMagic), + m_omegaMagicHeader(OmegaMagic), m_omegaVersion{OMEGA_VERSION}, #ifdef OMEGA_USERNAME m_username{OMEGA_USERNAME}, #else m_username{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, #endif - m_ohm_footer(OmegaMagic) { } + m_omegaMagicFooter(OmegaMagic), + m_upsilonMagicHeader(UpsilonMagic), + m_UpsilonVersion{UPSILON_VERSION}, + m_osType(OSType), + m_upsilonMagicFooter(UpsilonMagic) { } const char * omegaVersion() const { assert(m_storageAddressRAM != nullptr); assert(m_storageSizeRAM != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_omegaVersion; } + const char * upsilonVersion() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_UpsilonVersion; + } const volatile char * username() const volatile { assert(m_storageAddressRAM != nullptr); assert(m_storageSizeRAM != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_username; } - + const void * storage_address() const { + return storageAddress; + } private: constexpr static uint32_t Magic = 0xDEC0EDFE; constexpr static uint32_t OmegaMagic = 0xEFBEADDE; + constexpr static uint32_t UpsilonMagic = 0x55707369; + constexpr static uint32_t OSType = 0x79827178; uint32_t m_header; const char m_expectedEpsilonVersion[8]; void * m_storageAddressRAM; @@ -99,10 +120,14 @@ class UserlandHeader { uint32_t m_externalAppsRAMStart; uint32_t m_externalAppsRAMEnd; uint32_t m_footer; - uint32_t m_ohm_header; + uint32_t m_omegaMagicHeader; const char m_omegaVersion[16]; const volatile char m_username[16]; - uint32_t m_ohm_footer; + uint32_t m_omegaMagicFooter; + uint32_t m_upsilonMagicHeader; + const char m_UpsilonVersion[16]; + uint32_t m_osType; + uint32_t m_upsilonMagicFooter; }; const UserlandHeader __attribute__((section(".userland_header"), used)) k_userlandHeader; @@ -113,7 +138,6 @@ class SlotInfo { SlotInfo() : m_header(Magic), m_footer(Magic) {} - void update() { m_header = Magic; m_kernelHeaderAddress = &k_kernelHeader; @@ -134,6 +158,10 @@ const char * Ion::omegaVersion() { return k_userlandHeader.omegaVersion(); } +const char * Ion::upsilonVersion() { + return k_userlandHeader.upsilonVersion(); +} + const volatile char * Ion::username() { return k_userlandHeader.username(); } @@ -146,6 +174,10 @@ const char * Ion::patchLevel() { return k_kernelHeader.patchLevel(); } +const void * Ion::storageAddress() { + return k_userlandHeader.storage_address(); +} + SlotInfo * slotInfo() { static SlotInfo __attribute__((used)) __attribute__((section(".slot_info"))) slotInformation; return &slotInformation; diff --git a/ion/src/device/bootloader/usb/Makefile b/ion/src/device/bootloader/usb/Makefile new file mode 100644 index 00000000000..f3d20253040 --- /dev/null +++ b/ion/src/device/bootloader/usb/Makefile @@ -0,0 +1,99 @@ +# USB code + +ion_device_usb_src += $(addprefix ion/src/device/bootloader/usb/, \ + calculator.cpp \ + dfu_interface.cpp\ +) + +ion_device_usb_src += $(addprefix ion/src/device/bootloader/usb/stack/, \ + device.cpp\ + endpoint0.cpp \ + interface.cpp\ + request_recipient.cpp\ + setup_packet.cpp\ + streamable.cpp\ +) + +ion_device_usb_src += $(addprefix ion/src/device/bootloader/usb/stack/descriptor/, \ + bos_descriptor.cpp\ + configuration_descriptor.cpp \ + descriptor.cpp\ + device_descriptor.cpp\ + device_capability_descriptor.cpp\ + dfu_functional_descriptor.cpp\ + extended_compat_id_descriptor.cpp \ + interface_descriptor.cpp\ + language_id_string_descriptor.cpp \ + microsoft_os_string_descriptor.cpp\ + platform_device_capability_descriptor.cpp\ + string_descriptor.cpp\ + url_descriptor.cpp\ + webusb_platform_descriptor.cpp\ +) + +# DFU code + +ion_device_dfu_src += liba/src/abs.c +ion_device_dfu_src += liba/src/assert.c +ion_device_dfu_src += liba/src/strlen.c +ion_device_dfu_src += liba/src/strlcpy.c +ion_device_dfu_src += liba/src/memset.c +ion_device_dfu_src += liba/src/memcpy.c +ion_device_dfu_src += libaxx/src/cxxabi/pure_virtual.cpp +ion_device_dfu_src += ion/src/device/bootloader/usb/boot.cpp +ion_device_dfu_src += ion/src/device/n0110/drivers/board.cpp +ion_device_dfu_src += ion/src/device/n0110/drivers/cache.cpp +ion_device_dfu_src += ion/src/device/n0110/drivers/external_flash.cpp +ion_device_dfu_src += ion/src/device/n0110/drivers/reset.cpp +ion_device_dfu_src += ion/src/device/n0110/drivers/usb.cpp +ion_device_dfu_src += $(addprefix ion/src/device/shared/drivers/, \ + backlight.cpp \ + battery.cpp \ + base64.cpp \ + board.cpp \ + console_uart.cpp \ + crc32.cpp \ + display.cpp \ + events_keyboard_platform.cpp \ + flash.cpp \ + internal_flash.cpp \ + keyboard.cpp \ + led.cpp \ + power.cpp\ + random.cpp\ + reset.cpp \ + serial_number.cpp \ + swd.cpp \ + timing.cpp \ + usb.cpp \ + usb_desc.cpp \ + wakeup.cpp \ +) + +# Sources required to execute DFU in place +ion_device_src += ion/src/device/bootloader/usb/dfu_xip.cpp:+usbxip +ion_device_src += $(addsuffix :+usbxip,$(ion_device_usb_src)) + +# Sources required to execute DFU in RAM + +$(BUILD_DIR)/ion/src/device/bootloader/usb/dfu.elf: LDSCRIPT = ion/src/device/bootloader/usb/dfu.ld +$(BUILD_DIR)/ion/src/device/bootloader/usb/dfu.elf: $(call object_for,$(ion_device_usb_src) $(ion_device_dfu_src)) + +# In order to link the dfu bootloader inside the epsilon firmware, we need to +# turn the dfu binary (dfu.bin) into an elf object. +# By default, 'objcpy' generates a section 'data' and two symbols to the +# start and the end of the binary input respectively named: +# - '_binary_[file name]_[file extension]_start' +# - '_binary_[file name]_[file extension]_end'. +# For our purpose, dfu.o can go in rodata section and we rename the start and +# end of binary symbols: _dfu_bootloader_flash_[start/end] +$(BUILD_DIR)/ion/src/device/bootloader/usb/dfu.o: $(BUILD_DIR)/ion/src/device/bootloader/usb/dfu.bin + $(call rule_label,OBJCOPY) + $(Q) cd $(dir $<) ; $(OBJCOPY) -I binary -O elf32-littlearm -B arm --rename-section .data=.rodata.dfu_bootloader --redefine-sym _binary_dfu_bin_start=_dfu_bootloader_flash_start --redefine-sym _binary_dfu_bin_end=_dfu_bootloader_flash_end $(notdir $<) $(notdir $@) + +ion_device_src += ion/src/device/bootloader/usb/dfu.cpp:-usbxip +ion_device_src += ion/src/device/bootloader/usb/dfu_relocated.cpp:-usbxip + +ion_device_src += $(addprefix ion/src/device/bootloader/drivers/, \ + usb_desc.cpp \ +) diff --git a/ion/src/device/bootloader/usb/boot.cpp b/ion/src/device/bootloader/usb/boot.cpp new file mode 100644 index 00000000000..0eb8d71d608 --- /dev/null +++ b/ion/src/device/bootloader/usb/boot.cpp @@ -0,0 +1,2 @@ +extern "C" void abort() { +} diff --git a/ion/src/device/bootloader/usb/calculator.cpp b/ion/src/device/bootloader/usb/calculator.cpp new file mode 100644 index 00000000000..db8d8df3dde --- /dev/null +++ b/ion/src/device/bootloader/usb/calculator.cpp @@ -0,0 +1,94 @@ +#include "calculator.h" +#include +#include +#include +#include + +namespace Ion { +namespace Device { +namespace USB { + +void Calculator::PollAndReset(bool exitWithKeyboard, void * data) { + char serialNumber[Ion::Device::SerialNumber::Length+1]; + Ion::Device::SerialNumber::copy(serialNumber); + + Calculator c(serialNumber, data == nullptr ? stringDescriptor() : static_cast(data)->stringDescriptor(), data == nullptr ? "Upsilon Bootloader" : static_cast(data)->getName()); + + if (data != nullptr) { + c.setConfigData(static_cast(data)); + } + + /* Leave DFU mode if the Back key is pressed, the calculator unplugged or the + * USB core soft-disconnected. */ + Ion::Keyboard::Key exitKey = Ion::Keyboard::Key::Back; + uint8_t exitKeyRow = Ion::Device::Keyboard::rowForKey(exitKey); + uint8_t exitKeyColumn = Ion::Device::Keyboard::columnForKey(exitKey); + + Ion::Device::Keyboard::activateRow(exitKeyRow); + + while (!(exitWithKeyboard && !c.isErasingAndWriting() && Ion::Device::Keyboard::columnIsActive(exitKeyColumn)) && + Ion::USB::isPlugged() && + !c.isSoftDisconnected()) { + c.poll(); + } + if (!c.isSoftDisconnected()) { + c.detach(); + } + if (c.resetOnDisconnect()) { + c.leave(c.addressPointer()); + } +} + +Descriptor * Calculator::descriptor(uint8_t type, uint8_t index) { + /* Special case: Microsoft OS String Descriptor should be returned when + * searching for string descriptor at index 0xEE. */ + if (type == m_microsoftOSStringDescriptor.type() && index == 0xEE) { + return &m_microsoftOSStringDescriptor; + } + int typeCount = 0; + for (size_t i=0; itype() != type) { + continue; + } + if (typeCount == index) { + return descriptor; + } else { + typeCount++; + } + } + return nullptr; +} + +bool Calculator::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (Device::processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { + return true; + } + if (request->requestType() == SetupPacket::RequestType::Vendor) { + if (request->bRequest() == k_webUSBVendorCode && request->wIndex() == 2) { + // This is a WebUSB, GET_URL request + assert(request->wValue() == k_webUSBLandingPageIndex); + return getURLCommand(transferBuffer, transferBufferLength, transferBufferMaxLength); + } + if (request->bRequest() == k_microsoftOSVendorCode && request->wIndex() == 0x0004) { + // This is a Microsoft OS descriptor, Extended Compat ID request + assert(request->wValue() == 0); + return getExtendedCompatIDCommand(transferBuffer, transferBufferLength, transferBufferMaxLength); + } + } + return false; +} + +bool Calculator::getURLCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = m_workshopURLDescriptor.copy(transferBuffer, transferBufferMaxLength); + return true; +} + +bool Calculator::getExtendedCompatIDCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = m_extendedCompatIdDescriptor.copy(transferBuffer, transferBufferMaxLength); + return true; +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/calculator.h b/ion/src/device/bootloader/usb/calculator.h new file mode 100644 index 00000000000..68e967cf8fa --- /dev/null +++ b/ion/src/device/bootloader/usb/calculator.h @@ -0,0 +1,176 @@ +#ifndef ION_DEVICE_SHARED_USB_CALCULATOR_H +#define ION_DEVICE_SHARED_USB_CALCULATOR_H + +#include +#include +#include +#include +#include "dfu_interface.h" +#include "stack/device.h" +#include "stack/descriptor/bos_descriptor.h" +#include "stack/descriptor/configuration_descriptor.h" +#include "stack/descriptor/descriptor.h" +#include "stack/descriptor/device_descriptor.h" +#include "stack/descriptor/dfu_functional_descriptor.h" +#include "stack/descriptor/extended_compat_id_descriptor.h" +#include "stack/descriptor/interface_descriptor.h" +#include "stack/descriptor/language_id_string_descriptor.h" +#include "stack/descriptor/microsoft_os_string_descriptor.h" +#include "stack/descriptor/string_descriptor.h" +#include "stack/descriptor/url_descriptor.h" +#include "stack/descriptor/webusb_platform_descriptor.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +class Calculator : public Device { +public: + static void PollAndReset(bool exitWithKeyboard, void * data) + __attribute__((section(".dfu_entry_point"))) // Needed to pinpoint this symbol in the linker script + __attribute__((used)) // Make sure this symbol is not discarded at link time + ; // Return true if reset is needed + Calculator(const char * serialNumber, const char * desc, const char * product) : + Device(&m_dfuInterface), + m_usbConfig(nullptr), + m_deviceDescriptor( + 0x0210, /* bcdUSB: USB Specification Number which the device complies + * to. Must be greater than 0x0200 to use the BOS. */ + 0, // bDeviceClass: The class is defined by the interface. + 0, // bDeviceSUBClass: The subclass is defined by the interface. + 0, // bDeviceProtocol: The protocol is defined by the interface. + 64, // bMaxPacketSize0: Maximum packet size for endpoint 0 + 0x0483, // idVendor + 0xA291, // idProduct + 0x0100, // bcdDevice: Device Release Number + 1, // iManufacturer: Index of the manufacturer name string, see m_descriptor + 2, // iProduct: Index of the product name string, see m_descriptor + 3, // iSerialNumber: Index of the SerialNumber string, see m_descriptor + 1), // bNumConfigurations + m_dfuFunctionalDescriptor( + 0b0011, /* bmAttributes: + * - bitWillDetach: If true, the device will perform a bus + * detach-attach sequence when it receives a DFU_DETACH + * request. The host must not issue a USB Reset. + * - bitManifestationTolerant: if true, the device is able to + * communicate via USB after Manifestation phase. The + * manifestation phase implies a reset in the calculator, so, + * even if the device is still plugged, it needs to be + * re-enumerated to communicate. + * - bitCanUpload + * - bitCanDnload */ + 0, /* wDetachTimeOut: Time, in milliseconds, that the device in APP + * mode will wait after receipt of the DFU_DETACH request before + * switching to DFU mode. It does not apply to the calculator.*/ + 2048, // wTransferSize: Maximum number of bytes that the device can accept per control-write transaction + 0x0100),// bcdDFUVersion + m_interfaceDescriptor( + 0, // bInterfaceNumber + k_dfuInterfaceAlternateSetting, // bAlternateSetting + 0, // bNumEndpoints: Other than endpoint 0 + 0xFE, // bInterfaceClass: DFU (https://www.usb.org/defined-class-codes) + 1, // bInterfaceSubClass: DFU + 2, // bInterfaceProtocol: DFU Mode (not DFU Runtime, which would be 1) + 4, // iInterface: Index of the Interface string, see m_descriptor + &m_dfuFunctionalDescriptor), + m_configurationDescriptor( + 9 + 9 + 9, // wTotalLength: configuration descriptor + interface descriptor + dfu functional descriptor lengths + 1, // bNumInterfaces + k_bConfigurationValue, // bConfigurationValue + 0, // iConfiguration: No string descriptor for the configuration + 0x80, /* bmAttributes: + * Bit 7: Reserved, set to 1 + * Bit 6: Self Powered + * Bit 5: Remote Wakeup (allows the device to wake up the host when the host is in suspend) + * Bit 4..0: Reserved, set to 0 */ + 0x32, // bMaxPower: half of the Maximum Power Consumption + &m_interfaceDescriptor), + m_webUSBPlatformDescriptor( + k_webUSBVendorCode, + k_webUSBLandingPageIndex), + m_bosDescriptor( + 5 + 24, // wTotalLength: BOS descriptor + webusb platform descriptor lengths + 1, // bNumDeviceCapabilities + &m_webUSBPlatformDescriptor), + m_languageStringDescriptor(), + m_manufacturerStringDescriptor("NumWorks"), + m_productStringDescriptor(product), + m_serialNumberStringDescriptor(serialNumber), + m_interfaceStringDescriptor(desc), + //m_interfaceStringDescriptor("@SRAM/0x20000000/01*256Ke"), + /* Switch to this descriptor to use dfu-util to write in the SRAM. + * FIXME Should be an alternate Interface. */ + m_microsoftOSStringDescriptor(k_microsoftOSVendorCode), + m_workshopURLDescriptor(URLDescriptor::Scheme::HTTPS, "getupsilon.web.app"), + m_extendedCompatIdDescriptor("WINUSB"), + m_descriptors{ + &m_deviceDescriptor, // Type = Device, Index = 0 + &m_configurationDescriptor, // Type = Configuration, Index = 0 + &m_languageStringDescriptor, // Type = String, Index = 0 + &m_manufacturerStringDescriptor, // Type = String, Index = 1 + &m_productStringDescriptor, // Type = String, Index = 2 + &m_serialNumberStringDescriptor, // Type = String, Index = 3 + &m_interfaceStringDescriptor, // Type = String, Index = 4 + &m_bosDescriptor // Type = BOS, Index = 0 + }, + m_dfuInterface(this, &m_ep0, k_dfuInterfaceAlternateSetting) + { + } + uint32_t addressPointer() const { return m_dfuInterface.addressPointer(); } + bool isErasingAndWriting() const { return m_dfuInterface.isErasingAndWriting(); } + + void setConfigData(Bootloader::USBData * data) { m_usbConfig = data; m_dfuInterface.setDfuConfig(data->getData()); } + Bootloader::USBData * getConfigData() const { return m_usbConfig; } +protected: + Descriptor * descriptor(uint8_t type, uint8_t index) override; + void setActiveConfiguration(uint8_t configurationIndex) override { + assert(configurationIndex == k_bConfigurationValue); + } + uint8_t getActiveConfiguration() override { + return k_bConfigurationValue; + } + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; + +private: + static constexpr uint8_t k_bConfigurationValue = 1; + static constexpr uint8_t k_dfuInterfaceAlternateSetting = 0; + static constexpr uint8_t k_webUSBVendorCode = 1; + static constexpr uint8_t k_webUSBLandingPageIndex = 1; + static constexpr uint8_t k_microsoftOSVendorCode = 2; + + // WebUSB and MicrosoftOSDescriptor commands + bool getURLCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool getExtendedCompatIDCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + + // Descriptors + Bootloader::USBData * m_usbConfig; + DeviceDescriptor m_deviceDescriptor; + DFUFunctionalDescriptor m_dfuFunctionalDescriptor; + InterfaceDescriptor m_interfaceDescriptor; + ConfigurationDescriptor m_configurationDescriptor; + WebUSBPlatformDescriptor m_webUSBPlatformDescriptor; + BOSDescriptor m_bosDescriptor; + LanguageIDStringDescriptor m_languageStringDescriptor; + StringDescriptor m_manufacturerStringDescriptor; + StringDescriptor m_productStringDescriptor; + StringDescriptor m_serialNumberStringDescriptor; + StringDescriptor m_interfaceStringDescriptor; + MicrosoftOSStringDescriptor m_microsoftOSStringDescriptor; + URLDescriptor m_workshopURLDescriptor; + ExtendedCompatIDDescriptor m_extendedCompatIdDescriptor; + + Descriptor * m_descriptors[8]; + /* m_descriptors contains only descriptors that should be returned via the + * method descriptor(uint8_t type, uint8_t index), so do not count descriptors + * included in other descriptors or returned by other functions. */ + + // Interface + DFUInterface m_dfuInterface; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/dfu.ld b/ion/src/device/bootloader/usb/dfu.ld new file mode 100644 index 00000000000..957bcfdd536 --- /dev/null +++ b/ion/src/device/bootloader/usb/dfu.ld @@ -0,0 +1,57 @@ +/* DFU transfers can serve two purposes: + * - Transfering RAM data between the machine and the host, e.g. Python scripts + * - Upgrading the flash memory to perform a software update + * + * The second case raises a huge issue: code cannot be executed from memory that + * is being modified. We're solving this issue by copying the DFU code in RAM. + * + * This linker script will generate some code that expects to be executed from a + * fixed address in RAM. The corresponding instructions will be embedded in the + * main Epsilon ELF file, and copied to that address before execution. + * + * This address needs to live in RAM, and needs to be temporarily overwriteable + * when the program is being run. Epsilon has a large stack to allow deeply + * recursive code to run. But when doing DFU transfers it is safe to assume we + * will need very little stack space. We're therefore using the topmost 8K of + * the stack reserved by Epsilon. + * + * Last but not least, we'll want to jump to a known entry point when running + * the DFU code (namely, Ion::USB::Device::Calculator::Poll). We're simply + * making sure this is the first symbol output. */ + +EPSILON_STACK_END = 0x20000000 + 256K - 32K; + +MEMORY { + RAM_BUFFER (rw) : ORIGIN = EPSILON_STACK_END, LENGTH = 9K +} + +SECTIONS { + .text : { + . = ALIGN(4); + KEEP(*(.dfu_entry_point)) + *(.text) + *(.text.*) + } >RAM_BUFFER + + .rodata : { + *(.rodata) + *(.rodata.*) + } >RAM_BUFFER + + .data : { + /* We need to keep these symbols. */ + *(.data._ZN3Ion6Device13ExternalFlashL14sOperatingModeE) + *(.data._ZN3Ion6Device5BoardL18sStandardFrequencyE) + } >RAM_BUFFER + + /DISCARD/ : { + /* For now, we do not need .bss and .data sections. This allows us to simply + * skip any rt0-style initialization and jump straight into the PollAndReset + * routine. */ + *(.bss) + *(.bss.*) + *(.data) + *(.data.*) + } +} + diff --git a/ion/src/device/bootloader/usb/dfu_interface.cpp b/ion/src/device/bootloader/usb/dfu_interface.cpp new file mode 100644 index 00000000000..05e86f4edd2 --- /dev/null +++ b/ion/src/device/bootloader/usb/dfu_interface.cpp @@ -0,0 +1,301 @@ +#include "dfu_interface.h" +#include +#include +#include + +namespace Ion { +namespace Device { +namespace USB { + +static inline uint32_t minUint32T(uint32_t x, uint32_t y) { return x < y ? x : y; } + +void DFUInterface::StatusData::push(Channel * c) const { + c->push(m_bStatus); + c->push(m_bwPollTimeout[2]); + c->push(m_bwPollTimeout[1]); + c->push(m_bwPollTimeout[0]); + c->push(m_bState); + c->push(m_iString); +} + +void DFUInterface::StateData::push(Channel * c) const { + c->push(m_bState); +} + +void DFUInterface::wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) { + if (request->bRequest() == (uint8_t) DFURequest::Download) { + // Handle a download request + if (request->wValue() == 0) { + // The request is a special command + switch (transferBuffer[0]) { + case (uint8_t) DFUDownloadCommand::SetAddressPointer: + setAddressPointerCommand(request, transferBuffer, *transferBufferLength); + return; + case (uint8_t) DFUDownloadCommand::Erase: + eraseCommand(transferBuffer, *transferBufferLength); + return; + default: + m_state = State::dfuERROR; + m_status = Status::errSTALLEDPKT; + return; + } + } + if (request->wValue() == 1) { + m_ep0->stallTransaction(); + return; + } + if (request->wLength() > 0) { + // The request is a "real" download. Compute the writing address. + m_writeAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer; + // Store the received data until we copy it on the flash. + memcpy(m_largeBuffer, transferBuffer, *transferBufferLength); + m_largeBufferLength = *transferBufferLength; + m_state = State::dfuDNLOADSYNC; + } + } +} + +void DFUInterface::wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) { + if (request->bRequest() == (uint8_t) DFURequest::GetStatus) { + // Do any needed action after the GetStatus request. + if (m_state == State::dfuMANIFEST) { + /* If we leave the DFU and reset immediately, dfu-util outputs an error: + * "File downloaded successfully + * dfu-util: Error during download get_status" + * If we sleep 1us here, there is no error. We put 1ms for security. + * This error might be due to the USB connection being cut too soon after + * the last USB exchange, so the host does not have time to process the + * answer received for the last GetStatus request. */ + Ion::Timing::msleep(1); + // Leave DFU routine: Leave DFU, reset device, jump to application code + leaveDFUAndReset(); + } else if (m_state == State::dfuDNBUSY) { + if (m_largeBufferLength != 0) { + // Here, copy the data from the transfer buffer to the flash memory + writeOnMemory(); + } + changeAddressPointerIfNeeded(); + eraseMemoryIfNeeded(); + m_state = State::dfuDNLOADIDLE; + } + } +} + +bool DFUInterface::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (Interface::processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { + return true; + } + switch (request->bRequest()) { + case (uint8_t) DFURequest::Detach: + m_device->detach(); + return true; + case (uint8_t) DFURequest::Download: + return processDownloadRequest(request->wLength(), transferBufferLength); + case (uint8_t) DFURequest::Upload: + return processUploadRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::GetStatus: + return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::ClearStatus: + return clearStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::GetState: + return getState(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::Abort: + return dfuAbort(transferBufferLength); + } + return false; +} + +bool DFUInterface::processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength) { + if (m_state != State::dfuIDLE && m_state != State::dfuDNLOADIDLE) { + m_state = State::dfuERROR; + m_status = Status::errNOTDONE; + m_ep0->stallTransaction(); + return false; + } + if (wLength == 0) { + // Leave DFU routine: Reset the device and jump to application code + m_state = State::dfuMANIFESTSYNC; + } else { + // Prepare to receive the download data + m_ep0->clearForOutTransactions(wLength); + m_state = State::dfuDNLOADSYNC; + } + return true; +} + +bool DFUInterface::processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (m_state != State::dfuIDLE && m_state != State::dfuUPLOADIDLE) { + m_ep0->stallTransaction(); + return false; + } + if (request->wValue() == 0) { + /* The host requests to read the commands supported by the bootloader. After + * receiving this command, the device should returns N bytes representing + * the command codes for : + * Get command / Set Address Pointer / Erase / Read Unprotect + * We no not need it for now. */ + return false; + } else if (request->wValue() == 1) { + m_ep0->stallTransaction(); + return false; + } else { + /* We decided to never protect Read operation. Else we would have to check + * here it is not protected before reading. */ + + // Compute the reading address + uint32_t readAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer; + // Copy the requested memory zone into the transfer buffer. + uint16_t copySize = minUint32T(transferBufferMaxLength, request->wLength()); + memcpy(transferBuffer, (void *)readAddress, copySize); + *transferBufferLength = copySize; + } + m_state = State::dfuUPLOADIDLE; + return true; +} + +void DFUInterface::setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength) { + assert(transferBufferLength == 5); + // Compute the new address but change it after the next getStatus request. + m_potentialNewAddressPointer = transferBuffer[1] + + (transferBuffer[2] << 8) + + (transferBuffer[3] << 16) + + (transferBuffer[4] << 24); + m_state = State::dfuDNLOADSYNC; +} + +void DFUInterface::changeAddressPointerIfNeeded() { + if (m_potentialNewAddressPointer == 0) { + // There was no address change waiting. + return; + } + // If there is a new address pointer waiting, change the pointer address. + m_addressPointer = m_potentialNewAddressPointer; + m_potentialNewAddressPointer = 0; + m_state = State::dfuDNLOADIDLE; + m_status = Status::OK; +} + +void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength) { + /* We determine whether the commands asks for a mass erase or which sector to + * erase. The erase must be done after the next getStatus request. */ + m_state = State::dfuDNLOADSYNC; + + if (transferBufferLength == 1) { + // Mass erase + m_erasePage = Flash::TotalNumberOfSectors(); + return; + } + + // Sector erase + assert(transferBufferLength == 5); + + m_eraseAddress = transferBuffer[1] + + (transferBuffer[2] << 8) + + (transferBuffer[3] << 16) + + (transferBuffer[4] << 24); + + m_erasePage = Flash::SectorAtAddress(m_eraseAddress); + if (m_erasePage < 0) { + // Unrecognized sector + m_state = State::dfuERROR; + m_status = Status::errTARGET; + } +} + + +void DFUInterface::eraseMemoryIfNeeded() { + if (m_erasePage < 0) { + // There was no erase waiting. + return; + } + + willErase(); + + Bootloader::ProtectionState config = getDfuConfig(); + + // More simple to read + if ((0x08000000 <= m_eraseAddress && m_eraseAddress <= 0x08010000)&& !m_dfuData.isProtectedInternal()) { + Flash::EraseSector(m_erasePage); + } else if ((0x90000000 <= m_eraseAddress && m_eraseAddress <= 0x90800000)&& !m_dfuData.isProtectedExternal()) { + Flash::EraseSector(m_erasePage); + } + /* Put an out of range value in m_erasePage to indicate that no erase is + * waiting. */ + m_erasePage = -1; + m_state = State::dfuDNLOADIDLE; + m_status = Status::OK; +} + +void DFUInterface::writeOnMemory() { + if (m_writeAddress >= k_sramStartAddress && m_writeAddress <= k_sramEndAddress) { + // Write on SRAM + // FIXME We should check that we are not overriding the current instructions. + memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); + } else if (Flash::SectorAtAddress(m_writeAddress) >= 0) { + + Bootloader::ProtectionState config = getDfuConfig(); + + if (m_writeAddress >= 0x08000000 && m_writeAddress <= 0x08010000 && !m_dfuData.isProtectedInternal()) { + Flash::WriteMemory(reinterpret_cast(m_writeAddress), m_largeBuffer, m_largeBufferLength); + } else if (m_writeAddress >= 0x90000000 && m_writeAddress <= 0x90800000 && !m_dfuData.isProtectedExternal()) { + Flash::WriteMemory(reinterpret_cast(m_writeAddress), m_largeBuffer, m_largeBufferLength); + } + + } else { + // Invalid write address + m_largeBufferLength = 0; + m_state = State::dfuERROR; + m_status = Status::errTARGET; + return; + } + + // Reset the buffer length + m_largeBufferLength = 0; + // Change the interface state and status + m_state = State::dfuDNLOADIDLE; + m_status = Status::OK; +} + + +bool DFUInterface::getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + // Change the status if needed + if (m_state == State::dfuMANIFESTSYNC) { + m_state = State::dfuMANIFEST; + } else if (m_state == State::dfuDNLOADSYNC) { + m_state = State::dfuDNBUSY; + } + // Copy the status on the TxFifo + *transferBufferLength = StatusData(m_status, m_state).copy(transferBuffer, transferBufferMaxLength); + return true; +} + +bool DFUInterface::clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + m_status = Status::OK; + m_state = State::dfuIDLE; + return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); +} + +bool DFUInterface::getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize) { + *transferBufferLength = StateData(m_state).copy(transferBuffer, maxSize); + return true; +} + +bool DFUInterface::dfuAbort(uint16_t * transferBufferLength) { + m_status = Status::OK; + m_state = State::dfuIDLE; + *transferBufferLength = 0; + return true; +} + +void DFUInterface::leaveDFUAndReset() { + m_device->setResetOnDisconnect(true); + m_device->detach(); +} + +void DFUInterface::copyDfuData() { + m_dfuData = Bootloader::ProtectionState(!m_dfuConfig.isProtectedInternal(), !m_dfuConfig.isProtectedExternal()); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/dfu_interface.h b/ion/src/device/bootloader/usb/dfu_interface.h new file mode 100644 index 00000000000..4fa319bd990 --- /dev/null +++ b/ion/src/device/bootloader/usb/dfu_interface.h @@ -0,0 +1,192 @@ +#ifndef ION_DEVICE_SHARED_USB_DFU_INTERFACE_H +#define ION_DEVICE_SHARED_USB_DFU_INTERFACE_H + +#include +#include +#include "stack/device.h" +#include "stack/interface.h" +#include "stack/endpoint0.h" +#include "stack/setup_packet.h" +#include "stack/streamable.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +class DFUInterface : public Interface { + +public: + DFUInterface(Device * device, Endpoint0 * ep0, uint8_t bInterfaceAlternateSetting) : + Interface(ep0), + m_device(device), + m_status(Status::OK), + m_state(State::dfuIDLE), + m_addressPointer(0), + m_potentialNewAddressPointer(0), + m_erasePage(-1), + m_largeBuffer{0}, + m_largeBufferLength(0), + m_writeAddress(0), + m_bInterfaceAlternateSetting(bInterfaceAlternateSetting), + m_isErasingAndWriting(false), + m_dfuConfig(), + m_eraseAddress(0), + m_dfuData() + { + } + uint32_t addressPointer() const { return m_addressPointer; } + void wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) override; + void wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) override; + bool isErasingAndWriting() const { return m_isErasingAndWriting; } + + void setDfuConfig(Bootloader::ProtectionState data) { m_dfuConfig = data; copyDfuData(); } + Bootloader::ProtectionState getDfuConfig() const { return m_dfuConfig; } + +protected: + void setActiveInterfaceAlternative(uint8_t interfaceAlternativeIndex) override { + assert(interfaceAlternativeIndex == m_bInterfaceAlternateSetting); + } + uint8_t getActiveInterfaceAlternative() override { + return m_bInterfaceAlternateSetting; + } + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; + +private: + // DFU Request Codes + enum class DFURequest { + Detach = 0, + Download = 1, + Upload = 2, + GetStatus = 3, + ClearStatus = 4, + GetState = 5, + Abort = 6 + }; + + // DFU Download Commmand Codes + enum class DFUDownloadCommand { + GetCommand = 0x00, + SetAddressPointer = 0x21, + Erase = 0x41, + ReadUnprotect = 0x92 + }; + + enum class Status : uint8_t { + OK = 0x00, + errTARGET = 0x01, + errFILE = 0x02, + errWRITE = 0x03, + errERASE = 0x04, + errCHECK_ERASED = 0x05, + errPROG = 0x06, + errVERIFY = 0x07, + errADDRESS = 0x08, + errNOTDONE = 0x09, + errFIRMWARE = 0x0A, + errVENDOR = 0x0B, + errUSBR = 0x0C, + errPOR = 0x0D, + errUNKNOWN = 0x0E, + errSTALLEDPKT = 0x0F + }; + + enum class State : uint8_t { + appIDLE = 0, + appDETACH = 1, + dfuIDLE = 2, + dfuDNLOADSYNC = 3, + dfuDNBUSY = 4, + dfuDNLOADIDLE = 5, + dfuMANIFESTSYNC = 6, + dfuMANIFEST = 7, + dfuMANIFESTWAITRESET = 8, + dfuUPLOADIDLE = 9, + dfuERROR = 10 + }; + + class StatusData : public Streamable { + public: + StatusData(Status status, State state, uint32_t pollTimeout = 1) : + /* We put a default pollTimeout value of 1ms: if the device is busy, the + * host has to wait 1ms before sending a getStatus Request. */ + m_bStatus((uint8_t)status), + m_bwPollTimeout{uint8_t((pollTimeout>>16) & 0xFF), uint8_t((pollTimeout>>8) & 0xFF), uint8_t(pollTimeout & 0xFF)}, + m_bState((uint8_t)state), + m_iString(0) + { + } + protected: + void push(Channel * c) const override; + private: + uint8_t m_bStatus; // Status resulting from the execution of the most recent request + uint8_t m_bwPollTimeout[3]; // m_bwPollTimeout is 24 bits + uint8_t m_bState; // State of the device immediately following transmission of this response + uint8_t m_iString; + }; + + class StateData : public Streamable { + public: + StateData(State state) : m_bState((uint8_t)state) {} + protected: + void push(Channel * c) const override; + private: + uint8_t m_bState; // Current state of the device + }; + + /* The Flash and SRAM addresses are in flash.ld. However, dfu_interface is + * linked with dfu.ld, so we cannot access the values. */ + constexpr static uint32_t k_sramStartAddress = 0x20000000; + constexpr static uint32_t k_sramEndAddress = 0x20040000; + + // Download and upload + bool processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength); + bool processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + // Address pointer + void setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength); + void changeAddressPointerIfNeeded(); + // Access memory + void eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength); + void eraseMemoryIfNeeded(); + void writeOnMemory(); + void unlockFlashMemory(); + void lockFlashMemoryAndPurgeCaches(); + // Status + bool getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + // State + bool getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize); + // Abort + bool dfuAbort(uint16_t * transferBufferLength); + // Leave DFU + void leaveDFUAndReset(); + /* Erase and Write state. After starting the erase of flash memory, the user + * can no longer leave DFU mode by pressing the Back key of the keyboard. This + * way, we prevent the user from interrupting a software download. After every + * software download, the calculator resets, which unlocks the "exit on + * pressing back". */ + void willErase() { m_isErasingAndWriting = true; } + + void copyDfuData(); + + Device * m_device; + Status m_status; + State m_state; + uint32_t m_addressPointer; + uint32_t m_potentialNewAddressPointer; + int32_t m_erasePage; + uint8_t m_largeBuffer[Endpoint0::MaxTransferSize]; + uint16_t m_largeBufferLength; + uint32_t m_writeAddress; + uint8_t m_bInterfaceAlternateSetting; + bool m_isErasingAndWriting; + Bootloader::ProtectionState m_dfuConfig; + uint32_t m_eraseAddress; + Bootloader::ProtectionState m_dfuData; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/dfu_relocated.cpp b/ion/src/device/bootloader/usb/dfu_relocated.cpp new file mode 100644 index 00000000000..7669a31036d --- /dev/null +++ b/ion/src/device/bootloader/usb/dfu_relocated.cpp @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include +#include "../drivers/timing.h" + +extern const void * _stack_end; +extern char _dfu_bootloader_flash_start; +extern char _dfu_bootloader_flash_end; + +namespace Ion { +namespace USB { + +typedef void (*PollFunctionPointer)(bool exitWithKeyboard, void * data); + +void DFU(bool exitWithKeyboard, void * data) { + Ion::updateSlotInfo(); + + /* DFU transfers can serve two purposes: + * - Transfering RAM data between the machine and a host, e.g. Python scripts + * - Upgrading the flash memory to perform a software update + * + * The second case raises a huge issue: code cannot be executed from memory + * that is being modified. We're solving this issue by copying the DFU code in + * RAM. + * + * The new DFU address in RAM needs to be temporarily overwriteable when the + * program is being run. Epsilon has a large stack to allow deeply recursive + * code to run, but when doing DFU transfers it is safe to assume we will need + * very little stack space. We're therefore using the topmost 8K of the stack + * reserved by Epsilon. */ + + /* 1- The stack being in reverse order, the end of the stack will be the + * beginning of the DFU bootloader copied in RAM. */ + + size_t dfu_bootloader_size = &_dfu_bootloader_flash_end - &_dfu_bootloader_flash_start; + char * dfu_bootloader_ram_start = reinterpret_cast(&_stack_end); + assert(&_stack_end == (void *)(0x20000000 + 256*1024 - 32*1024)); + + /* 2- Verify there is enough free space on the stack to copy the DFU code. */ + + char foo; + char * stackPointer = &foo; + if (dfu_bootloader_ram_start + dfu_bootloader_size > stackPointer) { + // There is not enough room on the stack to copy the DFU bootloader. + return; + } + + /* 3- Copy the DFU bootloader from Flash to RAM. */ + + memcpy(dfu_bootloader_ram_start, &_dfu_bootloader_flash_start, dfu_bootloader_size); + /* The DFU bootloader might have been copied in the DCache. However, when we + * run the instructions from the DFU bootloader, the CPU looks for + * instructions in the ICache and then in the RAM. We thus need to flush the + * DCache to update the RAM. */ + // Flush data cache + Device::Cache::cleanDCache(); + + /* 4- Disable all interrupts + * The interrupt service routines live in the Flash and could be overwritten + * by garbage during a firmware upgrade opration, so we disable them. */ + Device::Timing::shutdown(); + + /* 5- Jump to DFU bootloader code. We made sure in the linker script that the + * first function we want to call is at the beginning of the DFU code. */ + + PollFunctionPointer dfu_bootloader_entry = reinterpret_cast(dfu_bootloader_ram_start); + + /* To have the right debug symbols for the reallocated code, break here and: + * - Get the address of the new .text section + * In a terminal: arm-none-eabi-readelf -a ion/src/device/usb/dfu.elf + * - Delete the current symbol table + * symbol-file + * - Add the new symbol table, with the address of the new .text section + * add-symbol-file ion/src/device/usb/dfu.elf 0x20038000 + */ + + dfu_bootloader_entry(exitWithKeyboard, data); + + /* 5- Restore interrupts */ + Device::Timing::init(); + + /* 6- That's all. The DFU bootloader on the stack is now dead code that will + * be overwritten when the stack grows. */ +} + +} +} diff --git a/ion/src/device/bootloader/usb/dfu_xip.cpp b/ion/src/device/bootloader/usb/dfu_xip.cpp new file mode 100644 index 00000000000..55500cf3cee --- /dev/null +++ b/ion/src/device/bootloader/usb/dfu_xip.cpp @@ -0,0 +1,13 @@ +#include +#include "calculator.h" + +namespace Ion { +namespace USB { + +void DFU(bool exitWithKeyboard, void * data) { + Ion::updateSlotInfo(); + Ion::Device::USB::Calculator::PollAndReset(exitWithKeyboard, data); +} + +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/bos_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/bos_descriptor.cpp new file mode 100644 index 00000000000..2099df6b439 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/bos_descriptor.cpp @@ -0,0 +1,22 @@ +#include "bos_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +void BOSDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_wTotalLength); + c->push(m_bNumDeviceCaps); + for (uint8_t i = 0; i < m_bNumDeviceCaps; i++) { + m_deviceCapabilities[i].push(c); + } +} + +uint8_t BOSDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint16_t) + sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/bos_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/bos_descriptor.h new file mode 100644 index 00000000000..25529c0e522 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/bos_descriptor.h @@ -0,0 +1,36 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_BOS_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_BOS_DESCRIPTOR_H + +#include "descriptor.h" +#include "device_capability_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class BOSDescriptor : public Descriptor { +public: + constexpr BOSDescriptor( + uint16_t wTotalLength, + uint8_t bNumDeviceCapabilities, + const DeviceCapabilityDescriptor * deviceCapabilities) : + Descriptor(0x0F), + m_wTotalLength(wTotalLength), + m_bNumDeviceCaps(bNumDeviceCapabilities), + m_deviceCapabilities(deviceCapabilities) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + uint16_t m_wTotalLength; + uint8_t m_bNumDeviceCaps; + const DeviceCapabilityDescriptor * m_deviceCapabilities; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/configuration_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/configuration_descriptor.cpp new file mode 100644 index 00000000000..c4d97a36811 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/configuration_descriptor.cpp @@ -0,0 +1,26 @@ +#include "configuration_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +void ConfigurationDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_wTotalLength); + c->push(m_bNumInterfaces); + c->push(m_bConfigurationValue); + c->push(m_iConfiguration); + c->push(m_bmAttributes); + c->push(m_bMaxPower); + for (uint8_t i = 0; i < m_bNumInterfaces; i++) { + m_interfaces[i].push(c); + } +} + +uint8_t ConfigurationDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint16_t) + 5*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/configuration_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/configuration_descriptor.h new file mode 100644 index 00000000000..9f994b6e3b7 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/configuration_descriptor.h @@ -0,0 +1,48 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_CONFIGURATION_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_CONFIGURATION_DESCRIPTOR_H + +#include "descriptor.h" +#include "interface_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class ConfigurationDescriptor : public Descriptor { +public: + constexpr ConfigurationDescriptor( + uint16_t wTotalLength, + uint8_t bNumInterfaces, + uint8_t bConfigurationValue, + uint8_t iConfiguration, + uint8_t bmAttributes, + uint8_t bMaxPower, + const InterfaceDescriptor * interfaces) : + Descriptor(0x02), + m_wTotalLength(wTotalLength), + m_bNumInterfaces(bNumInterfaces), + m_bConfigurationValue(bConfigurationValue), + m_iConfiguration(iConfiguration), + m_bmAttributes(bmAttributes), + m_bMaxPower(bMaxPower), + m_interfaces(interfaces) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + uint16_t m_wTotalLength; + uint8_t m_bNumInterfaces; + uint8_t m_bConfigurationValue; + uint8_t m_iConfiguration; + uint8_t m_bmAttributes; + uint8_t m_bMaxPower; + const InterfaceDescriptor * m_interfaces; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/descriptor.cpp new file mode 100644 index 00000000000..373388cb322 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/descriptor.cpp @@ -0,0 +1,15 @@ +#include "descriptor.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +void Descriptor::push(Channel * c) const { + c->push(bLength()); + c->push(m_bDescriptorType); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/descriptor.h new file mode 100644 index 00000000000..b413b3fe44c --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/descriptor.h @@ -0,0 +1,32 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DESCRIPTOR_H + +#include "../streamable.h" + +namespace Ion { +namespace Device { +namespace USB { + +class InterfaceDescriptor; + +class Descriptor : public Streamable { + friend class InterfaceDescriptor; +public: + constexpr Descriptor(uint8_t bDescriptorType) : + m_bDescriptorType(bDescriptorType) + { + } + uint8_t type() const { return m_bDescriptorType; } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const { return 2*sizeof(uint8_t); } +private: + uint8_t m_bDescriptorType; +}; + + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.cpp new file mode 100644 index 00000000000..8f5d6abb88b --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.cpp @@ -0,0 +1,18 @@ +#include "device_capability_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +void DeviceCapabilityDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bDeviceCapabilityType); +} + +uint8_t DeviceCapabilityDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.h new file mode 100644 index 00000000000..8b67cabc6ca --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/device_capability_descriptor.h @@ -0,0 +1,31 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABILITY_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABILITY_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class BOSDescriptor; + +class DeviceCapabilityDescriptor : public Descriptor { + friend class BOSDescriptor; +public: + constexpr DeviceCapabilityDescriptor(uint8_t bDeviceCapabilityType) : + Descriptor(0x10), + m_bDeviceCapabilityType(bDeviceCapabilityType) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + uint8_t m_bDeviceCapabilityType; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/device_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/device_descriptor.cpp new file mode 100644 index 00000000000..424e891e865 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/device_descriptor.cpp @@ -0,0 +1,29 @@ +#include "device_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +void DeviceDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bcdUSB); + c->push(m_bDeviceClass); + c->push(m_bDeviceSubClass); + c->push(m_bDeviceProtocol); + c->push(m_bMaxPacketSize0); + c->push(m_idVendor); + c->push(m_idProduct); + c->push(m_bcdDevice); + c->push(m_iManufacturer); + c->push(m_iProduct); + c->push(m_iSerialNumber); + c->push(m_bNumConfigurations); +} + +uint8_t DeviceDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint16_t) + 4*sizeof(uint8_t) + 3*sizeof(uint16_t) + 4*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/device_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/device_descriptor.h new file mode 100644 index 00000000000..3b526f43f7d --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/device_descriptor.h @@ -0,0 +1,62 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_DEVICE_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DEVICE_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class DeviceDescriptor : public Descriptor { +public: + constexpr DeviceDescriptor( + uint16_t bcdUSB, + uint8_t bDeviceClass, + uint8_t bDeviceSubClass, + uint8_t bDeviceProtocol, + uint8_t bMaxPacketSize0, + uint16_t idVendor, + uint16_t idProduct, + uint16_t bcdDevice, + uint8_t iManufacturer, + uint8_t iProduct, + uint8_t iSerialNumber, + uint8_t bNumConfigurations) : + Descriptor(0x01), + m_bcdUSB(bcdUSB), + m_bDeviceClass(bDeviceClass), + m_bDeviceSubClass(bDeviceSubClass), + m_bDeviceProtocol(bDeviceProtocol), + m_bMaxPacketSize0(bMaxPacketSize0), + m_idVendor(idVendor), + m_idProduct(idProduct), + m_bcdDevice(bcdDevice), + m_iManufacturer(iManufacturer), + m_iProduct(iProduct), + m_iSerialNumber(iSerialNumber), + m_bNumConfigurations(bNumConfigurations) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + uint16_t m_bcdUSB; + uint8_t m_bDeviceClass; + uint8_t m_bDeviceSubClass; + uint8_t m_bDeviceProtocol; + uint8_t m_bMaxPacketSize0; + uint16_t m_idVendor; + uint16_t m_idProduct; + uint16_t m_bcdDevice; + uint8_t m_iManufacturer; + uint8_t m_iProduct; + uint8_t m_iSerialNumber; + uint8_t m_bNumConfigurations; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/dfu_functional_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/dfu_functional_descriptor.cpp new file mode 100644 index 00000000000..414421d9b87 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/dfu_functional_descriptor.cpp @@ -0,0 +1,21 @@ +#include "dfu_functional_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +void DFUFunctionalDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bmAttributes); + c->push(m_wDetachTimeOut); + c->push(m_wTransferSize); + c->push(m_bcdDFUVersion); +} + +uint8_t DFUFunctionalDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint8_t) + 3*sizeof(uint16_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/dfu_functional_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/dfu_functional_descriptor.h new file mode 100644 index 00000000000..60439289542 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/dfu_functional_descriptor.h @@ -0,0 +1,38 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_DFU_FUNCTIONAL_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DFU_FUNCTIONAL_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class DFUFunctionalDescriptor : public Descriptor { +public: + constexpr DFUFunctionalDescriptor( + uint8_t bmAttributes, + uint16_t wDetachTimeOut, + uint16_t wTransferSize, + uint16_t bcdDFUVersion) : + Descriptor(0x21), + m_bmAttributes(bmAttributes), + m_wDetachTimeOut(wDetachTimeOut), + m_wTransferSize(wTransferSize), + m_bcdDFUVersion(bcdDFUVersion) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + uint8_t m_bmAttributes; + uint16_t m_wDetachTimeOut; + uint16_t m_wTransferSize; + uint16_t m_bcdDFUVersion; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/extended_compat_id_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/extended_compat_id_descriptor.cpp new file mode 100644 index 00000000000..024ac15526f --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/extended_compat_id_descriptor.cpp @@ -0,0 +1,61 @@ +#include "extended_compat_id_descriptor.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +ExtendedCompatIDDescriptor::ExtendedCompatIDDescriptor(const char * compatibleID) : + m_dwLength(sizeof(uint32_t) + + 2*sizeof(uint16_t) + + sizeof(uint8_t) + + k_reserved1Size * sizeof(uint8_t) + + 2*sizeof(uint8_t) + + k_compatibleIDSize * sizeof(uint8_t) + + k_compatibleIDSize * sizeof(uint8_t) + + k_reserved2Size * sizeof(uint8_t)), + m_bcdVersion(0x0100), // Microsoft OS Descriptors version 1 + m_wIndex(Index), + m_bCount(1), // We assume one function only. + m_reserved1{0, 0, 0, 0, 0, 0, 0}, + m_bFirstInterfaceNumber(0), + m_bReserved(1), + m_subCompatibleID{0, 0, 0, 0, 0, 0, 0, 0}, + m_reserved2{0, 0, 0, 0, 0, 0} +{ + /* Compatible ID has size k_compatibleIDSize, and any unused bytes should be + * filled with 0. */ + size_t compatibleIDSize = strlen(compatibleID); + size_t compatibleIDCopySize = k_compatibleIDSize < compatibleIDSize ? k_compatibleIDSize : compatibleIDSize; + for (size_t i = 0; i < compatibleIDCopySize; i++) { + m_compatibleID[i] = compatibleID[i]; + } + for (size_t i = compatibleIDCopySize; i < k_compatibleIDSize; i++) { + m_compatibleID[i] = 0; + } +} + +void ExtendedCompatIDDescriptor::push(Channel * c) const { + c->push(m_dwLength); + c->push(m_bcdVersion); + c->push(m_wIndex); + c->push(m_bCount); + for (uint8_t i = 0; i < k_reserved1Size; i++) { + c->push(m_reserved1[i]); + } + c->push(m_bFirstInterfaceNumber); + c->push(m_bReserved); + for (uint8_t i = 0; i < k_compatibleIDSize; i++) { + c->push(m_compatibleID[i]); + } + for (uint8_t i = 0; i < k_compatibleIDSize; i++) { + c->push(m_subCompatibleID[i]); + } + for (uint8_t i = 0; i < k_reserved2Size; i++) { + c->push(m_reserved2[i]); + } +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/extended_compat_id_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/extended_compat_id_descriptor.h new file mode 100644 index 00000000000..016beab978d --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/extended_compat_id_descriptor.h @@ -0,0 +1,43 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_EXTENDED_COMPAT_ID_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_EXTENDED_COMPAT_ID_DESCRIPTOR_H + +#include "../streamable.h" + +namespace Ion { +namespace Device { +namespace USB { + +/* We use this descriptor to tell the Windows OS that the device should be + * treated as a WinUSB device. The Extended Compat ID Descriptor can set + * differents compat IDs according to the interface and function of the device, + * but we assume there is only one. */ + +class ExtendedCompatIDDescriptor : public Streamable { +public: + static constexpr uint8_t Index = 0x0004; + ExtendedCompatIDDescriptor(const char * compatibleID); +protected: + void push(Channel * c) const override; +private: + constexpr static uint8_t k_reserved1Size = 7; + constexpr static uint8_t k_compatibleIDSize = 8; + constexpr static uint8_t k_reserved2Size = 6; + // Header + uint32_t m_dwLength; // The length, in bytes, of the complete extended compat ID descriptor + uint16_t m_bcdVersion; // The descriptor’s version number, in binary coded decimal format + uint16_t m_wIndex; // An index that identifies the particular OS feature descriptor + uint8_t m_bCount; // The number of function sections + uint8_t m_reserved1[k_reserved1Size]; + // Function + uint8_t m_bFirstInterfaceNumber; // The interface or function number + uint8_t m_bReserved; + uint8_t m_compatibleID[k_compatibleIDSize]; + uint8_t m_subCompatibleID[k_compatibleIDSize]; + uint8_t m_reserved2[k_reserved2Size]; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/interface_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/interface_descriptor.cpp new file mode 100644 index 00000000000..75f193aba06 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/interface_descriptor.cpp @@ -0,0 +1,27 @@ +#include "interface_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +void InterfaceDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bInterfaceNumber); + c->push(m_bAlternateSetting); + c->push(m_bNumEndpoints); + c->push(m_bInterfaceClass); + c->push(m_bInterfaceSubClass); + c->push(m_bInterfaceProtocol); + c->push(m_iInterface); + if (m_additionalDescriptor != nullptr) { + m_additionalDescriptor->push(c); + } +} + +uint8_t InterfaceDescriptor::bLength() const { + return Descriptor::bLength() + 7*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/interface_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/interface_descriptor.h new file mode 100644 index 00000000000..6a49bfb8a22 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/interface_descriptor.h @@ -0,0 +1,55 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_INTERFACE_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_INTERFACE_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class ConfigurationDescriptor; + +class InterfaceDescriptor : public Descriptor { + friend class ConfigurationDescriptor; +public: + constexpr InterfaceDescriptor( + uint8_t bInterfaceNumber, + uint8_t bAlternateSetting, + uint8_t bNumEndpoints, + uint8_t bInterfaceClass, + uint8_t bInterfaceSubClass, + uint8_t bInterfaceProtocol, + uint8_t iInterface, + Descriptor * additionalDescriptor) : + Descriptor(0x04), + m_bInterfaceNumber(bInterfaceNumber), + m_bAlternateSetting(bAlternateSetting), + m_bNumEndpoints(bNumEndpoints), + m_bInterfaceClass(bInterfaceClass), + m_bInterfaceSubClass(bInterfaceSubClass), + m_bInterfaceProtocol(bInterfaceProtocol), + m_iInterface(iInterface), + m_additionalDescriptor(additionalDescriptor) + /* There could be more than one additional descriptor, but we do not need + * this for now. */ + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + uint8_t m_bInterfaceNumber; + uint8_t m_bAlternateSetting; + uint8_t m_bNumEndpoints; + uint8_t m_bInterfaceClass; + uint8_t m_bInterfaceSubClass; + uint8_t m_bInterfaceProtocol; + uint8_t m_iInterface; + const Descriptor * m_additionalDescriptor; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/language_id_string_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/language_id_string_descriptor.cpp new file mode 100644 index 00000000000..8027fb75cbd --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/language_id_string_descriptor.cpp @@ -0,0 +1,19 @@ +#include "language_id_string_descriptor.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +void LanguageIDStringDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push((uint16_t)(0x0409)); +} + +uint8_t LanguageIDStringDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint16_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/language_id_string_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/language_id_string_descriptor.h new file mode 100644 index 00000000000..0f72abb91bb --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/language_id_string_descriptor.h @@ -0,0 +1,25 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_LANGUAGE_ID_STRING_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_LANGUAGE_ID_STRING_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +// For now this LanguageIDStringDescriptor only ever returns American English + +class LanguageIDStringDescriptor : public Descriptor { +public: + constexpr LanguageIDStringDescriptor() : + Descriptor(0x03) { } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/microsoft_os_string_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/microsoft_os_string_descriptor.cpp new file mode 100644 index 00000000000..b125b8efcee --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/microsoft_os_string_descriptor.cpp @@ -0,0 +1,19 @@ +#include "microsoft_os_string_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +void MicrosoftOSStringDescriptor::push(Channel * c) const { + StringDescriptor::push(c); + c->push(m_bMSVendorCode); + c->push(m_bPad); +} + +uint8_t MicrosoftOSStringDescriptor::bLength() const { + return StringDescriptor::bLength() + 2 * sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/microsoft_os_string_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/microsoft_os_string_descriptor.h new file mode 100644 index 00000000000..dfe3a7ab264 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/microsoft_os_string_descriptor.h @@ -0,0 +1,30 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_MICROSOFT_OS_STRING_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_MICROSOFT_OS_STRING_DESCRIPTOR_H + +#include "string_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class MicrosoftOSStringDescriptor : public StringDescriptor { +public: + constexpr MicrosoftOSStringDescriptor(uint8_t bMSVendorCode) : + StringDescriptor("MSFT100"), + m_bMSVendorCode(bMSVendorCode), + m_bPad(0) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + uint8_t m_bMSVendorCode; + uint8_t m_bPad; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.cpp new file mode 100644 index 00000000000..d5c7756bafc --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.cpp @@ -0,0 +1,21 @@ +#include "platform_device_capability_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +void PlatformDeviceCapabilityDescriptor::push(Channel * c) const { + DeviceCapabilityDescriptor::push(c); + c->push(m_bReserved); + for (int i = 0; i < k_platformCapabilityUUIDSize; i++) { + c->push(m_platformCapabilityUUID[i]); + } +} + +uint8_t PlatformDeviceCapabilityDescriptor::bLength() const { + return DeviceCapabilityDescriptor::bLength() + sizeof(uint8_t) + k_platformCapabilityUUIDSize*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.h new file mode 100644 index 00000000000..3c117348c50 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/platform_device_capability_descriptor.h @@ -0,0 +1,47 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABILITY_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABILITY_DESCRIPTOR_H + +#include "device_capability_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class PlatformDeviceCapabilityDescriptor : public DeviceCapabilityDescriptor { +public: + constexpr PlatformDeviceCapabilityDescriptor(const uint8_t platformCapabilityUUID[]) : + DeviceCapabilityDescriptor(0x05), + m_bReserved(0), + m_platformCapabilityUUID{ + platformCapabilityUUID[0], + platformCapabilityUUID[1], + platformCapabilityUUID[2], + platformCapabilityUUID[3], + platformCapabilityUUID[4], + platformCapabilityUUID[5], + platformCapabilityUUID[6], + platformCapabilityUUID[7], + platformCapabilityUUID[8], + platformCapabilityUUID[9], + platformCapabilityUUID[10], + platformCapabilityUUID[11], + platformCapabilityUUID[12], + platformCapabilityUUID[13], + platformCapabilityUUID[14], + platformCapabilityUUID[15]} + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + constexpr static uint8_t k_platformCapabilityUUIDSize = 16; + uint8_t m_bReserved; + uint8_t m_platformCapabilityUUID[k_platformCapabilityUUIDSize]; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/string_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/string_descriptor.cpp new file mode 100644 index 00000000000..044e54fb0e1 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/string_descriptor.cpp @@ -0,0 +1,25 @@ +#include "string_descriptor.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +void StringDescriptor::push(Channel * c) const { + Descriptor::push(c); + const char * stringPointer = m_string; + while (*stringPointer != 0) { + uint16_t stringAsUTF16CodePoint = *stringPointer; + c->push(stringAsUTF16CodePoint); + stringPointer++; + } +} + +uint8_t StringDescriptor::bLength() const { + // The script is returned in UTF-16, hence the multiplication. + return Descriptor::bLength() + 2*strlen(m_string); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/string_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/string_descriptor.h new file mode 100644 index 00000000000..1391c4aec07 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/string_descriptor.h @@ -0,0 +1,28 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_STRING_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_STRING_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class StringDescriptor : public Descriptor { +public: + constexpr StringDescriptor(const char * string) : + Descriptor(0x03), + m_string(string) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + const char * m_string; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/url_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/url_descriptor.cpp new file mode 100644 index 00000000000..2b4580b8a02 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/url_descriptor.cpp @@ -0,0 +1,25 @@ +#include "url_descriptor.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +void URLDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bScheme); + const char * stringPointer = m_string; + while (*stringPointer != 0) { + c->push(*stringPointer); + stringPointer++; + } +} + +uint8_t URLDescriptor::bLength() const { + // The script is returned in UTF-8. + return Descriptor::bLength() + sizeof(uint8_t) + strlen(m_string); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/url_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/url_descriptor.h new file mode 100644 index 00000000000..7dee33e7c33 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/url_descriptor.h @@ -0,0 +1,36 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_URL_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_URL_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class URLDescriptor : public Descriptor { +public: + enum class Scheme { + HTTP = 0, + HTTPS = 1, + IncludedInURL = 255 + }; + + constexpr URLDescriptor(Scheme scheme, const char * url) : + Descriptor(0x03), + m_bScheme((uint8_t)scheme), + m_string(url) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + uint8_t m_bScheme; + const char * m_string; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/descriptor/webusb_platform_descriptor.cpp b/ion/src/device/bootloader/usb/stack/descriptor/webusb_platform_descriptor.cpp new file mode 100644 index 00000000000..d859f5927a7 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/webusb_platform_descriptor.cpp @@ -0,0 +1,22 @@ +#include "webusb_platform_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +constexpr uint8_t WebUSBPlatformDescriptor::k_webUSBUUID[]; + +void WebUSBPlatformDescriptor::push(Channel * c) const { + PlatformDeviceCapabilityDescriptor::push(c); + c->push(m_bcdVersion); + c->push(m_bVendorCode); + c->push(m_iLandingPage); +} + +uint8_t WebUSBPlatformDescriptor::bLength() const { + return PlatformDeviceCapabilityDescriptor::bLength() + sizeof(uint16_t) + 2*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/descriptor/webusb_platform_descriptor.h b/ion/src/device/bootloader/usb/stack/descriptor/webusb_platform_descriptor.h new file mode 100644 index 00000000000..549bcdae2bb --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/descriptor/webusb_platform_descriptor.h @@ -0,0 +1,37 @@ +#ifndef ION_DEVICE_SHARED_USB_STACK_WEBUSB_PLATFORM_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_WEBUSB_PLATFORM_DESCRIPTOR_H + +#include "platform_device_capability_descriptor.h" + +namespace Ion { +namespace Device { +namespace USB { + +class WebUSBPlatformDescriptor : public PlatformDeviceCapabilityDescriptor { +public: + constexpr WebUSBPlatformDescriptor(uint8_t bVendorCode, uint8_t iLandingPage) : + PlatformDeviceCapabilityDescriptor(k_webUSBUUID), + m_bcdVersion(0x0100), + m_bVendorCode(bVendorCode), + m_iLandingPage(iLandingPage) + { + } +protected: + void push(Channel * c) const override; + uint8_t bLength() const override; +private: + /* Little-endian encoding of {3408B638-09A9-47A0-8BFD-A0768815B665}. + * See https://wicg.github.io/webusb/#webusb-platform-capability-descriptor */ + constexpr static uint8_t k_webUSBUUID[] = { + 0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, + 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65}; + uint16_t m_bcdVersion; + uint8_t m_bVendorCode; + uint8_t m_iLandingPage; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/device.cpp b/ion/src/device/bootloader/usb/stack/device.cpp new file mode 100644 index 00000000000..294b647e12c --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/device.cpp @@ -0,0 +1,168 @@ +#include "device.h" +#include +#include +#include + +namespace Ion { +namespace Device { +namespace USB { + +using namespace Regs; + +static inline uint16_t minUint16T(uint16_t x, uint16_t y) { return x < y ? x : y; } + +void Device::poll() { + // Read the interrupts + class OTG::GINTSTS intsts(OTG.GINTSTS()->get()); + + /* SETUP or OUT transaction + * If the Rx FIFO is not empty, there is a SETUP or OUT transaction. + * The interrupt is done AFTER THE HANSDHAKE of the transaction. */ + if (intsts.getRXFLVL()) { + class OTG::GRXSTSP grxstsp(OTG.GRXSTSP()->get()); + + // Store the packet status + OTG::GRXSTSP::PKTSTS pktsts = grxstsp.getPKTSTS(); + + // We only use endpoint 0 + assert(grxstsp.getEPNUM() == 0); + + if (pktsts == OTG::GRXSTSP::PKTSTS::OutTransferCompleted || pktsts == OTG::GRXSTSP::PKTSTS::SetupTransactionCompleted) { + // There is no data associated with this interrupt. + return; + } + + assert(pktsts != OTG::GRXSTSP::PKTSTS::GlobalOutNAK); + /* We did not enable the GONAKEFFM (Global OUT NAK effective mask) bit in + * GINTSTS, so we should never get this interrupt. */ + + assert(pktsts == OTG::GRXSTSP::PKTSTS::OutReceived || pktsts == OTG::GRXSTSP::PKTSTS::SetupReceived); + + TransactionType type = (pktsts == OTG::GRXSTSP::PKTSTS::OutReceived) ? TransactionType::Out : TransactionType::Setup; + + if (type == TransactionType::Setup && OTG.DIEPTSIZ0()->getPKTCNT()) { + // SETUP received but there is a packet in the Tx FIFO. Flush it. + m_ep0.flushTxFifo(); + } + + // Save the received packet byte count + m_ep0.setReceivedPacketSize(grxstsp.getBCNT()); + + if (type == TransactionType::Setup) { + m_ep0.readAndDispatchSetupPacket(); + } else { + assert(type == TransactionType::Out); + m_ep0.processOUTpacket(); + } + + m_ep0.discardUnreadData(); + } + + /* IN transactions. + * The interrupt is done AFTER THE HANSDHAKE of the transaction. */ + if (OTG.DIEPINT(0)->getXFRC()) { // We only check endpoint 0. + m_ep0.processINpacket(); + // Clear the Transfer Completed Interrupt + OTG.DIEPINT(0)->setXFRC(true); + } + + // Handle USB RESET. ENUMDNE = **SPEED** Enumeration Done + if (intsts.getENUMDNE()) { + // Clear the ENUMDNE bit + OTG.GINTSTS()->setENUMDNE(true); + /* After a USB reset, the host talks to the device by sending messages to + * address 0; */ + setAddress(0); + // Flush the FIFOs + m_ep0.reset(); + m_ep0.setup(); + /* In setup(), we should set the MPSIZ field in OTG_DIEPCTL0 to the maximum + * packet size depending on the enumeration speed (found in OTG_DSTS). We + * should always get FullSpeed, so we set the packet size accordingly. */ + } +} + +bool Device::isSoftDisconnected() const { + return OTG.DCTL()->getSDIS(); +} + +void Device::detach() { + // Get in soft-disconnected state + OTG.DCTL()->setSDIS(true); +} + +void Device::leave(uint32_t leaveAddress) { + if (leaveAddress == Ion::Device::InternalFlash::Config::StartAddress) { + Ion::Device::Reset::coreWhilePlugged(); + } else { + Ion::Device::Reset::jump(leaveAddress); + } +} + +bool Device::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + // Device only handles standard requests. + if (request->requestType() != SetupPacket::RequestType::Standard) { + return false; + } + switch (request->bRequest()) { + case (int) Request::GetStatus: + return getStatus(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (int) Request::SetAddress: + // Make sure the request is adress is valid. + assert(request->wValue() < 128); + /* According to the reference manual, the address should be set after the + * Status stage of the current transaction, but this is not true. + * It should be set here, after the Data stage. */ + setAddress(request->wValue()); + *transferBufferLength = 0; + return true; + case (int) Request::GetDescriptor: + return getDescriptor(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (int) Request::SetConfiguration: + *transferBufferLength = 0; + return setConfiguration(request); + case (int) Request::GetConfiguration: + return getConfiguration(transferBuffer, transferBufferLength); + } + return false; +} + +bool Device::getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = minUint16T(2, transferBufferMaxLength); + for (int i = 0; i<*transferBufferLength; i++) { + transferBuffer[i] = 0; // No remote wakeup, not self-powered. + } + return true; +} + +void Device::setAddress(uint8_t address) { + OTG.DCFG()->setDAD(address); +} + +bool Device::getDescriptor(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + Descriptor * wantedDescriptor = descriptor(request->descriptorType(), request->descriptorIndex()); + if (wantedDescriptor == nullptr) { + return false; + } + *transferBufferLength = wantedDescriptor->copy(transferBuffer, transferBufferMaxLength); + return true; +} + +bool Device::getConfiguration(uint8_t * transferBuffer, uint16_t * transferBufferLength) { + *transferBufferLength = 1; + transferBuffer[0] = getActiveConfiguration(); + return true; +} + +bool Device::setConfiguration(SetupPacket * request) { + // We support one configuration only + setActiveConfiguration(request->wValue()); + /* There is one configuration only, we no need to set it again, just reset the + * endpoint. */ + m_ep0.reset(); + return true; +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/device.h b/ion/src/device/bootloader/usb/stack/device.h new file mode 100644 index 00000000000..04f90710805 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/device.h @@ -0,0 +1,67 @@ +#ifndef ION_DEVICE_SHARED_USB_DEVICE_H +#define ION_DEVICE_SHARED_USB_DEVICE_H + +#include "descriptor/descriptor.h" +#include "endpoint0.h" +#include "interface.h" +#include "request_recipient.h" +#include "setup_packet.h" + +namespace Ion { +namespace Device { +namespace USB { + +// We only handle control transfers, on EP0. +class Device : public RequestRecipient { +public: + Device(Interface * interface) : + RequestRecipient(&m_ep0), + m_ep0(this, interface), + m_resetOnDisconnect(false) + { + } + void poll(); + bool isSoftDisconnected() const; + void detach(); + void leave(uint32_t leaveAddress); + bool resetOnDisconnect() { return m_resetOnDisconnect; } + void setResetOnDisconnect(bool reset) { m_resetOnDisconnect = reset; } +protected: + virtual Descriptor * descriptor(uint8_t type, uint8_t index) = 0; + virtual void setActiveConfiguration(uint8_t configurationIndex) = 0; + virtual uint8_t getActiveConfiguration() = 0; + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; + Endpoint0 m_ep0; +private: + // USB Standard Device Request Codes + enum class Request { + GetStatus = 0, + ClearFeature = 1, + SetFeature = 3, + SetAddress = 5, + GetDescriptor = 6, + SetDescriptor = 7, + GetConfiguration = 8, + SetConfiguration = 9, + }; + + enum class TransactionType { + Setup, + In, + Out + }; + + void setAddress(uint8_t address); + bool getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool getDescriptor(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool getConfiguration(uint8_t * transferBuffer, uint16_t * transferBufferLength); + bool setConfiguration(SetupPacket * request); + + bool m_resetOnDisconnect; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/endpoint0.cpp b/ion/src/device/bootloader/usb/stack/endpoint0.cpp new file mode 100644 index 00000000000..32121076ffd --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/endpoint0.cpp @@ -0,0 +1,348 @@ +#include "endpoint0.h" +#include +#include +#include "device.h" +#include "interface.h" +#include "request_recipient.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +using namespace Regs; + +constexpr int Endpoint0::k_maxPacketSize; +constexpr uint16_t Endpoint0::MaxTransferSize; + +void Endpoint0::setup() { + // Setup the IN direction + + // Reset the device IN endpoint 0 transfer size register + class OTG::DIEPTSIZ0 dieptsiz0(0); + /* Transfer size. The core interrupts the application only after it has + * exhausted the transfer size amount of data. The transfer size is set to the + * maximum packet size, to be interrupted at the end of each packet. */ + dieptsiz0.setXFRSIZ(k_maxPacketSize); + OTG.DIEPTSIZ0()->set(dieptsiz0); + + // Reset the device IN endpoint 0 control register + class OTG::DIEPCTL0 diepctl0(0); // Reset value + // Set the maximum packet size + diepctl0.setMPSIZ(OTG::DIEPCTL0::MPSIZ::Size64); + // Set the NAK bit: all IN transactions on endpoint 0 receive a NAK answer + diepctl0.setSNAK(true); + // Enable the endpoint + diepctl0.setEPENA(true); + OTG.DIEPCTL0()->set(diepctl0); + + // Setup the OUT direction + + setupOut(); + // Set the NAK bit + OTG.DOEPCTL0()->setSNAK(true); + // Enable the endpoint + enableOut(); + + // Setup the Tx FIFO + + /* Tx FIFO depth + * We process each packet as soon as it arrives, so we only need + * k_maxPacketSize bytes. TX0FD being in terms of 32-bit words, we divide + * k_maxPacketSize by 4. */ + OTG.DIEPTXF0()->setTX0FD(k_maxPacketSize/4); + /* Tx FIFO RAM start address. It starts just after the Rx FIFOso the value is + * Rx FIFO start address (0) + Rx FIFO depth. the Rx FIFO depth is set in + * usb.cpp, but because the code is linked separately, we cannot get it. */ + OTG.DIEPTXF0()->setTX0FSA(128); +} + +void Endpoint0::setupOut() { + class OTG::DOEPTSIZ0 doeptsiz0(0); + // Number of back-to-back SETUP data packets the endpoint can receive + doeptsiz0.setSTUPCNT(1); + // Packet count, false if a packet is written into the Rx FIFO + doeptsiz0.setPKTCNT(true); + /* Transfer size. The core interrupts the application only after it has + * exhausted the transfer size amount of data. The transfer size is set to the + * maximum packet size, to be interrupted at the end of each packet. */ + doeptsiz0.setXFRSIZ(64); + OTG.DOEPTSIZ0()->set(doeptsiz0); +} + +void Endpoint0::setOutNAK(bool nak) { + m_forceNAK = nak; + /* We need to keep track of the NAK state of the endpoint to use the value + * after a setupOut in poll() of device.cpp. */ + if (nak) { + OTG.DOEPCTL0()->setSNAK(true); + } else { + OTG.DOEPCTL0()->setCNAK(true); + } +} + +void Endpoint0::enableOut() { + OTG.DOEPCTL0()->setEPENA(true); +} + +void Endpoint0::reset() { + flushTxFifo(); + flushRxFifo(); +} + +void Endpoint0::readAndDispatchSetupPacket() { + setOutNAK(true); + + // Read the 8-bytes Setup packet + if (readPacket(m_largeBuffer, sizeof(SetupPacket)) != sizeof(SetupPacket)) { + stallTransaction(); + return; + }; + + m_request = SetupPacket(m_largeBuffer); + uint16_t maxBufferLength = std::min(m_request.wLength(), MaxTransferSize); + + // Forward the request to the request recipient + uint8_t type = static_cast(m_request.recipientType()); + if (type == 0) { + // Device recipient + m_requestRecipients[0]->processSetupRequest(&m_request, m_largeBuffer, &m_transferBufferLength, maxBufferLength); + } else { + // Interface recipient + m_requestRecipients[1]->processSetupRequest(&m_request, m_largeBuffer, &m_transferBufferLength, maxBufferLength); + } +} + +void Endpoint0::processINpacket() { + switch (m_state) { + case State::DataIn: + sendSomeData(); + break; + case State::LastDataIn: + m_state = State::StatusOut; + // Prepare to receive the OUT Data[] transaction. + setOutNAK(false); + break; + case State::StatusIn: + { + m_state = State::Idle; + // All the data has been received. Callback the request recipient. + uint8_t type = static_cast(m_request.recipientType()); + if (type == 0) { + // Device recipient + m_requestRecipients[0]->wholeDataReceivedCallback(&m_request, m_largeBuffer, &m_transferBufferLength); + } else { + // Interface recipient + m_requestRecipients[1]->wholeDataReceivedCallback(&m_request, m_largeBuffer, &m_transferBufferLength); + } + } + break; + default: + stallTransaction(); + } +} + +void Endpoint0::processOUTpacket() { + switch (m_state) { + case State::DataOut: + if (receiveSomeData() < 0) { + break; + } + if ((m_request.wLength() - m_transferBufferLength) <= k_maxPacketSize) { + m_state = State::LastDataOut; + } + break; + case State::LastDataOut: + if (receiveSomeData() < 0) { + break; + } + // Send the DATA1[] to the host. + writePacket(NULL, 0); + m_state = State::StatusIn; + break; + case State::StatusOut: + { + // Read the DATA1[] sent by the host. + readPacket(NULL, 0); + m_state = State::Idle; + // All the data has been sent. Callback the request recipient. + uint8_t type = static_cast(m_request.recipientType()); + if (type == 0) { + // Device recipient + m_requestRecipients[0]->wholeDataSentCallback(&m_request, m_largeBuffer, &m_transferBufferLength); + } else { + // Interface recipient + m_requestRecipients[1]->wholeDataSentCallback(&m_request, m_largeBuffer, &m_transferBufferLength); + } + } + break; + default: + stallTransaction(); + } +} + +void Endpoint0::flushTxFifo() { + // Set IN endpoint NAK + OTG.DIEPCTL0()->setSNAK(true); + + // Wait for core to respond + while (!OTG.DIEPINT(0)->getINEPNE()) { + } + + // Get the Tx FIFO number + uint32_t fifo = OTG.DIEPCTL0()->getTXFNUM(); + + // Wait for AHB idle + while (!OTG.GRSTCTL()->getAHBIDL()) { + } + + // Flush Tx FIFO + OTG.GRSTCTL()->setTXFNUM(fifo); + OTG.GRSTCTL()->setTXFFLSH(true); + + // Reset packet counter + OTG.DIEPTSIZ0()->set(0); + + // Wait for the flush + while (OTG.GRSTCTL()->getTXFFLSH()) { + } +} + +void Endpoint0::flushRxFifo() { + // Set OUT endpoint NAK + OTG.DOEPCTL0()->setSNAK(true); + + // Wait for AHB idle + while (!OTG.GRSTCTL()->getAHBIDL()) { + } + + // Flush Rx FIFO + OTG.GRSTCTL()->setRXFFLSH(true); + + // Reset packet counter + OTG.DOEPTSIZ0()->set(0); + + // Wait for the flush + while (OTG.GRSTCTL()->getRXFFLSH()) { + } +} + +void Endpoint0::discardUnreadData() { + for (int i = 0; i < m_receivedPacketSize; i += 4) { + OTG.DFIFO0()->get(); + } + m_receivedPacketSize = 0; +} + +void Endpoint0::sendSomeData() { + if (k_maxPacketSize < m_transferBufferLength) { + // More than one packet needs to be sent + writePacket(m_largeBuffer + m_bufferOffset, k_maxPacketSize); + m_state = State::DataIn; + m_bufferOffset += k_maxPacketSize; + m_transferBufferLength -= k_maxPacketSize; + return; + } + // Last data packet sent + writePacket(m_largeBuffer + m_bufferOffset, m_transferBufferLength); + if (m_zeroLengthPacketNeeded) { + m_state = State::DataIn; + } else { + m_state = State::LastDataIn; + } + m_bufferOffset = 0; + m_zeroLengthPacketNeeded = false; + m_transferBufferLength = 0; +} + +void Endpoint0::clearForOutTransactions(uint16_t wLength) { + m_transferBufferLength = 0; + m_state = (wLength > k_maxPacketSize) ? State::DataOut : State::LastDataOut; + setOutNAK(false); +} + +int Endpoint0::receiveSomeData() { + // If it is the first chunk of data to be received, m_transferBufferLength is 0. + uint16_t packetSize = std::min(k_maxPacketSize, m_request.wLength() - m_transferBufferLength); + uint16_t sizeOfPacketRead = readPacket(m_largeBuffer + m_transferBufferLength, packetSize); + if (sizeOfPacketRead != packetSize) { + stallTransaction(); + return -1; + } + m_transferBufferLength += packetSize; + return packetSize; +} + +uint16_t Endpoint0::readPacket(void * buffer, uint16_t length) { + uint32_t * buffer32 = (uint32_t *) buffer; + uint16_t buffer32Length = std::min(length, m_receivedPacketSize); + + int i; + // The RX FIFO is read 4 bytes by 4 bytes + for (i = buffer32Length; i >= 4; i -= 4) { + *buffer32++ = OTG.DFIFO0()->get(); + m_receivedPacketSize -= 4; + } + + if (i) { + /* If there are remaining bytes that should be read, read the next 4 bytes + * and copy only the wanted bytes. */ + uint32_t extraData = OTG.DFIFO0()->get(); + memcpy(buffer32, &extraData, i); + if (m_receivedPacketSize < 4) { + m_receivedPacketSize = 0; + } else { + m_receivedPacketSize -= 4; + } + } + return buffer32Length; +} + +uint16_t Endpoint0::writePacket(const void * buffer, uint16_t length) { + const uint32_t * buffer32 = (uint32_t *) buffer; + + // Return if there is already a packet waiting to be read in the TX FIFO + if (OTG.DIEPTSIZ0()->getPKTCNT()) { + return 0; + } + + // Enable transmission + + class OTG::DIEPTSIZ0 dieptsiz0(0); + // Indicate that the Transfer Size is one packet + dieptsiz0.setPKTCNT(1); + // Indicate the length of the Transfer Size + dieptsiz0.setXFRSIZ(length); + OTG.DIEPTSIZ0()->set(dieptsiz0); + // Enable the endpoint + OTG.DIEPCTL0()->setEPENA(true); + // Clear the NAK bit + OTG.DIEPCTL0()->setCNAK(true); + + // Copy the buffer to the TX FIFO by writing data 32bits by 32 bits. + for (int i = length; i > 0; i -= 4) { + OTG.DFIFO0()->set(*buffer32++); + } + + return length; +} + +void Endpoint0::stallTransaction() { + OTG.DIEPCTL0()->setSTALL(true); + m_state = State::Idle; +} + +void Endpoint0::computeZeroLengthPacketNeeded() { + if (m_transferBufferLength + && m_transferBufferLength < m_request.wLength() + && m_transferBufferLength % k_maxPacketSize == 0) + { + m_zeroLengthPacketNeeded = true; + return; + } + m_zeroLengthPacketNeeded = false; +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/endpoint0.h b/ion/src/device/bootloader/usb/stack/endpoint0.h new file mode 100644 index 00000000000..42d39dbefef --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/endpoint0.h @@ -0,0 +1,79 @@ +#ifndef ION_DEVICE_SHARED_USB_ENDPOINT0_H +#define ION_DEVICE_SHARED_USB_ENDPOINT0_H + +#include "setup_packet.h" + +namespace Ion { +namespace Device { +namespace USB { + +class RequestRecipient; + +class Endpoint0 { +public: + enum class State { + Idle, + Stalled, + DataIn, + LastDataIn, + StatusIn, + DataOut, + LastDataOut, + StatusOut, + }; + + constexpr static int k_maxPacketSize = 64; + constexpr static uint16_t MaxTransferSize = 2048; + + constexpr Endpoint0(RequestRecipient * device, RequestRecipient * interface) : + m_forceNAK(false), + m_bufferOffset(0), + m_transferBufferLength(0), + m_receivedPacketSize(0), + m_zeroLengthPacketNeeded(false), + m_request(), + m_requestRecipients{device, interface}, + m_state(State::Idle), + m_largeBuffer{0} + { + } + void setup(); + void setupOut(); + void setOutNAK(bool nak); + void enableOut(); + void reset(); + bool NAKForced() const { return m_forceNAK; } + void readAndDispatchSetupPacket(); + void processINpacket(); + void processOUTpacket(); + void flushTxFifo(); + void flushRxFifo(); + void setReceivedPacketSize(uint16_t size) { m_receivedPacketSize = size; } + void discardUnreadData(); + void stallTransaction(); + void computeZeroLengthPacketNeeded(); + void setState(State state) { m_state = state; } + void sendSomeData(); // Writes the next data packet and updates the state. + void clearForOutTransactions(uint16_t wLength); + +private: + int receiveSomeData(); + uint16_t readPacket(void * buffer, uint16_t length); + uint16_t writePacket(const void * buffer, uint16_t length); + + bool m_forceNAK; + int m_bufferOffset; // When sending large data stored in the buffer, the offset keeps tracks of which data packet should be sent next. + uint16_t m_transferBufferLength; + uint16_t m_receivedPacketSize; + bool m_zeroLengthPacketNeeded; + SetupPacket m_request; + RequestRecipient * m_requestRecipients[2]; + State m_state; + uint8_t m_largeBuffer[MaxTransferSize]; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/interface.cpp b/ion/src/device/bootloader/usb/stack/interface.cpp new file mode 100644 index 00000000000..835fe2cfb3f --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/interface.cpp @@ -0,0 +1,66 @@ +#include "interface.h" + +namespace Ion { +namespace Device { +namespace USB { + +static inline uint16_t minUint16T(uint16_t x, uint16_t y) { return x < y ? x : y; } + +bool Interface::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (request->requestType() != SetupPacket::RequestType::Standard) { + return false; + } + switch (request->bRequest()) { + case (int) Request::GetStatus: + return getStatus(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (int) Request::SetInterface: + return setInterface(request, transferBufferLength); + case (int) Request::GetInterface: + return getInterface(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (int) Request::ClearFeature: + return clearFeature(transferBufferLength); + case (int) Request::SetFeature: + return setFeature(transferBufferLength); + } + return false; +} + +bool Interface::getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = minUint16T(2, transferBufferMaxLength); + for (int i = 0; i<*transferBufferLength; i++) { + transferBuffer[i] = 0; // Reserved, must be set to 0 + } + return true; +} + +bool Interface::getInterface(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = minUint16T(1, transferBufferMaxLength);; + if (*transferBufferLength > 0) { + transferBuffer[0] = getActiveInterfaceAlternative(); + } + return true; +} + +bool Interface::setInterface(SetupPacket * request, uint16_t * transferBufferLength) { + // We support one interface only + setActiveInterfaceAlternative(request->wValue()); + // There is one interface alternative only, we no need to set it again. + *transferBufferLength = 0; + return true; +} + +bool Interface::clearFeature(uint16_t * transferBufferLength) { + // Not needed for now + *transferBufferLength = 0; + return true; +} + +bool Interface::setFeature(uint16_t * transferBufferLength) { + // Not needed for now + *transferBufferLength = 0; + return true; +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/interface.h b/ion/src/device/bootloader/usb/stack/interface.h new file mode 100644 index 00000000000..d1a98a16c3b --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/interface.h @@ -0,0 +1,42 @@ +#ifndef ION_DEVICE_SHARED_USB_INTERFACE_H +#define ION_DEVICE_SHARED_USB_INTERFACE_H + +#include "endpoint0.h" +#include "request_recipient.h" +#include "setup_packet.h" + +namespace Ion { +namespace Device { +namespace USB { + +class Interface : public RequestRecipient { +public: + Interface(Endpoint0 * ep0) : + RequestRecipient(ep0) + { + } +protected: + virtual void setActiveInterfaceAlternative(uint8_t interfaceAlternativeIndex) = 0; + virtual uint8_t getActiveInterfaceAlternative() = 0; + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; +private: + // USB Standard Interface Request Codes + enum class Request { + GetStatus = 0, + ClearFeature = 1, + SetFeature = 3, + GetInterface = 10, + SetInterface = 11, + }; + bool getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool getInterface(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool setInterface(SetupPacket * request, uint16_t * transferBufferLength); + bool clearFeature(uint16_t * transferBufferLength); + bool setFeature(uint16_t * transferBufferLength); +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/request_recipient.cpp b/ion/src/device/bootloader/usb/stack/request_recipient.cpp new file mode 100644 index 00000000000..dd781cfa1aa --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/request_recipient.cpp @@ -0,0 +1,32 @@ +#include "request_recipient.h" + +namespace Ion { +namespace Device { +namespace USB { + +bool RequestRecipient::processSetupRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (request->followingTransaction() == SetupPacket::TransactionType::InTransaction) { + // There is no data stage in this transaction, or the data stage will be in IN direction. + if (!processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { + m_ep0->stallTransaction(); + return false; + } + if (*transferBufferLength > 0) { + m_ep0->computeZeroLengthPacketNeeded(); + m_ep0->sendSomeData(); + } else { + m_ep0->sendSomeData(); + // On seeing a zero length packet, sendSomeData changed endpoint0 state to + // LastDataIn, but it should be StatusIn as there was no data stage. + m_ep0->setState(Endpoint0::State::StatusIn); + } + } else { + // The following transaction will be an OUT transaction. + m_ep0->clearForOutTransactions(request->wLength()); + } + return true; +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/request_recipient.h b/ion/src/device/bootloader/usb/stack/request_recipient.h new file mode 100644 index 00000000000..8973fba8c7b --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/request_recipient.h @@ -0,0 +1,29 @@ +#ifndef ION_DEVICE_SHARED_USB_REQUEST_RECIPIENT_H +#define ION_DEVICE_SHARED_USB_REQUEST_RECIPIENT_H + +#include "endpoint0.h" +#include "setup_packet.h" + +namespace Ion { +namespace Device { +namespace USB { + +class RequestRecipient { +public: + RequestRecipient(Endpoint0 * ep0): + m_ep0(ep0) + { + } + bool processSetupRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + virtual void wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) {} + virtual void wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) {} +protected: + virtual bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) = 0; + Endpoint0 * m_ep0; +}; + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/setup_packet.cpp b/ion/src/device/bootloader/usb/stack/setup_packet.cpp new file mode 100644 index 00000000000..77882d6a5a6 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/setup_packet.cpp @@ -0,0 +1,38 @@ +#include "setup_packet.h" +#include + +namespace Ion { +namespace Device { +namespace USB { + +SetupPacket::SetupPacket(void * buffer) { + memcpy(this, buffer, sizeof(SetupPacket)); +} + +SetupPacket::TransactionType SetupPacket::followingTransaction() const { + if (m_wLength == 0 || (m_bmRequestType & 0b10000000) != 0) { + return TransactionType::InTransaction; + } else { + return TransactionType::OutTransaction; + } +} + +SetupPacket::RequestType SetupPacket::requestType() const { + return (RequestType) ((m_bmRequestType & 0b01100000) >> 5); +} + +SetupPacket::RecipientType SetupPacket::recipientType() const { + return (RecipientType) (m_bmRequestType & 0b00001111); +} + +int SetupPacket::descriptorIndex() { + return m_wValue & 0xFF; +} + +int SetupPacket::descriptorType() { + return m_wValue >> 8; +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/setup_packet.h b/ion/src/device/bootloader/usb/stack/setup_packet.h new file mode 100644 index 00000000000..d68ec32fb8b --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/setup_packet.h @@ -0,0 +1,65 @@ +#ifndef ION_DEVICE_SHARED_USB_SETUP_PACKET_H +#define ION_DEVICE_SHARED_USB_SETUP_PACKET_H + +#include + +namespace Ion { +namespace Device { +namespace USB { + +class SetupPacket { +public: + enum class TransactionType { + SetupTransaction, + InTransaction, + OutTransaction + }; + + enum class RequestType { + Standard = 0, + Class = 1, + Vendor = 2 + }; + + enum class RecipientType { + Device = 0, + Interface = 1, + Endpoint = 2, + Other = 3 + }; + + constexpr SetupPacket() : + m_bmRequestType(0), + m_bRequest(0), + m_wValue(0), + m_wIndex(0), + m_wLength(0) + { + } + + SetupPacket(void * buffer); + TransactionType followingTransaction() const; + RequestType requestType() const; + RecipientType recipientType() const; + int descriptorIndex(); + int descriptorType(); + uint8_t bmRequestType() { return m_bmRequestType; } + uint8_t bRequest() { return m_bRequest; } + uint16_t wValue() { return m_wValue; } + uint16_t wIndex() { return m_wIndex; } + uint16_t wLength() { return m_wLength; } +private: + uint8_t m_bmRequestType; + uint8_t m_bRequest; + uint16_t m_wValue; + uint16_t m_wIndex; + uint16_t m_wLength; +}; + +static_assert(sizeof(SetupPacket) == 8, "SetupData must be packed"); + +} +} +} + +#endif diff --git a/ion/src/device/bootloader/usb/stack/streamable.cpp b/ion/src/device/bootloader/usb/stack/streamable.cpp new file mode 100644 index 00000000000..f680439ce98 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/streamable.cpp @@ -0,0 +1,15 @@ +#include "streamable.h" + +namespace Ion { +namespace Device { +namespace USB { + +uint16_t Streamable::copy(void * target, size_t maxSize) const { + Channel c(target, maxSize); + push(&c); + return maxSize - c.sizeLeft(); +} + +} +} +} diff --git a/ion/src/device/bootloader/usb/stack/streamable.h b/ion/src/device/bootloader/usb/stack/streamable.h new file mode 100644 index 00000000000..c6a79ad62b2 --- /dev/null +++ b/ion/src/device/bootloader/usb/stack/streamable.h @@ -0,0 +1,44 @@ +#ifndef ION_DEVICE_SHARED_USB_STREAMABLE_H +#define ION_DEVICE_SHARED_USB_STREAMABLE_H + +#include +#include + +namespace Ion { +namespace Device { +namespace USB { + +class Streamable { +public: + uint16_t copy(void * target, size_t maxSize) const; +protected: + class Channel { + public: + Channel(void * pointer, size_t maxSize) : + m_pointer(pointer), + m_sizeLeft(maxSize) + { + } + template + void push(T data) { + if (m_sizeLeft >= sizeof(T)) { + T * typedPointer = static_cast(m_pointer); + *typedPointer++ = data; // Actually push the data + m_pointer = static_cast(typedPointer); + m_sizeLeft -= sizeof(T); + } + } + size_t sizeLeft() { return m_sizeLeft; } + private: + void * m_pointer; + size_t m_sizeLeft; + }; + virtual void push(Channel * c) const = 0; +}; + + +} +} +} + +#endif diff --git a/ion/src/device/flasher/display_light.cpp b/ion/src/device/flasher/display_light.cpp index 1549c576335..ec5ba11886a 100644 --- a/ion/src/device/flasher/display_light.cpp +++ b/ion/src/device/flasher/display_light.cpp @@ -1,10 +1,16 @@ #include +#include namespace Flasher { namespace Display { void init() { - Ion::Display::pushRectUniform(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColor::RGB24(0xFFFF00)); + KDRect screen = KDRect(0,0,Ion::Display::Width,Ion::Display::Height); + Ion::Display::pushRectUniform(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColor::RGB24(0x5e81ac)); + KDContext * ctx = KDIonContext::sharedContext(); + ctx->setOrigin(KDPointZero); + ctx->setClippingRect(screen); + ctx->drawString("RECOVERY MODE", KDPoint(10, 10), KDFont::LargeFont, KDColorWhite, KDColor::RGB24(0x5e81ac)); } } diff --git a/ion/src/device/flasher/display_verbose.cpp b/ion/src/device/flasher/display_verbose.cpp index aad0b6c4598..ed7b5ff2983 100644 --- a/ion/src/device/flasher/display_verbose.cpp +++ b/ion/src/device/flasher/display_verbose.cpp @@ -1,47 +1,55 @@ #include #include +#include "image.h" namespace Flasher { -namespace Display { - -constexpr static int sNumberOfMessages = 5; -constexpr static int sNumberOfLanguages = 2; - -constexpr static const char * sMessages[sNumberOfLanguages][sNumberOfMessages] = { - {"RECOVERY MODE", - "Your calculator is waiting", - "for a new software.", - "Follow the instructions", - "on your computer to continue."}, - {"MODE RECUPERATION", - "Votre calculatrice attend", - "l'installation d'un nouveau logiciel.", - "Suivez les instructions sur", - "votre ordinateur pour continuer."} -}; - -void init() { - KDRect screen = KDRect(0,0,Ion::Display::Width,Ion::Display::Height); - Ion::Display::pushRectUniform(screen, KDColorWhite); - KDContext * ctx = KDIonContext::sharedContext(); - ctx->setOrigin(KDPointZero); - ctx->setClippingRect(screen); - KDCoordinate margin = 20; - KDCoordinate currentHeight = 0; - for (int i = 0; i < sNumberOfLanguages; i++) { - currentHeight += margin; - const char * title = sMessages[i][0]; - KDSize titleSize = KDFont::LargeFont->stringSize(title); - ctx->drawString(title, KDPoint((Ion::Display::Width-titleSize.width())/2, currentHeight), KDFont::LargeFont); - currentHeight += 2*titleSize.height(); - for (int j = 1; j < sNumberOfMessages; j++) { - const char * message = sMessages[i][j]; - KDSize messageSize = KDFont::SmallFont->stringSize(message); - ctx->drawString(message, KDPoint((Ion::Display::Width-messageSize.width())/2, currentHeight), KDFont::SmallFont); - currentHeight += messageSize.height(); - } - } -} + namespace Display { -} + constexpr static int sNumberOfMessages = 5; + + constexpr static const char * sMessages[sNumberOfMessages] = { + "RECOVERY MODE", + "Your calculator is waiting", + "for Upsilon to be installed.", + "Follow the instructions", + "on your computer to continue.", + }; + + void init() { + KDRect screen = KDRect(0,0,Ion::Display::Width,Ion::Display::Height); + Ion::Display::pushRectUniform(screen, KDColor::RGB24(0x2B2B2B)); + KDContext * ctx = KDIonContext::sharedContext(); + ctx->setOrigin(KDPointZero); + ctx->setClippingRect(screen); + KDCoordinate margin = 30; + KDCoordinate currentHeight = margin; + + /* Title */ + const char * title = sMessages[0]; + KDSize titleSize = KDFont::LargeFont->stringSize(title); + ctx->drawString(title, KDPoint((Ion::Display::Width-titleSize.width())/2, currentHeight), + KDFont::LargeFont, KDColorWhite, KDColor::RGB24(0x2B2B2B)); + currentHeight = (uint16_t)((Ion::Display::Height*2)/3); + + /* Logo */ + for (int i = 0; i < IMAGE_WIDTH; ++i) { + for (int j = 0; j < IMAGE_HEIGHT; ++j) { + ctx->setPixel(KDPoint(i+(uint16_t)((Ion::Display::Width-IMAGE_WIDTH)/2), + j+(titleSize.height()+margin+15)), + KDColor::RGB16(image[i+(j*IMAGE_WIDTH)])); + } + } + + /* Messages */ + const char * message; + for (int i = 1; i < sNumberOfMessages; ++i) { + message = sMessages[i]; + KDSize messageSize = KDFont::SmallFont->stringSize(message); + ctx->drawString(message, KDPoint((Ion::Display::Width-messageSize.width())/2, currentHeight), + KDFont::SmallFont, KDColorWhite, KDColor::RGB24(0x2B2B2B)); + currentHeight += messageSize.height(); + } + } + + } } diff --git a/ion/src/device/flasher/image.h b/ion/src/device/flasher/image.h new file mode 100644 index 00000000000..87317365646 --- /dev/null +++ b/ion/src/device/flasher/image.h @@ -0,0 +1,11 @@ +#ifndef __IMAGE_image_to_show_H +#define __IMAGE_image_to_show_H + +#define IMAGE_WIDTH 80 +#define IMAGE_HEIGHT 80 + +constexpr static uint16_t image[] { + 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x3186, 0x3186, 0x0, 0x0, 0x4a49, 0x7bcf, 0x9cd2, 0xad55, 0xb5b6, 0xbdd6, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd6, 0xb5b6, 0xad55, 0x94b2, 0x73ae, 0x4207, 0x0, 0x0, 0x3186, 0x3165, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x3186, 0x821, 0x0, 0x6b6d, 0xad34, 0xc618, 0xce59, 0xce79, 0xce79, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce59, 0xce79, 0xce79, 0xce59, 0xc5f7, 0xa513, 0x630c, 0x0, 0x18a2, 0x3186, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2965, 0x3165, 0x0, 0x5aeb, 0xad75, 0xce59, 0xce59, 0xce58, 0xce38, 0xc618, 0xc618, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc617, 0xc618, 0xc618, 0xce38, 0xce59, 0xce79, 0xce38, 0xa534, 0x5269, 0x0, 0x3186, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x3165, 0x2924, 0x0, 0x8c71, 0xce38, 0xce59, 0xce38, 0xc638, 0xc618, 0xc618, 0xce59, 0xd69a, 0xdedb, 0xe6fb, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xdefb, 0xdedb, 0xd69a, 0xce58, 0xc618, 0xc618, 0xc638, 0xce38, 0xce59, 0xc618, 0x8410, 0x0, 0x2965, 0x2965, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x3165, 0x2104, 0x2104, 0xad34, 0xce79, 0xce38, 0xc638, 0xc618, 0xc638, 0xdedb, 0xef7d, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xef7d, 0xd6ba, 0xc618, 0xc618, 0xc638, 0xce38, 0xce59, 0x9cd3, 0x0, 0x2945, 0x2965, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2965, 0x2924, 0x2104, 0xad75, 0xce79, 0xc618, 0xc638, 0xc617, 0xdeba, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xf79e, 0xd69a, 0xc617, 0xc638, 0xc638, 0xce79, 0xa514, 0x0, 0x2965, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x3165, 0x0, 0xa534, 0xce59, 0xc618, 0xc618, 0xc638, 0xef5d, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xe73c, 0xc618, 0xc638, 0xc618, 0xce59, 0x9cd2, 0x0, 0x3186, 0x2945, 0x2945, 0x2945, 0x3186, 0x0, 0x8c71, 0xce59, 0xc618, 0xc618, 0xce38, 0xf79e, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xef7d, 0xc618, 0xc638, 0xc618, 0xce59, 0x7bef, 0x0, 0x3186, 0x2945, 0x3185, 0x800, 0x62eb, 0xc638, 0xc638, 0xc618, 0xc618, 0xf79e, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xef7d, 0xc618, 0xc638, 0xc638, 0xc617, 0x4a48, 0x18e3, 0x3165, 0x3165, 0x0, 0xad55, 0xce59, 0xc618, 0xc5f7, 0xef5d, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xe71c, 0xc5f7, 0xc618, 0xce59, 0xa4f3, 0x0, 0x3186, 0x0, 0x6b6d, 0xce38, 0xc638, 0xc617, 0xdeba, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xf79e, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xf79e, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd699, 0xc618, 0xc638, 0xc618, 0x5acb, 0x1082, 0x0, 0xa534, 0xce59, 0xc618, 0xc618, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xef7d, 0xe73c, 0xe73c, 0xe73c, 0xef3c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xef3c, 0xe73c, 0xe73c, 0xe73c, 0xef5d, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xf79e, 0xc617, 0xc618, 0xce59, 0x9cb2, 0x0, 0x4a49, 0xc5f7, 0xc638, 0xbdf7, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xef3c, 0xef5d, 0xef7d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef7d, 0xef5d, 0xef3c, 0xe73c, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xc5f7, 0xce38, 0xbdd6, 0x3186, 0x7bcf, 0xce58, 0xc618, 0xc5f7, 0xef7d, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xef5d, 0xe6fb, 0x8c71, 0x6b6d, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x738e, 0x6b6d, 0x8c51, 0xdefb, 0xef5d, 0xe73c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xe73c, 0xbdf7, 0xc638, 0xc638, 0x6b2c, 0x9cd2, 0xce59, 0xc617, 0xce38, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe71c, 0xf77d, 0xa534, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9cf3, 0xf77d, 0xe71c, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xf7be, 0xc618, 0xc618, 0xce59, 0x8c51, 0xad55, 0xce58, 0xbdf7, 0xd69a, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cd3, 0x0, 0x31a6, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x31a6, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xce59, 0xc5f7, 0xce59, 0x9cf3, 0xb596, 0xce38, 0xbdf7, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xbdf7, 0xce58, 0xad34, 0xbdb6, 0xc638, 0xbdf7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdf7, 0xce38, 0xad75, 0xbdf7, 0xc618, 0xbdf7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xdeba, 0xbdf7, 0xce38, 0xb595, 0xbdf7, 0xc618, 0xbdd7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x20e3, 0x20e3, 0x20e3, 0x20e3, 0x20e3, 0x20e3, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x20e3, 0x18e3, 0x20e3, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xdeba, 0xbdd7, 0xce38, 0xb575, 0xbdf7, 0xc618, 0xbdd7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2104, 0x3186, 0x31a6, 0x31a6, 0x3186, 0x31a6, 0x3186, 0x2103, 0x2104, 0x2104, 0x2104, 0x2103, 0x3185, 0x31a6, 0x3186, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdd7, 0xc638, 0xb575, 0xbdf7, 0xc618, 0xbdd7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2104, 0x18c2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2104, 0x2103, 0x2104, 0x2103, 0x2104, 0x0, 0x0, 0x0, 0x2945, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdd7, 0xc638, 0xb575, 0xbdf7, 0xc618, 0xbdd7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x1061, 0x4208, 0xbdb6, 0xbdf7, 0xbdf7, 0xbdf7, 0xc618, 0xad75, 0x20, 0x2124, 0x2124, 0x18e3, 0x2945, 0xb5b6, 0xc638, 0xa534, 0x0, 0x2945, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdd7, 0xc638, 0xb575, 0xbdd7, 0xc618, 0xbdd7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2944, 0x0, 0x5aeb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xef7d, 0x0, 0x2924, 0x2104, 0x3185, 0x0, 0xb5b6, 0xffff, 0xffff, 0x8c51, 0x0, 0x2965, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdd7, 0xc638, 0xb575, 0xbdd7, 0xc618, 0xbdd7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x1061, 0x4208, 0xbdd7, 0xbdf7, 0xbdf7, 0xf7be, 0xffff, 0xe71c, 0x0, 0x2124, 0x2104, 0x2124, 0x2124, 0x0, 0xdefb, 0xffff, 0xe71c, 0x0, 0x2945, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdd7, 0xc618, 0xb575, 0xbdd7, 0xc618, 0xbdd7, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2104, 0x18c2, 0x0, 0x0, 0x0, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x20e3, 0x31a6, 0x0, 0xa514, 0xffff, 0xffff, 0x8410, 0x0, 0x2965, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffde, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdd7, 0xc618, 0xb575, 0xbdd7, 0xc618, 0xbdb6, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2104, 0x31a6, 0x2965, 0x39e7, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x2104, 0x2945, 0x0, 0x6b4d, 0xffdf, 0xffff, 0xc618, 0x0, 0x31a6, 0x20e3, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffbe, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdd7, 0xc618, 0xb575, 0xbdd7, 0xc5f7, 0xbdb6, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x18c2, 0x3165, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x2104, 0x2124, 0x18a2, 0x39a6, 0xef7d, 0xffff, 0xdefb, 0x0, 0x2965, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffbe, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd6ba, 0xbdb6, 0xc618, 0xad75, 0xbdd7, 0xc5f7, 0xbdb6, 0xdefb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x18c3, 0x3165, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x2104, 0x2104, 0x2104, 0x0, 0xef5d, 0xffff, 0xef5d, 0x800, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffbe, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xbdb6, 0xc618, 0xad75, 0xbdd7, 0xc5f7, 0xbdb6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x18c3, 0x2965, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x2104, 0x2104, 0x2104, 0x0, 0xef5d, 0xffff, 0xef5d, 0x18a2, 0x2104, 0x2124, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xffbe, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xbdb6, 0xc618, 0xad75, 0xbdd7, 0xc5f7, 0xbdb6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x18c3, 0x2965, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x2104, 0x2124, 0x10a2, 0x39c7, 0xf79e, 0xffff, 0xef5d, 0x0, 0x2124, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xbdb6, 0xc618, 0xad75, 0xbdd7, 0xc5f7, 0xbdb6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x18c3, 0x2965, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x2104, 0x2945, 0x0, 0x6b4d, 0xffdf, 0xffff, 0xdedb, 0x0, 0x2965, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xbdb6, 0xc618, 0xad55, 0xbdd7, 0xc5f7, 0xb5b6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x18c3, 0x2965, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x2103, 0x3186, 0x0, 0x9cf3, 0xffff, 0xffff, 0xbdd7, 0x0, 0x31a6, 0x20e3, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb5b6, 0xc618, 0xad55, 0xbdb6, 0xbdf7, 0xb5b6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x18e3, 0x2945, 0xef7d, 0xffff, 0xe73c, 0x0, 0x2124, 0x2104, 0x18e3, 0x31a6, 0x0, 0xd6ba, 0xffff, 0xffff, 0x7bef, 0x0, 0x2965, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb5b6, 0xc618, 0xad55, 0xbdb6, 0xbdf7, 0xb5b6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x2104, 0x0, 0xef5d, 0xffff, 0xef5d, 0x2104, 0x3186, 0x3186, 0x39e7, 0x0, 0x7bcf, 0xffff, 0xffff, 0xe71c, 0x0, 0x2945, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb5b6, 0xc618, 0xad55, 0xbdb6, 0xbdf7, 0xb5b6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x3185, 0x0, 0xd69a, 0xffff, 0xffdf, 0x5aeb, 0x0, 0x0, 0x0, 0x31a6, 0xef5d, 0xffff, 0xffff, 0x8c51, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb5b6, 0xc617, 0xad55, 0xbdb6, 0xbdf7, 0xb5b6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x3186, 0x0, 0x9cf3, 0xffff, 0xffff, 0xe71c, 0x94b2, 0x8c51, 0xa534, 0xef7d, 0xffff, 0xffff, 0xce38, 0x0, 0x3185, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb5b6, 0xc617, 0xad55, 0xbdb6, 0xbdf7, 0xb5b6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x0, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xce79, 0x0, 0x2104, 0x2124, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb5b6, 0xc5f7, 0xad55, 0xbdb6, 0xbdf7, 0xb5b6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2924, 0x820, 0x2945, 0xbdd7, 0xef5d, 0xf7be, 0xf7be, 0xf79e, 0xdedb, 0x9492, 0x0, 0x20e3, 0x2924, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb5b6, 0xc5f7, 0xad55, 0xbdb6, 0xbdf7, 0xb5b6, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x20e3, 0x2924, 0x18c2, 0x0, 0x1082, 0x4a69, 0x528a, 0x39c7, 0x0, 0x0, 0x2965, 0x2124, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb5b6, 0xc5f7, 0xad55, 0xbdb6, 0xbdf7, 0xb596, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2124, 0x3186, 0x2104, 0x0, 0x0, 0x1082, 0x2965, 0x3185, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb596, 0xc5f7, 0xad55, 0xbdb6, 0xbdf7, 0xb596, 0xdedb, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2124, 0x2944, 0x2944, 0x2124, 0x2104, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xd69a, 0xb596, 0xc5f7, 0xad55, 0xb5b6, 0xbdd7, 0xb596, 0xdedb, 0xffff, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffbe, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xffff, 0xd69a, 0xb596, 0xc5f7, 0xad55, 0xb5b6, 0xbdd7, 0xb596, 0xdedb, 0xffff, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffbe, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3165, 0x2103, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2104, 0x2103, 0x2965, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xffff, 0xd699, 0xb596, 0xbdf7, 0xad54, 0xb5b6, 0xbdd7, 0xb596, 0xdedb, 0xffff, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffbe, 0xe73c, 0xf77d, 0x9cf3, 0x0, 0x3186, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x2924, 0x3186, 0x0, 0x9492, 0xef7d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xffff, 0xd699, 0xb596, 0xbdf7, 0xad54, 0xb5b6, 0xbdd7, 0xb596, 0xdedb, 0xffff, 0xffdf, 0xffff, 0xffff, 0xffdf, 0xffff, 0xffbe, 0xe71c, 0xf77d, 0xa4f3, 0x0, 0x18a2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18a2, 0x0, 0x94b2, 0xf77d, 0xe71c, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xffff, 0xd699, 0xb596, 0xbdf7, 0xad54, 0xb5b6, 0xbdd7, 0xb596, 0xdeda, 0xffff, 0xffdf, 0xffff, 0xffff, 0xffdf, 0xffff, 0xffbe, 0xe73c, 0xef5d, 0xdedb, 0x738e, 0x52aa, 0x5aeb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5aaa, 0x6b6d, 0xd6ba, 0xef5d, 0xe73c, 0xf7be, 0xffff, 0xffdf, 0xffff, 0xffff, 0xffdf, 0xffff, 0xd699, 0xb596, 0xbdf7, 0xad34, 0xb5b6, 0xbdd7, 0xb596, 0xdeba, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xffbe, 0xe73c, 0xef3c, 0xef5d, 0xef5d, 0xe71c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe71c, 0xef5d, 0xef5d, 0xef3c, 0xe73c, 0xf7be, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xd679, 0xb596, 0xbdf7, 0xad34, 0xb596, 0xbdd7, 0xb596, 0xdeba, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xef5d, 0xe73c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xe73c, 0xef5d, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xd679, 0xb596, 0xbdf7, 0xad34, 0xb596, 0xbdd7, 0xb595, 0xdeba, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffbe, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xffbe, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xd679, 0xb595, 0xbdd7, 0xad34, 0xb596, 0xbdd6, 0xb595, 0xdeba, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xd679, 0xb595, 0xbdd7, 0xad34, 0xb596, 0xbdd6, 0xb595, 0xdeba, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xce79, 0xb595, 0xbdd7, 0xad34, 0xb596, 0xbdd6, 0xb575, 0xd6ba, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xce79, 0xb595, 0xbdd7, 0xa534, 0xb596, 0xbdd6, 0xb575, 0xd6ba, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xce79, 0xb595, 0xbdd7, 0xa534, 0xb596, 0xbdd6, 0xb575, 0xd6ba, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xf77d, 0xf7be, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xffdf, 0xe71c, 0xdefc, 0xdefc, 0xdefc, 0xdefc, 0xdefc, 0xe73c, 0xffff, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xce79, 0xb575, 0xbdd7, 0xa534, 0xb596, 0xbdd6, 0xb575, 0xd6ba, 0xffff, 0xffbe, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffde, 0xffdf, 0xf79d, 0xce79, 0xce38, 0xce38, 0xe71c, 0xffdf, 0xffde, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xf7be, 0xffff, 0xe73c, 0x7c33, 0x4311, 0x4311, 0x4311, 0x4311, 0x4311, 0x4331, 0x4310, 0x94f5, 0xf79e, 0xffdf, 0xffde, 0xffdf, 0xffdf, 0xffdf, 0xffde, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffbe, 0xffdf, 0xce79, 0xb575, 0xbdd7, 0xa534, 0xb596, 0xbdb6, 0xb575, 0xd6ba, 0xffff, 0xf7be, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xce79, 0xce59, 0xd69a, 0xd69a, 0xc618, 0xef5d, 0xffdf, 0xf7be, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xf7be, 0xffdf, 0xf7be, 0x73f2, 0x4352, 0x6c55, 0x6c35, 0x6c35, 0x6c35, 0x6c35, 0x6c35, 0x6c55, 0x1ab1, 0x9d35, 0xffff, 0xf7be, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xffde, 0xf7be, 0xffdf, 0xce79, 0xb575, 0xbdd7, 0xa534, 0xb595, 0xbdb6, 0xb575, 0xd69a, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xef7d, 0xce38, 0xd69a, 0xd679, 0xd69a, 0xce59, 0xdedb, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffff, 0xd6bb, 0x3b11, 0x6c55, 0x6414, 0x6415, 0x6415, 0x6415, 0x6415, 0x6414, 0x6415, 0x6435, 0x5b71, 0xef5d, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffbf, 0xffdf, 0xffdf, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xce59, 0xb575, 0xbdd7, 0xa534, 0xb595, 0xbdb6, 0xad75, 0xd69a, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xffdf, 0xf77d, 0xce38, 0xd69a, 0xd69a, 0xd69a, 0xce59, 0xdedb, 0xffff, 0xffdf, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffff, 0xd6bb, 0x3b11, 0x6c55, 0x6414, 0x6414, 0x6414, 0x6414, 0x6414, 0x6414, 0x6414, 0x6c35, 0x5371, 0xef5d, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xf79e, 0xe73c, 0xe71c, 0xef5d, 0xffbe, 0xffbf, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xef7d, 0xe71c, 0xe71c, 0xef7d, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xce59, 0xad75, 0xbdd6, 0xa534, 0xb595, 0xb5b6, 0xad75, 0xd69a, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffbe, 0xf7be, 0xef5d, 0xef3c, 0xe6fb, 0xce38, 0xd69a, 0xd69a, 0xd69a, 0xce59, 0xd69a, 0xef5d, 0xe73c, 0xef7d, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffbe, 0xef7e, 0x6391, 0x53b3, 0x6c55, 0x6c35, 0x6c55, 0x6c55, 0x6c55, 0x6c35, 0x7456, 0x3312, 0x8cb4, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xef5d, 0xce38, 0xce38, 0xce58, 0xc638, 0xd699, 0xf79e, 0xffbe, 0xf7be, 0xffbe, 0xdedb, 0xc638, 0xce38, 0xce38, 0xc638, 0xdefb, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xce59, 0xad75, 0xbdd6, 0xa514, 0xb575, 0xb5b6, 0xad75, 0xd69a, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xef7d, 0xce59, 0xce38, 0xce38, 0xce38, 0xd679, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xce59, 0xce38, 0xce38, 0xc638, 0xdefb, 0xffbe, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xdedb, 0x5b91, 0x3b11, 0x3b11, 0x3b11, 0x3b11, 0x3b11, 0x3b11, 0x3af0, 0x7c33, 0xef5d, 0xffde, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf77d, 0xc638, 0xd679, 0xd69a, 0xd69a, 0xd69a, 0xce38, 0xd69a, 0xf7be, 0xffdf, 0xe6fb, 0xc618, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xc618, 0xe71c, 0xffdf, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xffdf, 0xce59, 0xad75, 0xbdb6, 0xa514, 0xb575, 0xb5b6, 0xad75, 0xd69a, 0xffdf, 0xf79e, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xd69a, 0xce59, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xc618, 0xef5d, 0xf7be, 0xf79e, 0xf7be, 0xf7be, 0xf79e, 0xffdf, 0xef7d, 0xce7a, 0xc659, 0xce59, 0xce59, 0xce59, 0xce59, 0xd6ba, 0xf79e, 0xffbe, 0xf79e, 0xf7be, 0xf7be, 0xf79e, 0xffbe, 0xdedb, 0xce38, 0xd69a, 0xd679, 0xd69a, 0xd69a, 0xd69a, 0xc638, 0xef5d, 0xf7be, 0xce59, 0xd679, 0xd69a, 0xd69a, 0xd679, 0xd69a, 0xd679, 0xce79, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf7be, 0xf79e, 0xffbe, 0xce59, 0xad75, 0xbdb6, 0xa514, 0xad55, 0xb5b6, 0xad75, 0xd679, 0xffbe, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf7be, 0xf79e, 0xce59, 0xd69a, 0xd69a, 0xd679, 0xd679, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd679, 0xd679, 0xd69a, 0xce59, 0xdefb, 0xffbe, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf7be, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffdf, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf7be, 0xd6ba, 0xce59, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xce38, 0xe73c, 0xf79e, 0xce58, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd679, 0xce59, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf7be, 0xce38, 0xad75, 0xbdb6, 0xa513, 0xa514, 0xb5b6, 0xad75, 0xce59, 0xf7be, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xd679, 0xce59, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xc618, 0xe73c, 0xf7be, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf7be, 0xf79e, 0xe71c, 0xdedb, 0xdedb, 0xdedb, 0xdedb, 0xdedb, 0xe73c, 0xf7be, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf7be, 0xdeda, 0xce59, 0xd69a, 0xd679, 0xd69a, 0xd679, 0xd69a, 0xc638, 0xef3c, 0xf79e, 0xce59, 0xd679, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd679, 0xce79, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xc618, 0xad75, 0xbdb6, 0x9cd3, 0x9cd3, 0xbdb6, 0xad75, 0xc618, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xef3c, 0xce38, 0xce38, 0xce38, 0xce58, 0xd679, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xce59, 0xce38, 0xce58, 0xc638, 0xd6ba, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf77d, 0xf7be, 0xe71c, 0x7bcf, 0x20e3, 0x0, 0x0, 0x0, 0x0, 0x841, 0x2945, 0x94b2, 0xef7d, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xef5d, 0xc638, 0xd679, 0xd69a, 0xd69a, 0xd69a, 0xce59, 0xce79, 0xf79e, 0xf79e, 0xdedb, 0xc638, 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xc618, 0xdefb, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf77d, 0xbdd7, 0xad75, 0xbdb6, 0x9471, 0x8c51, 0xbdb6, 0xb575, 0xb5b6, 0xef5d, 0xf79e, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf79e, 0xef7d, 0xe6fb, 0xe6fb, 0xdeba, 0xce38, 0xd69a, 0xd69a, 0xd69a, 0xce59, 0xce79, 0xe6fb, 0xdefb, 0xe73c, 0xf79e, 0xf77e, 0xf77d, 0xf77d, 0xf77d, 0xf79e, 0xef7d, 0x632c, 0x0, 0x39a6, 0x3186, 0x3186, 0x3186, 0x3186, 0x3186, 0x3186, 0x0, 0x9cb2, 0xf7be, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf79e, 0xe71b, 0xc638, 0xce38, 0xce59, 0xce38, 0xce59, 0xef5d, 0xf79e, 0xf77d, 0xf77d, 0xd69a, 0xc638, 0xce59, 0xce59, 0xc638, 0xd6ba, 0xf79e, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf79e, 0xef3c, 0xb595, 0xb595, 0xbdb6, 0x7bef, 0x6b6d, 0xb5b6, 0xb596, 0xad75, 0xdefb, 0xf79e, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf79d, 0xf79e, 0xf7be, 0xef5d, 0xce38, 0xd69a, 0xd69a, 0xd69a, 0xce59, 0xdeda, 0xf7be, 0xf79e, 0xf79e, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xef7d, 0xf7be, 0xce79, 0x0, 0x4208, 0x3165, 0x3165, 0x3165, 0x3165, 0x3165, 0x3165, 0x31a6, 0x3186, 0x3186, 0xe71c, 0xf79e, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf79e, 0xef5d, 0xdedb, 0xdeba, 0xe6fb, 0xef7d, 0xf79e, 0xf77d, 0xf77d, 0xf77d, 0xf79e, 0xe71c, 0xdeda, 0xdeda, 0xe73c, 0xf79e, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf79e, 0xd6ba, 0xad55, 0xb596, 0xb596, 0x5aeb, 0x4208, 0xad75, 0xb596, 0xad75, 0xc638, 0xf79e, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xe73c, 0xce38, 0xd69a, 0xd679, 0xd69a, 0xce59, 0xd6ba, 0xf79d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xf7be, 0xce58, 0x0, 0x41e7, 0x2965, 0x3165, 0x3165, 0x3165, 0x3165, 0x3165, 0x3165, 0x31a6, 0x2104, 0xdefb, 0xf79e, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xf77d, 0xf79e, 0xf79e, 0xf79e, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xf79e, 0xf79e, 0xf79e, 0xf79d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xbdf7, 0xad75, 0xb5b6, 0xa534, 0x3165, 0x0, 0x9492, 0xb5b6, 0xb575, 0xb575, 0xe71c, 0xef7d, 0xef5d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef5d, 0xce59, 0xce79, 0xd69a, 0xd69a, 0xc618, 0xe6fb, 0xf77d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef5d, 0xf77d, 0xe71c, 0x2924, 0x1082, 0x4208, 0x39c7, 0x39c7, 0x39c7, 0x39c7, 0x39c7, 0x4208, 0x0, 0x738e, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef5d, 0xef5d, 0xef5d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef5d, 0xef5d, 0xef5d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, 0xef5d, 0xf77d, 0xdefb, 0xad75, 0xb595, 0xb5b6, 0x8c30, 0x0, 0x841, 0x630c, 0xb596, 0xb595, 0xad75, 0xc618, 0xef7d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef7d, 0xe71c, 0xce38, 0xce38, 0xc638, 0xd6ba, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xf79e, 0xc618, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4a48, 0xd6ba, 0xf77d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xbdd7, 0xad75, 0xb596, 0xb575, 0x5269, 0x18c3, 0x3165, 0x820, 0x9cd2, 0xb5b6, 0xb575, 0xad55, 0xd69a, 0xef7d, 0xef3c, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef3c, 0xef5c, 0xef3c, 0xef5d, 0xef3c, 0xdefb, 0xe71c, 0xef5d, 0xef5d, 0xef3c, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef3c, 0xef7d, 0xdeba, 0xb5b6, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xbdd7, 0xe71c, 0xef7d, 0xef3c, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5c, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef5c, 0xef5d, 0xef5d, 0xef5d, 0xef3c, 0xef5d, 0xef3c, 0xef7d, 0xce59, 0xad55, 0xb575, 0xb5b6, 0x8c71, 0x0, 0x3186, 0x3165, 0x18a2, 0x528a, 0xb575, 0xb595, 0xb575, 0xb575, 0xdedb, 0xef5d, 0xe73c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef5d, 0xef5d, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xe73c, 0xef5d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xf77d, 0xef5d, 0xe73c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xef3c, 0xe73c, 0xef5d, 0xd6ba, 0xad75, 0xb575, 0xb596, 0xad55, 0x41e7, 0x2104, 0x2965, 0x2945, 0x3186, 0x0, 0x7bcf, 0xb5b6, 0xb575, 0xb575, 0xb575, 0xdedb, 0xef5d, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xef5d, 0xd6ba, 0xad75, 0xb575, 0xb575, 0xb5b6, 0x6b6d, 0x0, 0x3186, 0x2945, 0x2945, 0x2945, 0x3165, 0x800, 0x9492, 0xb5b6, 0xad75, 0xad75, 0xad75, 0xd679, 0xef3c, 0xef3c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xef3c, 0xe73c, 0xce59, 0xad55, 0xb575, 0xad75, 0xb5b6, 0x8430, 0x0, 0x3186, 0x2945, 0x2945, 0x2945, 0x2945, 0x2965, 0x2945, 0x20e3, 0x9cb2, 0xb5b6, 0xad75, 0xad75, 0xad55, 0xbdf7, 0xdedb, 0xe73c, 0xef3c, 0xe73c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe73c, 0xef3c, 0xe73c, 0xdeba, 0xbdd7, 0xad55, 0xad75, 0xad75, 0xb5b6, 0x8c71, 0x0, 0x2965, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2965, 0x2924, 0x20e3, 0x9471, 0xb5b6, 0xb575, 0xad75, 0xad55, 0xad75, 0xbdf7, 0xd69a, 0xdefb, 0xe71c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, 0xe71c, 0xdefb, 0xd679, 0xbdd7, 0xad55, 0xad55, 0xad75, 0xb575, 0xb596, 0x8430, 0x800, 0x2945, 0x2965, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2965, 0x2945, 0x0, 0x7bcf, 0xad75, 0xb596, 0xad75, 0xad75, 0xad55, 0xad55, 0xb575, 0xbdb6, 0xbdf7, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xc618, 0xbdf7, 0xbdb6, 0xad75, 0xad55, 0xad55, 0xad75, 0xb575, 0xb596, 0xad55, 0x6b6d, 0x0, 0x2965, 0x2965, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2965, 0x3165, 0x0, 0x5269, 0x94b2, 0xb595, 0xb596, 0xb595, 0xad75, 0xad75, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad55, 0xad75, 0xad75, 0xb596, 0xb596, 0xad75, 0x8c71, 0x4208, 0x0, 0x3186, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x3186, 0x18c3, 0x0, 0x5acb, 0x8c71, 0xad34, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb596, 0xb595, 0xa534, 0x8c51, 0x528a, 0x0, 0x20e3, 0x3186, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2965, 0x3165, 0x1062, 0x0, 0x39e7, 0x632c, 0x8410, 0x9492, 0x9cd3, 0xa4f3, 0xa514, 0xa513, 0xa513, 0xa514, 0xa513, 0xa513, 0xa514, 0xa514, 0xa514, 0xa514, 0xa514, 0xa514, 0xa513, 0xa513, 0xa514, 0xa513, 0xa514, 0xa513, 0xa513, 0xa513, 0xa513, 0xa513, 0xa514, 0xa513, 0xa514, 0xa514, 0xa513, 0xa513, 0xa514, 0xa514, 0xa514, 0xa513, 0xa514, 0xa514, 0xa514, 0xa513, 0xa513, 0xa513, 0xa514, 0xa514, 0xa514, 0xa513, 0xa513, 0xa514, 0x9cf3, 0x9cd3, 0x9491, 0x7bef, 0x630c, 0x31a6, 0x0, 0x18a2, 0x3186, 0x2965, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, 0x2945, +}; + +#endif // __IMAGE_image_to_show_H diff --git a/ion/src/device/n0100/flash.ld b/ion/src/device/n0100/flash.ld index e9b073bb21e..d22fe7b9fd8 100644 --- a/ion/src/device/n0100/flash.ld +++ b/ion/src/device/n0100/flash.ld @@ -71,7 +71,7 @@ SECTIONS { /* The data section is written to Flash but linked as if it were in RAM. * * This is required because its initial value matters (so it has to be in - * persistant memory in the first place), but it is a R/W area of memory + * persistent memory in the first place), but it is a R/W area of memory * so it will have to live in RAM upon execution (in linker lingo, that * translates to the data section having a LMA in Flash and a VMA in RAM). * diff --git a/ion/src/device/n0100/platform_info.cpp b/ion/src/device/n0100/platform_info.cpp index 90d205352ba..084438ca8f1 100644 --- a/ion/src/device/n0100/platform_info.cpp +++ b/ion/src/device/n0100/platform_info.cpp @@ -13,6 +13,10 @@ #error This file expects OMEGA_VERSION to be defined #endif +#ifndef UPSILON_VERSION +#error This file expects UPSILON_VERSION to be defined +#endif + #ifndef HEADER_SECTION #define HEADER_SECTION #endif @@ -31,30 +35,45 @@ class PlatformInfo { m_storageAddress(storageAddress), m_storageSize(Ion::Storage::k_storageSize), m_footer(Magic), - m_ohm_header(OmegaMagic), + m_omegaMagicHeader(OmegaMagic), m_omegaVersion{OMEGA_VERSION}, #ifdef OMEGA_USERNAME m_username{OMEGA_USERNAME}, #else m_username{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, #endif - m_ohm_footer(OmegaMagic) { } + m_omegaMagicFooter(OmegaMagic), + m_upsilonMagicHeader(UpsilonMagic), + m_upsilonVersion{UPSILON_VERSION}, + m_osType(OSType), + m_upsilonMagicFooter(UpsilonMagic) { } const char * version() const { assert(m_storageAddress != nullptr); assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_version; } + const char * upsilonVersion() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + assert(m_upsilonMagicHeader == UpsilonMagic); + assert(m_upsilonMagicFooter == UpsilonMagic); + return m_upsilonVersion; + } const char * omegaVersion() const { assert(m_storageAddress != nullptr); assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_omegaVersion; } const volatile char * username() const volatile { @@ -62,8 +81,8 @@ class PlatformInfo { assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_username; } const char * patchLevel() const { @@ -71,23 +90,33 @@ class PlatformInfo { assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_patchLevel; } + const void * storage_address() const { + return storageAddress; + } private: constexpr static uint32_t Magic = 0xDEC00DF0; constexpr static uint32_t OmegaMagic = 0xEFBEADDE; + constexpr static uint32_t UpsilonMagic = 0x55707369; + constexpr static uint32_t OSType = 0x79827178; uint32_t m_header; const char m_version[8]; const char m_patchLevel[8]; void * m_storageAddress; size_t m_storageSize; uint32_t m_footer; - uint32_t m_ohm_header; + uint32_t m_omegaMagicHeader; const char m_omegaVersion[16]; const volatile char m_username[16]; - uint32_t m_ohm_footer; + uint32_t m_omegaMagicFooter; + uint32_t m_upsilonMagicHeader; + const char m_upsilonVersion[16]; + uint32_t m_osType; + uint32_t m_upsilonMagicFooter; + }; const PlatformInfo HEADER_SECTION platform_infos; @@ -96,6 +125,10 @@ const char * Ion::softwareVersion() { return platform_infos.version(); } +const char * Ion::upsilonVersion() { + return platform_infos.upsilonVersion(); +} + const char * Ion::omegaVersion() { return platform_infos.omegaVersion(); } @@ -108,6 +141,10 @@ const char * Ion::patchLevel() { return platform_infos.patchLevel(); } +const void * Ion::storageAddress() { + return platform_infos.storage_address(); +} + void Ion::updateSlotInfo() { - -} \ No newline at end of file + +} diff --git a/ion/src/device/n0110/Makefile b/ion/src/device/n0110/Makefile index 11c3b224f94..24b1a720469 100644 --- a/ion/src/device/n0110/Makefile +++ b/ion/src/device/n0110/Makefile @@ -1,5 +1,4 @@ ion_device_src += $(addprefix ion/src/device/n0110/drivers/, \ - board.cpp \ cache.cpp \ external_flash.cpp \ led.cpp \ @@ -8,9 +7,21 @@ ion_device_src += $(addprefix ion/src/device/n0110/drivers/, \ usb.cpp \ ) +ifeq ($(filter bootloader, ${MAKECMDGOALS}), bootloader) +ion_device_src += $(addprefix bootloader/drivers/, \ + board.cpp \ +) +ion_device_src += $(addprefix bootloader/boot/, \ + rt0.cpp \ +) +else +ion_device_src += $(addprefix ion/src/device/n0110/drivers/, \ + board.cpp \ +) ion_device_src += $(addprefix ion/src/device/n0110/boot/, \ rt0.cpp \ ) +endif ion_device_src += $(addprefix ion/src/device/n0110/, \ platform_info.cpp \ diff --git a/ion/src/device/n0110/drivers/board.cpp b/ion/src/device/n0110/drivers/board.cpp index 23579c5a262..7060da3e5f5 100644 --- a/ion/src/device/n0110/drivers/board.cpp +++ b/ion/src/device/n0110/drivers/board.cpp @@ -62,7 +62,7 @@ void initMPU() { /* This is needed for interfacing with the LCD * We define the whole FMC memory bank 1 as strongly ordered, non-executable * and not accessible. We define the FMC command and data addresses as - * writeable non-cachable, non-buffereable and non shareable. */ + * writeable non-cacheable, non-buffereable and non shareable. */ int sector = 0; MPU.RNR()->setREGION(sector++); MPU.RBAR()->setADDR(0x60000000); @@ -104,7 +104,7 @@ void initMPU() { * then an AHB error is given (AN4760). To prevent this to happen, we * configure the MPU to define the whole Quad-SPI addressable space as * strongly ordered, non-executable and not accessible. Plus, we define the - * Quad-SPI region corresponding to the Expternal Chip as executable and + * Quad-SPI region corresponding to the External Chip as executable and * fully accessible (AN4861). */ MPU.RNR()->setREGION(sector++); MPU.RBAR()->setADDR(0x90000000); @@ -434,6 +434,8 @@ bool pcbVersionIsLocked() { return *reinterpret_cast(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex)) == 0; } +void jumpToInternalBootloader() {} + } } } diff --git a/ion/src/device/n0110/drivers/external_flash.cpp b/ion/src/device/n0110/drivers/external_flash.cpp index 612efc01796..5e38449852e 100644 --- a/ion/src/device/n0110/drivers/external_flash.cpp +++ b/ion/src/device/n0110/drivers/external_flash.cpp @@ -89,10 +89,15 @@ class ExternalFlashStatusRegister { public: using Register8::Register8; REGS_BOOL_FIELD_R(BUSY, 0); + REGS_BOOL_FIELD(BP, 2); + REGS_BOOL_FIELD(BP1, 3); + REGS_BOOL_FIELD(BP2, 4); + REGS_BOOL_FIELD(TB, 5); }; class StatusRegister2 : public Register8 { public: using Register8::Register8; + REGS_BOOL_FIELD(SRP1, 0); REGS_BOOL_FIELD(QE, 1); }; }; @@ -428,6 +433,46 @@ void unlockFlash() { wait(); } +void LockSlotA() { + unset_memory_mapped_mode(); + unlockFlash(); + send_command(Command::WriteEnable); + wait(); + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + ExternalFlashStatusRegister::StatusRegister2 currentStatusRegister2(0); + send_read_command(Command::ReadStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(¤tStatusRegister2), sizeof(currentStatusRegister2)); + statusRegister2.setQE(currentStatusRegister2.getQE()); + statusRegister2.setSRP1(true); + statusRegister1.setTB(true); + statusRegister1.setBP2(true); + statusRegister1.setBP1(true); + uint8_t registers[] = {statusRegister1.get(), statusRegister2.get()}; + send_write_command(Command::WriteStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(registers), sizeof(registers), sOperatingModes101); + wait(); + set_as_memory_mapped(); +} + +void LockSlotB() { + unset_memory_mapped_mode(); + unlockFlash(); + send_command(Command::WriteEnable); + wait(); + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + ExternalFlashStatusRegister::StatusRegister2 currentStatusRegister2(0); + send_read_command(Command::ReadStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(¤tStatusRegister2), sizeof(currentStatusRegister2)); + statusRegister2.setQE(currentStatusRegister2.getQE()); + statusRegister2.setSRP1(true); + statusRegister1.setTB(false); + statusRegister1.setBP2(true); + statusRegister1.setBP1(true); + uint8_t registers[] = {statusRegister1.get(), statusRegister2.get()}; + send_write_command(Command::WriteStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(registers), sizeof(registers), sOperatingModes101); + wait(); + set_as_memory_mapped(); +} + void MassErase() { if (Config::NumberOfSectors == 0) { return; diff --git a/ion/src/device/n0110/flash.ld b/ion/src/device/n0110/flash.ld index 9728e4ad381..eb5578ac61f 100644 --- a/ion/src/device/n0110/flash.ld +++ b/ion/src/device/n0110/flash.ld @@ -63,6 +63,10 @@ SECTIONS { . = ALIGN(4); *(.text.start) *(.text.abort) + *(.text.hard_fault_handler) + *(.text.mem_fault_handler) + *(.text.usage_fault_handler) + *(.text.bus_fault_handler) *(.text.isr_systick) *(.text.__assert) *(.text.memcpy) diff --git a/ion/src/device/n0110/internal_flash.ld b/ion/src/device/n0110/internal_flash.ld index 924c67f7d01..2094a6469d9 100644 --- a/ion/src/device/n0110/internal_flash.ld +++ b/ion/src/device/n0110/internal_flash.ld @@ -64,7 +64,7 @@ SECTIONS { /* The data section is written to Flash but linked as if it were in RAM. * * This is required because its initial value matters (so it has to be in - * persistant memory in the first place), but it is a R/W area of memory + * persistent memory in the first place), but it is a R/W area of memory * so it will have to live in RAM upon execution (in linker lingo, that * translates to the data section having a LMA in Flash and a VMA in RAM). * diff --git a/ion/src/device/n0110/platform_info.cpp b/ion/src/device/n0110/platform_info.cpp index 90d205352ba..cfd1c546c44 100644 --- a/ion/src/device/n0110/platform_info.cpp +++ b/ion/src/device/n0110/platform_info.cpp @@ -13,6 +13,10 @@ #error This file expects OMEGA_VERSION to be defined #endif +#ifndef UPSILON_VERSION +#error This file expects UPSILON_VERSION to be defined +#endif + #ifndef HEADER_SECTION #define HEADER_SECTION #endif @@ -31,39 +35,52 @@ class PlatformInfo { m_storageAddress(storageAddress), m_storageSize(Ion::Storage::k_storageSize), m_footer(Magic), - m_ohm_header(OmegaMagic), - m_omegaVersion{OMEGA_VERSION}, + m_omegaMagicHeader(OmegaMagic), + m_OmegaVersion{OMEGA_VERSION}, #ifdef OMEGA_USERNAME m_username{OMEGA_USERNAME}, #else m_username{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, #endif - m_ohm_footer(OmegaMagic) { } + m_omegaMagicFooter(OmegaMagic), + m_upsilonMagicHeader(UpsilonMagic), + m_UpsilonVersion{UPSILON_VERSION}, + m_osType(OSType), + m_upsilonMagicFooter(UpsilonMagic) { } const char * version() const { assert(m_storageAddress != nullptr); assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_version; } + const char * upsilonVersion() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_UpsilonVersion; + } const char * omegaVersion() const { assert(m_storageAddress != nullptr); assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); - return m_omegaVersion; + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + return m_OmegaVersion; } const volatile char * username() const volatile { assert(m_storageAddress != nullptr); assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_username; } const char * patchLevel() const { @@ -71,23 +88,33 @@ class PlatformInfo { assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_patchLevel; } + const void * storage_address() const { + return storageAddress; + } private: constexpr static uint32_t Magic = 0xDEC00DF0; constexpr static uint32_t OmegaMagic = 0xEFBEADDE; + constexpr static uint32_t UpsilonMagic = 0x55707369; + constexpr static uint32_t OSType = 0x79827178; uint32_t m_header; const char m_version[8]; const char m_patchLevel[8]; void * m_storageAddress; size_t m_storageSize; uint32_t m_footer; - uint32_t m_ohm_header; - const char m_omegaVersion[16]; + uint32_t m_omegaMagicHeader; + const char m_OmegaVersion[16]; const volatile char m_username[16]; - uint32_t m_ohm_footer; + uint32_t m_omegaMagicFooter; + uint32_t m_upsilonMagicHeader; + const char m_UpsilonVersion[16]; + uint32_t m_osType; + uint32_t m_upsilonMagicFooter; + }; const PlatformInfo HEADER_SECTION platform_infos; @@ -96,6 +123,10 @@ const char * Ion::softwareVersion() { return platform_infos.version(); } +const char * Ion::upsilonVersion() { + return platform_infos.upsilonVersion(); +} + const char * Ion::omegaVersion() { return platform_infos.omegaVersion(); } @@ -108,6 +139,10 @@ const char * Ion::patchLevel() { return platform_infos.patchLevel(); } +const void * Ion::storageAddress() { + return platform_infos.storage_address(); +} + void Ion::updateSlotInfo() { - + } \ No newline at end of file diff --git a/ion/src/device/regs/rcc.h b/ion/src/device/regs/rcc.h index 017e15aeb69..b6b95193080 100644 --- a/ion/src/device/regs/rcc.h +++ b/ion/src/device/regs/rcc.h @@ -75,6 +75,7 @@ class RCC { public: using Register32::Register32; REGS_BOOL_FIELD(TIM3EN, 1); + REGS_BOOL_FIELD(TIM5EN, 3); REGS_BOOL_FIELD(RTCAPB, 10); REGS_BOOL_FIELD(SPI3EN, 15); REGS_BOOL_FIELD(USART3EN, 18); diff --git a/ion/src/device/shared/Makefile b/ion/src/device/shared/Makefile index 2db3b7db46e..4401005263b 100644 --- a/ion/src/device/shared/Makefile +++ b/ion/src/device/shared/Makefile @@ -1,5 +1,9 @@ include ion/src/device/shared/boot/Makefile +ifneq ($(filter bootloader, ${MAKECMDGOALS}), bootloader) include ion/src/device/shared/usb/Makefile +else +include ion/src/device/bootloader/usb/Makefile +endif include ion/src/device/shared/drivers/Makefile ion_device_src += $(addprefix ion/src/device/shared/, \ diff --git a/ion/src/device/shared/boot/Makefile b/ion/src/device/shared/boot/Makefile index 17dc4a4adbd..66415b3354d 100644 --- a/ion/src/device/shared/boot/Makefile +++ b/ion/src/device/shared/boot/Makefile @@ -1,3 +1,9 @@ +ifeq ($(filter bootloader, ${MAKECMDGOALS}), bootloader) +ion_device_src += $(addprefix bootloader/boot/, \ + isr.c \ +) +else ion_device_src += $(addprefix ion/src/device/shared/boot/, \ isr.c \ ) +endif diff --git a/ion/src/device/shared/drivers/Makefile b/ion/src/device/shared/drivers/Makefile index 67bfb6ff5ad..2db043c5fa4 100644 --- a/ion/src/device/shared/drivers/Makefile +++ b/ion/src/device/shared/drivers/Makefile @@ -25,6 +25,5 @@ ion_device_src += $(addprefix ion/src/device/shared/drivers/, \ swd.cpp \ timing.cpp \ usb.cpp \ - usb_desc.cpp \ wakeup.cpp \ ) diff --git a/ion/src/device/shared/drivers/board.h b/ion/src/device/shared/drivers/board.h index 8d1d1bbb6fa..ff6e88ac8b5 100644 --- a/ion/src/device/shared/drivers/board.h +++ b/ion/src/device/shared/drivers/board.h @@ -40,6 +40,7 @@ void writePCBVersion(PCBVersion version); void lockPCBVersion(); bool pcbVersionIsLocked(); +void jumpToInternalBootloader(); } } } diff --git a/ion/src/device/shared/drivers/display.cpp b/ion/src/device/shared/drivers/display.cpp index 508242ddeb3..6cc1f762c83 100644 --- a/ion/src/device/shared/drivers/display.cpp +++ b/ion/src/device/shared/drivers/display.cpp @@ -51,7 +51,7 @@ bool waitForVBlank() { uint64_t startTime = Timing::millis(); uint64_t timeout = startTime + timeoutDelta; - /* If current time is big enough, currentTime + timeout wraps aroud the + /* If current time is big enough, currentTime + timeout wraps around the * uint64_t. We need to take this into account when computing the terminating * event. * diff --git a/ion/src/device/shared/drivers/exam_mode.cpp b/ion/src/device/shared/drivers/exam_mode.cpp index 51fd7be06f2..121a9a15c23 100644 --- a/ion/src/device/shared/drivers/exam_mode.cpp +++ b/ion/src/device/shared/drivers/exam_mode.cpp @@ -34,7 +34,7 @@ char ones[Config::ExamModeBufferSize] constexpr static size_t numberOfBitsInByte = 8; -// if i = 0b000011101, firstOneBitInByte(i) returns 5 +// if i = 0b000011101, numberOfBitsAfterLeadingZeroes(i) returns 5 size_t numberOfBitsAfterLeadingZeroes(int i) { int minShift = 0; int maxShift = numberOfBitsInByte; diff --git a/ion/src/device/shared/drivers/external_flash.h b/ion/src/device/shared/drivers/external_flash.h index f2c1ddd6fa2..08ce49fb051 100644 --- a/ion/src/device/shared/drivers/external_flash.h +++ b/ion/src/device/shared/drivers/external_flash.h @@ -35,6 +35,9 @@ void EraseSector(int i); void WriteMemory(uint8_t * destination, const uint8_t * source, size_t length); void JDECid(uint8_t * manufacturerID, uint8_t * memoryType, uint8_t * capacityType); +void LockSlotA(); +void LockSlotB(); + static constexpr uint8_t NumberOfAddressBitsInChip = 23; // 2^23 Bytes = 8 MBytes static constexpr uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; diff --git a/ion/src/device/shared/drivers/flash.cpp b/ion/src/device/shared/drivers/flash.cpp index 692bd4df8ba..fd5ad33e240 100644 --- a/ion/src/device/shared/drivers/flash.cpp +++ b/ion/src/device/shared/drivers/flash.cpp @@ -50,6 +50,38 @@ void WriteMemory(uint8_t * destination, uint8_t * source, size_t length) { } } +void DisableInternalProtection() { + InternalFlash::DisableProtection(); +} + +void EnableInternalProtection() { + InternalFlash::EnableProtection(); +} + +void SetInternalSectorProtection(int i, bool protect) { + InternalFlash::SetSectorProtection(i, protect); +} + +void LockInternalFlashForSession() { + InternalFlash::EnableSessionLock(); +} + +void EnableInternalFlashInterrupt() { + InternalFlash::EnableFlashInterrupt(); +} + +void ClearInternalFlashErrors() { + InternalFlash::ClearErrors(); +} + +void LockSlotA() { + ExternalFlash::LockSlotA(); +} + +void LockSlotB() { + ExternalFlash::LockSlotB(); +} + } } } diff --git a/ion/src/device/shared/drivers/flash.h b/ion/src/device/shared/drivers/flash.h index 4e61b1fa912..1ff4bb6149e 100644 --- a/ion/src/device/shared/drivers/flash.h +++ b/ion/src/device/shared/drivers/flash.h @@ -1,5 +1,5 @@ -#ifndef ION_DEVICE_SHARED_FLASH_H -#define ION_DEVICE_SHARED_FLASH_H +#ifndef ION_DEVICE_SHARED_FLASH_DRIVER_H +#define ION_DEVICE_SHARED_FLASH_DRIVER_H #include #include @@ -15,6 +15,15 @@ void MassErase(); void EraseSector(int i); void WriteMemory(uint8_t * destination, uint8_t * source, size_t length); +void DisableInternalProtection(); +void EnableInternalProtection(); +void SetInternalSectorProtection(int i, bool protect); +void LockInternalFlashForSession(); // Will cause BUSERR when enabled +void EnableInternalFlashInterrupt(); +void ClearInternalFlashErrors(); +void LockSlotA(); +void LockSlotB(); + } } } diff --git a/ion/src/device/shared/drivers/internal_flash.cpp b/ion/src/device/shared/drivers/internal_flash.cpp index cd8fa60bd43..1d8eb894f0a 100644 --- a/ion/src/device/shared/drivers/internal_flash.cpp +++ b/ion/src/device/shared/drivers/internal_flash.cpp @@ -22,6 +22,7 @@ static inline void wait() { static void open() { // Unlock the Flash configuration register if needed if (FLASH.CR()->getLOCK()) { + // https://www.numworks.com/resources/engineering/hardware/electrical/parts/stm32f730-arm-mcu-reference-manual-1b6e1356.pdf#page=82 FLASH.KEYR()->set(0x45670123); FLASH.KEYR()->set(0xCDEF89AB); } @@ -31,6 +32,88 @@ static void open() { FLASH.CR()->setPSIZE(MemoryAccessWidth); } +static void open_protection() { + if (FLASH.OPTCR()->getLOCK()) { + // https://www.numworks.com/resources/engineering/hardware/electrical/parts/stm32f730-arm-mcu-reference-manual-1b6e1356.pdf#page=82 + FLASH.OPTKEYR()->set(0x08192A3B); + FLASH.OPTKEYR()->set(0x4C5D6E7F); + } +} + +static void close_protection() { + if(!FLASH.OPTCR()->getLOCK()) { + FLASH.OPTCR()->setLOCK(true); + } +} + +static void disable_protection_at(int i) { + if (!FLASH.OPTCR()->getLOCK()) { + switch (i) + { + case 0: + FLASH.OPTCR()->setnWRP0(true); + break; + case 1: + FLASH.OPTCR()->setnWRP1(true); + break; + case 2: + FLASH.OPTCR()->setnWRP2(true); + break; + case 3: + FLASH.OPTCR()->setnWRP3(true); + break; + case 4: + FLASH.OPTCR()->setnWRP4(true); + break; + case 5: + FLASH.OPTCR()->setnWRP5(true); + break; + case 6: + FLASH.OPTCR()->setnWRP6(true); + break; + case 7: + FLASH.OPTCR()->setnWRP7(true); + break; + default: + break; + } + } +} + +static void enable_protection_at(int i) { + if (!FLASH.OPTCR()->getLOCK()) { + switch (i) + { + case 0: + FLASH.OPTCR()->setnWRP0(false); + break; + case 1: + FLASH.OPTCR()->setnWRP1(false); + break; + case 2: + FLASH.OPTCR()->setnWRP2(false); + break; + case 3: + FLASH.OPTCR()->setnWRP3(false); + break; + case 4: + FLASH.OPTCR()->setnWRP4(false); + break; + case 5: + FLASH.OPTCR()->setnWRP5(false); + break; + case 6: + FLASH.OPTCR()->setnWRP6(false); + break; + case 7: + FLASH.OPTCR()->setnWRP7(false); + break; + default: + break; + } + } +} + static void close() { // Clear error flags class FLASH::SR sr(0); @@ -247,6 +330,56 @@ void WriteMemory(uint8_t * destination, uint8_t * source, size_t length) { close(); } +void EnableProtection() { + close_protection(); +} + +void DisableProtection() { + open_protection(); +} + +void SetSectorProtection(int i, bool protect) { + if (protect) { + enable_protection_at(i); + } else { + disable_protection_at(i); + } +} + +void EnableSessionLock() { + if (FLASH.OPTCR()->getLOCK()) { + // writing bullshit to the lock register to lock it until next core reset + // https://www.numworks.com/resources/engineering/hardware/electrical/parts/stm32f730-arm-mcu-reference-manual-1b6e1356.pdf#page=82 + // > "In the event of an unsuccessful unlock operation, this bit remains set until the next reset." + FLASH.OPTKEYR()->set(0x00000000); + FLASH.OPTKEYR()->set(0xFFFFFFFF); + + // Now, a bus fault error is triggered + } +} + +void EnableFlashInterrupt() { + open(); + FLASH.CR()->setERRIE(true); + wait(); + FLASH.CR()->setEOPIE(true); + wait(); + FLASH.CR()->setRDERRIE(true); + wait(); + close(); +} + +void ClearErrors() { + class FLASH::SR sr(0); + // Error flags are cleared by writing 1 + sr.setERSERR(true); + sr.setPGPERR(true); + sr.setPGAERR(true); + sr.setWRPERR(true); + sr.setEOP(true); + FLASH.SR()->set(sr); +} + } } } diff --git a/ion/src/device/shared/drivers/internal_flash.h b/ion/src/device/shared/drivers/internal_flash.h index befe83b3ef5..818a0a41454 100644 --- a/ion/src/device/shared/drivers/internal_flash.h +++ b/ion/src/device/shared/drivers/internal_flash.h @@ -15,6 +15,13 @@ void EraseSector(int i); void WriteMemory(uint8_t * destination, uint8_t * source, size_t length); +void EnableProtection(); +void DisableProtection(); +void SetSectorProtection(int i, bool protect); +void EnableSessionLock(); +void EnableFlashInterrupt(); +void ClearErrors(); + /* The Device is powered by a 2.8V LDO. This allows us to perform writes to the * Flash 32 bits at once. */ constexpr Regs::FLASH::CR::PSIZE MemoryAccessWidth = Regs::FLASH::CR::PSIZE::X32; diff --git a/ion/src/device/shared/drivers/reset.cpp b/ion/src/device/shared/drivers/reset.cpp index ef605357004..67e52f3d6b4 100644 --- a/ion/src/device/shared/drivers/reset.cpp +++ b/ion/src/device/shared/drivers/reset.cpp @@ -49,7 +49,7 @@ void jump(uint32_t jumpIsrVectorAddress) { // Disable cache before reset Ion::Device::Cache::disable(); - /* Shutdown all clocks and periherals to mimic a hardware reset. */ + /* Shutdown all clocks and peripherals to mimic a hardware reset. */ Board::shutdownPeripherals(); internalFlashJump(jumpIsrVectorAddress); diff --git a/ion/src/device/shared/drivers/usb_desc.cpp b/ion/src/device/shared/drivers/usb_desc.cpp index 34ea8527ae8..3d87d72fe9a 100644 --- a/ion/src/device/shared/drivers/usb_desc.cpp +++ b/ion/src/device/shared/drivers/usb_desc.cpp @@ -12,4 +12,4 @@ const char* stringDescriptor() { } } -} \ No newline at end of file +} diff --git a/ion/src/device/shared/drivers/wakeup.h b/ion/src/device/shared/drivers/wakeup.h index d8cb22c7b81..7bf12d585f1 100644 --- a/ion/src/device/shared/drivers/wakeup.h +++ b/ion/src/device/shared/drivers/wakeup.h @@ -8,7 +8,7 @@ namespace Device { namespace WakeUp { /* All wakeup functions can be called together without overwriting the same - * register. All togethed, they will set SYSCFG and EXTi registers as follow: + * register. All together, they will set SYSCFG and EXTi registers as follow: * * GPIO Pin Number|EXTI_EMR|EXTI_FTSR|EXTI_RTSR|EXTICR1|EXTICR2|EXTICR3| Wake up * ---------------+--------+---------+---------+-------+-------+-------+------------------------- diff --git a/ion/src/device/shared/ram.ld b/ion/src/device/shared/ram.ld index 6cf176697df..ef027f3e0b2 100644 --- a/ion/src/device/shared/ram.ld +++ b/ion/src/device/shared/ram.ld @@ -22,7 +22,11 @@ MEMORY { * object). Using a stack too small would result in some memory being * overwritten (for instance, vtables that live in the .rodata section). */ -STACK_SIZE = 32K; +/* The image is quite large too! + * So we put the stack to 18K so there's still space + * for our image, if not LD will throw an error. */ + +STACK_SIZE = 18K; SECTIONS { .isr_vector_table ORIGIN(RAM_BUFFER) : { diff --git a/ion/src/device/shared/regs/flash.h b/ion/src/device/shared/regs/flash.h index f17637710dc..4896e2b42c1 100644 --- a/ion/src/device/shared/regs/flash.h +++ b/ion/src/device/shared/regs/flash.h @@ -28,6 +28,9 @@ class FLASH { class KEYR : public Register32 { }; + class OPTKEYR : public Register32 { + }; + class CR : public Register32 { public: enum class PSIZE : uint8_t { @@ -42,6 +45,9 @@ class FLASH { REGS_FIELD(SNB, uint8_t, 6, 3); REGS_TYPE_FIELD(PSIZE, 9, 8); REGS_BOOL_FIELD(STRT, 16); + REGS_BOOL_FIELD(EOPIE, 24); + REGS_BOOL_FIELD(ERRIE, 25); + REGS_BOOL_FIELD(RDERRIE, 26) REGS_BOOL_FIELD(LOCK, 31); }; @@ -56,11 +62,26 @@ class FLASH { REGS_BOOL_FIELD(EOP, 0); }; + class OPTCR : public Register32 { + public: + REGS_BOOL_FIELD(nWRP0, 16); + REGS_BOOL_FIELD(nWRP1, 17); + REGS_BOOL_FIELD(nWRP2, 18); + REGS_BOOL_FIELD(nWRP3, 19); + REGS_BOOL_FIELD(nWRP4, 20); + REGS_BOOL_FIELD(nWRP5, 21); + REGS_BOOL_FIELD(nWRP6, 22); + REGS_BOOL_FIELD(nWRP7, 23); + REGS_BOOL_FIELD(LOCK, 0); + }; + constexpr FLASH() {}; REGS_REGISTER_AT(ACR, 0x00); REGS_REGISTER_AT(KEYR, 0x04); + REGS_REGISTER_AT(OPTKEYR, 0x08); REGS_REGISTER_AT(SR, 0x0C); REGS_REGISTER_AT(CR, 0x10); + REGS_REGISTER_AT(OPTCR, 0x14); private: constexpr uint32_t Base() const { return 0x40023C00; diff --git a/ion/src/device/shared/regs/rcc.h b/ion/src/device/shared/regs/rcc.h index 1355085741f..07b8854bed8 100644 --- a/ion/src/device/shared/regs/rcc.h +++ b/ion/src/device/shared/regs/rcc.h @@ -14,6 +14,12 @@ class RCC { public: REGS_BOOL_FIELD(HSION, 0); REGS_BOOL_FIELD(HSIRDY, 1); + REGS_BOOL_FIELD(HSITRIM1, 3); + REGS_BOOL_FIELD(HSITRIM2, 4); + REGS_BOOL_FIELD(HSITRIM3, 5); + REGS_BOOL_FIELD(HSITRIM4, 6); + REGS_BOOL_FIELD(HSITRIM5, 7); + REGS_BOOL_FIELD(HSICAL, 8); REGS_BOOL_FIELD(HSEON, 16); REGS_BOOL_FIELD_R(HSERDY, 17); REGS_BOOL_FIELD(PLLON, 24); @@ -112,6 +118,7 @@ class RCC { public: using Register32::Register32; REGS_BOOL_FIELD(TIM3EN, 1); + REGS_BOOL_FIELD(TIM5EN, 3); REGS_BOOL_FIELD(RTCAPB, 10); REGS_BOOL_FIELD(SPI3EN, 15); REGS_BOOL_FIELD(USART3EN, 18); diff --git a/ion/src/device/shared/regs/syscfg.h b/ion/src/device/shared/regs/syscfg.h index 82dfddda605..e044a65f630 100644 --- a/ion/src/device/shared/regs/syscfg.h +++ b/ion/src/device/shared/regs/syscfg.h @@ -5,6 +5,8 @@ #include #include "gpio.h" +#define REGS_SYSCFG_CONFIG_F412 1 + namespace Ion { namespace Device { namespace Regs { diff --git a/ion/src/device/shared/regs/tim.h b/ion/src/device/shared/regs/tim.h index f92d119e6da..704bd4fa901 100644 --- a/ion/src/device/shared/regs/tim.h +++ b/ion/src/device/shared/regs/tim.h @@ -17,7 +17,7 @@ class TIM { }; class CCMR : Register64 { - /* We're declaring CCMR as a 64 bits register. CCMR doesn't exsist per se, + /* We're declaring CCMR as a 64 bits register. CCMR doesn't exist per se, * it is in fact the consolidation of CCMR1 and CCMR2. Both are 16 bits * registers, so one could expect the consolidation to be 32 bits. However, * both CCMR1 and CCMR2 live on 32-bits boundaries, so the consolidation has diff --git a/ion/src/device/shared/usb/Makefile b/ion/src/device/shared/usb/Makefile index 02e931b6a21..3ed92c4bd59 100644 --- a/ion/src/device/shared/usb/Makefile +++ b/ion/src/device/shared/usb/Makefile @@ -93,3 +93,7 @@ $(BUILD_DIR)/ion/src/device/shared/usb/dfu.o: $(BUILD_DIR)/ion/src/device/shared ion_device_src += ion/src/device/shared/usb/dfu.cpp:-usbxip ion_device_src += ion/src/device/shared/usb/dfu_relocated.cpp:-usbxip + +ion_device_src += $(addprefix ion/src/device/shared/drivers/, \ + usb_desc.cpp \ +) diff --git a/ion/src/device/shared/usb/calculator.h b/ion/src/device/shared/usb/calculator.h index e5eaba3a467..29a7203c6b4 100644 --- a/ion/src/device/shared/usb/calculator.h +++ b/ion/src/device/shared/usb/calculator.h @@ -93,14 +93,14 @@ class Calculator : public Device { &m_webUSBPlatformDescriptor), m_languageStringDescriptor(), m_manufacturerStringDescriptor("NumWorks"), - m_productStringDescriptor("NumWorks Calculator"), + m_productStringDescriptor("Upsilon Calculator"), m_serialNumberStringDescriptor(serialNumber), m_interfaceStringDescriptor(stringDescriptor()), //m_interfaceStringDescriptor("@SRAM/0x20000000/01*256Ke"), /* Switch to this descriptor to use dfu-util to write in the SRAM. * FIXME Should be an alternate Interface. */ m_microsoftOSStringDescriptor(k_microsoftOSVendorCode), - m_workshopURLDescriptor(URLDescriptor::Scheme::HTTPS, "getomega.dev"), + m_workshopURLDescriptor(URLDescriptor::Scheme::HTTPS, "getupsilon.web.app"), m_extendedCompatIdDescriptor("WINUSB"), m_descriptors{ &m_deviceDescriptor, // Type = Device, Index = 0 @@ -155,7 +155,7 @@ class Calculator : public Device { ExtendedCompatIDDescriptor m_extendedCompatIdDescriptor; Descriptor * m_descriptors[8]; - /* m_descriptors contains only descriptors that sould be returned via the + /* m_descriptors contains only descriptors that should be returned via the * method descriptor(uint8_t type, uint8_t index), so do not count descriptors * included in other descriptors or returned by other functions. */ diff --git a/ion/src/device/shared/usb/dfu.ld b/ion/src/device/shared/usb/dfu.ld index 957bcfdd536..70aff4accbb 100644 --- a/ion/src/device/shared/usb/dfu.ld +++ b/ion/src/device/shared/usb/dfu.ld @@ -1,5 +1,5 @@ /* DFU transfers can serve two purposes: - * - Transfering RAM data between the machine and the host, e.g. Python scripts + * - Transferring RAM data between the machine and the host, e.g. Python scripts * - Upgrading the flash memory to perform a software update * * The second case raises a huge issue: code cannot be executed from memory that diff --git a/ion/src/device/shared/usb/dfu_interface.cpp b/ion/src/device/shared/usb/dfu_interface.cpp index 2b98274b430..2b56a2ebc96 100644 --- a/ion/src/device/shared/usb/dfu_interface.cpp +++ b/ion/src/device/shared/usb/dfu_interface.cpp @@ -1,286 +1,286 @@ -#include "dfu_interface.h" -#include -#include -#include - -namespace Ion { -namespace Device { -namespace USB { - -static inline uint32_t minUint32T(uint32_t x, uint32_t y) { return x < y ? x : y; } - -void DFUInterface::StatusData::push(Channel * c) const { - c->push(m_bStatus); - c->push(m_bwPollTimeout[2]); - c->push(m_bwPollTimeout[1]); - c->push(m_bwPollTimeout[0]); - c->push(m_bState); - c->push(m_iString); -} - -void DFUInterface::StateData::push(Channel * c) const { - c->push(m_bState); -} - -void DFUInterface::wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) { - if (request->bRequest() == (uint8_t) DFURequest::Download) { - // Handle a download request - if (request->wValue() == 0) { - // The request is a special command - switch (transferBuffer[0]) { - case (uint8_t) DFUDownloadCommand::SetAddressPointer: - setAddressPointerCommand(request, transferBuffer, *transferBufferLength); - return; - case (uint8_t) DFUDownloadCommand::Erase: - eraseCommand(transferBuffer, *transferBufferLength); - return; - default: - m_state = State::dfuERROR; - m_status = Status::errSTALLEDPKT; - return; - } - } - if (request->wValue() == 1) { - m_ep0->stallTransaction(); - return; - } - if (request->wLength() > 0) { - // The request is a "real" download. Compute the writing address. - m_writeAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer; - // Store the received data until we copy it on the flash. - memcpy(m_largeBuffer, transferBuffer, *transferBufferLength); - m_largeBufferLength = *transferBufferLength; - m_state = State::dfuDNLOADSYNC; - } - } -} - -void DFUInterface::wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) { - if (request->bRequest() == (uint8_t) DFURequest::GetStatus) { - // Do any needed action after the GetStatus request. - if (m_state == State::dfuMANIFEST) { - /* If we leave the DFU and reset immediately, dfu-util outputs an error: - * "File downloaded successfully - * dfu-util: Error during download get_status" - * If we sleep 1us here, there is no error. We put 1ms for security. - * This error might be due to the USB connection being cut too soon after - * the last USB exchange, so the host does not have time to process the - * answer received for the last GetStatus request. */ - Ion::Timing::msleep(1); - // Leave DFU routine: Leave DFU, reset device, jump to application code - leaveDFUAndReset(); - } else if (m_state == State::dfuDNBUSY) { - if (m_largeBufferLength != 0) { - // Here, copy the data from the transfer buffer to the flash memory - writeOnMemory(); - } - changeAddressPointerIfNeeded(); - eraseMemoryIfNeeded(); - m_state = State::dfuDNLOADIDLE; - } - } -} - -bool DFUInterface::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - if (Interface::processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { - return true; - } - switch (request->bRequest()) { - case (uint8_t) DFURequest::Detach: - m_device->detach(); - return true; - case (uint8_t) DFURequest::Download: - return processDownloadRequest(request->wLength(), transferBufferLength); - case (uint8_t) DFURequest::Upload: - return processUploadRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength); - case (uint8_t) DFURequest::GetStatus: - return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); - case (uint8_t) DFURequest::ClearStatus: - return clearStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); - case (uint8_t) DFURequest::GetState: - return getState(transferBuffer, transferBufferLength, transferBufferMaxLength); - case (uint8_t) DFURequest::Abort: - return dfuAbort(transferBufferLength); - } - return false; -} - -bool DFUInterface::processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength) { - if (m_state != State::dfuIDLE && m_state != State::dfuDNLOADIDLE) { - m_state = State::dfuERROR; - m_status = Status::errNOTDONE; - m_ep0->stallTransaction(); - return false; - } - if (wLength == 0) { - // Leave DFU routine: Reset the device and jump to application code - m_state = State::dfuMANIFESTSYNC; - } else { - // Prepare to receive the download data - m_ep0->clearForOutTransactions(wLength); - m_state = State::dfuDNLOADSYNC; - } - return true; -} - -bool DFUInterface::processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - if (m_state != State::dfuIDLE && m_state != State::dfuUPLOADIDLE) { - m_ep0->stallTransaction(); - return false; - } - if (request->wValue() == 0) { - /* The host requests to read the commands supported by the bootloader. After - * receiving this command, the device should returns N bytes representing - * the command codes for : - * Get command / Set Address Pointer / Erase / Read Unprotect - * We no not need it for now. */ - return false; - } else if (request->wValue() == 1) { - m_ep0->stallTransaction(); - return false; - } else { - /* We decided to never protect Read operation. Else we would have to check - * here it is not protected before reading. */ - - // Compute the reading address - uint32_t readAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer; - // Copy the requested memory zone into the transfer buffer. - uint16_t copySize = minUint32T(transferBufferMaxLength, request->wLength()); - memcpy(transferBuffer, (void *)readAddress, copySize); - *transferBufferLength = copySize; - } - m_state = State::dfuUPLOADIDLE; - return true; -} - -void DFUInterface::setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength) { - assert(transferBufferLength == 5); - // Compute the new address but change it after the next getStatus request. - m_potentialNewAddressPointer = transferBuffer[1] - + (transferBuffer[2] << 8) - + (transferBuffer[3] << 16) - + (transferBuffer[4] << 24); - m_state = State::dfuDNLOADSYNC; -} - -void DFUInterface::changeAddressPointerIfNeeded() { - if (m_potentialNewAddressPointer == 0) { - // There was no address change waiting. - return; - } - // If there is a new address pointer waiting, change the pointer address. - m_addressPointer = m_potentialNewAddressPointer; - m_potentialNewAddressPointer = 0; - m_state = State::dfuDNLOADIDLE; - m_status = Status::OK; -} - -void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength) { - /* We determine whether the commands asks for a mass erase or which sector to - * erase. The erase must be done after the next getStatus request. */ - m_state = State::dfuDNLOADSYNC; - - if (transferBufferLength == 1) { - // Mass erase - m_erasePage = Flash::TotalNumberOfSectors(); - return; - } - - // Sector erase - assert(transferBufferLength == 5); - - uint32_t eraseAddress = transferBuffer[1] - + (transferBuffer[2] << 8) - + (transferBuffer[3] << 16) - + (transferBuffer[4] << 24); - - m_erasePage = Flash::SectorAtAddress(eraseAddress); - if (m_erasePage < 0) { - // Unrecognized sector - m_state = State::dfuERROR; - m_status = Status::errTARGET; - } -} - - -void DFUInterface::eraseMemoryIfNeeded() { - if (m_erasePage < 0) { - // There was no erase waiting. - return; - } - - willErase(); - if (m_erasePage == Flash::TotalNumberOfSectors()) { - Flash::MassErase(); - } else { - Flash::EraseSector(m_erasePage); - } - - /* Put an out of range value in m_erasePage to indicate that no erase is - * waiting. */ - m_erasePage = -1; - m_state = State::dfuDNLOADIDLE; - m_status = Status::OK; -} - -void DFUInterface::writeOnMemory() { - if (m_writeAddress >= k_sramStartAddress && m_writeAddress <= k_sramEndAddress) { - // Write on SRAM - // FIXME We should check that we are not overriding the current instructions. - memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); - } else if (Flash::SectorAtAddress(m_writeAddress) >= 0) { - Flash::WriteMemory(reinterpret_cast(m_writeAddress), m_largeBuffer, m_largeBufferLength); - } else { - // Invalid write address - m_largeBufferLength = 0; - m_state = State::dfuERROR; - m_status = Status::errTARGET; - return; - } - - // Reset the buffer length - m_largeBufferLength = 0; - // Change the interface state and status - m_state = State::dfuDNLOADIDLE; - m_status = Status::OK; -} - - -bool DFUInterface::getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - // Change the status if needed - if (m_state == State::dfuMANIFESTSYNC) { - m_state = State::dfuMANIFEST; - } else if (m_state == State::dfuDNLOADSYNC) { - m_state = State::dfuDNBUSY; - } - // Copy the status on the TxFifo - *transferBufferLength = StatusData(m_status, m_state).copy(transferBuffer, transferBufferMaxLength); - return true; -} - -bool DFUInterface::clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - m_status = Status::OK; - m_state = State::dfuIDLE; - return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); -} - -bool DFUInterface::getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize) { - *transferBufferLength = StateData(m_state).copy(transferBuffer, maxSize); - return true; -} - -bool DFUInterface::dfuAbort(uint16_t * transferBufferLength) { - m_status = Status::OK; - m_state = State::dfuIDLE; - *transferBufferLength = 0; - return true; -} - -void DFUInterface::leaveDFUAndReset() { - m_device->setResetOnDisconnect(true); - m_device->detach(); -} - -} -} -} +#include "dfu_interface.h" +#include +#include +#include + +namespace Ion { +namespace Device { +namespace USB { + +static inline uint32_t minUint32T(uint32_t x, uint32_t y) { return x < y ? x : y; } + +void DFUInterface::StatusData::push(Channel * c) const { + c->push(m_bStatus); + c->push(m_bwPollTimeout[2]); + c->push(m_bwPollTimeout[1]); + c->push(m_bwPollTimeout[0]); + c->push(m_bState); + c->push(m_iString); +} + +void DFUInterface::StateData::push(Channel * c) const { + c->push(m_bState); +} + +void DFUInterface::wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) { + if (request->bRequest() == (uint8_t) DFURequest::Download) { + // Handle a download request + if (request->wValue() == 0) { + // The request is a special command + switch (transferBuffer[0]) { + case (uint8_t) DFUDownloadCommand::SetAddressPointer: + setAddressPointerCommand(request, transferBuffer, *transferBufferLength); + return; + case (uint8_t) DFUDownloadCommand::Erase: + eraseCommand(transferBuffer, *transferBufferLength); + return; + default: + m_state = State::dfuERROR; + m_status = Status::errSTALLEDPKT; + return; + } + } + if (request->wValue() == 1) { + m_ep0->stallTransaction(); + return; + } + if (request->wLength() > 0) { + // The request is a "real" download. Compute the writing address. + m_writeAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer; + // Store the received data until we copy it on the flash. + memcpy(m_largeBuffer, transferBuffer, *transferBufferLength); + m_largeBufferLength = *transferBufferLength; + m_state = State::dfuDNLOADSYNC; + } + } +} + +void DFUInterface::wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) { + if (request->bRequest() == (uint8_t) DFURequest::GetStatus) { + // Do any needed action after the GetStatus request. + if (m_state == State::dfuMANIFEST) { + /* If we leave the DFU and reset immediately, dfu-util outputs an error: + * "File downloaded successfully + * dfu-util: Error during download get_status" + * If we sleep 1us here, there is no error. We put 1ms for security. + * This error might be due to the USB connection being cut too soon after + * the last USB exchange, so the host does not have time to process the + * answer received for the last GetStatus request. */ + Ion::Timing::msleep(1); + // Leave DFU routine: Leave DFU, reset device, jump to application code + leaveDFUAndReset(); + } else if (m_state == State::dfuDNBUSY) { + if (m_largeBufferLength != 0) { + // Here, copy the data from the transfer buffer to the flash memory + writeOnMemory(); + } + changeAddressPointerIfNeeded(); + eraseMemoryIfNeeded(); + m_state = State::dfuDNLOADIDLE; + } + } +} + +bool DFUInterface::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (Interface::processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { + return true; + } + switch (request->bRequest()) { + case (uint8_t) DFURequest::Detach: + m_device->detach(); + return true; + case (uint8_t) DFURequest::Download: + return processDownloadRequest(request->wLength(), transferBufferLength); + case (uint8_t) DFURequest::Upload: + return processUploadRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::GetStatus: + return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::ClearStatus: + return clearStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::GetState: + return getState(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::Abort: + return dfuAbort(transferBufferLength); + } + return false; +} + +bool DFUInterface::processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength) { + if (m_state != State::dfuIDLE && m_state != State::dfuDNLOADIDLE) { + m_state = State::dfuERROR; + m_status = Status::errNOTDONE; + m_ep0->stallTransaction(); + return false; + } + if (wLength == 0) { + // Leave DFU routine: Reset the device and jump to application code + m_state = State::dfuMANIFESTSYNC; + } else { + // Prepare to receive the download data + m_ep0->clearForOutTransactions(wLength); + m_state = State::dfuDNLOADSYNC; + } + return true; +} + +bool DFUInterface::processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (m_state != State::dfuIDLE && m_state != State::dfuUPLOADIDLE) { + m_ep0->stallTransaction(); + return false; + } + if (request->wValue() == 0) { + /* The host requests to read the commands supported by the bootloader. After + * receiving this command, the device should returns N bytes representing + * the command codes for : + * Get command / Set Address Pointer / Erase / Read Unprotect + * We no not need it for now. */ + return false; + } else if (request->wValue() == 1) { + m_ep0->stallTransaction(); + return false; + } else { + /* We decided to never protect Read operation. Else we would have to check + * here it is not protected before reading. */ + + // Compute the reading address + uint32_t readAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer; + // Copy the requested memory zone into the transfer buffer. + uint16_t copySize = minUint32T(transferBufferMaxLength, request->wLength()); + memcpy(transferBuffer, (void *)readAddress, copySize); + *transferBufferLength = copySize; + } + m_state = State::dfuUPLOADIDLE; + return true; +} + +void DFUInterface::setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength) { + assert(transferBufferLength == 5); + // Compute the new address but change it after the next getStatus request. + m_potentialNewAddressPointer = transferBuffer[1] + + (transferBuffer[2] << 8) + + (transferBuffer[3] << 16) + + (transferBuffer[4] << 24); + m_state = State::dfuDNLOADSYNC; +} + +void DFUInterface::changeAddressPointerIfNeeded() { + if (m_potentialNewAddressPointer == 0) { + // There was no address change waiting. + return; + } + // If there is a new address pointer waiting, change the pointer address. + m_addressPointer = m_potentialNewAddressPointer; + m_potentialNewAddressPointer = 0; + m_state = State::dfuDNLOADIDLE; + m_status = Status::OK; +} + +void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength) { + /* We determine whether the commands asks for a mass erase or which sector to + * erase. The erase must be done after the next getStatus request. */ + m_state = State::dfuDNLOADSYNC; + + if (transferBufferLength == 1) { + // Mass erase + m_erasePage = Flash::TotalNumberOfSectors(); + return; + } + + // Sector erase + assert(transferBufferLength == 5); + + uint32_t eraseAddress = transferBuffer[1] + + (transferBuffer[2] << 8) + + (transferBuffer[3] << 16) + + (transferBuffer[4] << 24); + + m_erasePage = Flash::SectorAtAddress(eraseAddress); + if (m_erasePage < 0) { + // Unrecognized sector + m_state = State::dfuERROR; + m_status = Status::errTARGET; + } +} + + +void DFUInterface::eraseMemoryIfNeeded() { + if (m_erasePage < 0) { + // There was no erase waiting. + return; + } + + willErase(); + if (m_erasePage == Flash::TotalNumberOfSectors()) { + Flash::MassErase(); + } else { + Flash::EraseSector(m_erasePage); + } + + /* Put an out of range value in m_erasePage to indicate that no erase is + * waiting. */ + m_erasePage = -1; + m_state = State::dfuDNLOADIDLE; + m_status = Status::OK; +} + +void DFUInterface::writeOnMemory() { + if (m_writeAddress >= k_sramStartAddress && m_writeAddress <= k_sramEndAddress) { + // Write on SRAM + // FIXME We should check that we are not overriding the current instructions. + memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); + } else if (Flash::SectorAtAddress(m_writeAddress) >= 0) { + Flash::WriteMemory(reinterpret_cast(m_writeAddress), m_largeBuffer, m_largeBufferLength); + } else { + // Invalid write address + m_largeBufferLength = 0; + m_state = State::dfuERROR; + m_status = Status::errTARGET; + return; + } + + // Reset the buffer length + m_largeBufferLength = 0; + // Change the interface state and status + m_state = State::dfuDNLOADIDLE; + m_status = Status::OK; +} + + +bool DFUInterface::getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + // Change the status if needed + if (m_state == State::dfuMANIFESTSYNC) { + m_state = State::dfuMANIFEST; + } else if (m_state == State::dfuDNLOADSYNC) { + m_state = State::dfuDNBUSY; + } + // Copy the status on the TxFifo + *transferBufferLength = StatusData(m_status, m_state).copy(transferBuffer, transferBufferMaxLength); + return true; +} + +bool DFUInterface::clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + m_status = Status::OK; + m_state = State::dfuIDLE; + return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); +} + +bool DFUInterface::getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize) { + *transferBufferLength = StateData(m_state).copy(transferBuffer, maxSize); + return true; +} + +bool DFUInterface::dfuAbort(uint16_t * transferBufferLength) { + m_status = Status::OK; + m_state = State::dfuIDLE; + *transferBufferLength = 0; + return true; +} + +void DFUInterface::leaveDFUAndReset() { + m_device->setResetOnDisconnect(true); + m_device->detach(); +} + +} +} +} diff --git a/ion/src/device/shared/usb/dfu_interface.h b/ion/src/device/shared/usb/dfu_interface.h index 0ae25d87488..762243f06ad 100644 --- a/ion/src/device/shared/usb/dfu_interface.h +++ b/ion/src/device/shared/usb/dfu_interface.h @@ -57,7 +57,7 @@ class DFUInterface : public Interface { Abort = 6 }; - // DFU Download Commmand Codes + // DFU Download Command Codes enum class DFUDownloadCommand { GetCommand = 0x00, SetAddressPointer = 0x21, diff --git a/ion/src/device/shared/usb/dfu_relocated.cpp b/ion/src/device/shared/usb/dfu_relocated.cpp index fb001cef1c4..72b9b61be26 100644 --- a/ion/src/device/shared/usb/dfu_relocated.cpp +++ b/ion/src/device/shared/usb/dfu_relocated.cpp @@ -12,9 +12,9 @@ extern char _dfu_bootloader_flash_end; namespace Ion { namespace USB { -typedef void (*PollFunctionPointer)(bool exitWithKeyboard); +typedef void (*PollFunctionPointer)(bool exitWithKeyboard, void * data); -void DFU(bool exitWithKeyboard) { +void DFU(bool exitWithKeyboard, void * data) { Ion::updateSlotInfo(); /* DFU transfers can serve two purposes: @@ -59,7 +59,7 @@ void DFU(bool exitWithKeyboard) { /* 4- Disable all interrupts * The interrupt service routines live in the Flash and could be overwritten - * by garbage during a firmware upgrade opration, so we disable them. */ + * by garbage during a firmware upgrade operation, so we disable them. */ Device::Timing::shutdown(); /* 5- Jump to DFU bootloader code. We made sure in the linker script that the @@ -76,7 +76,7 @@ void DFU(bool exitWithKeyboard) { * add-symbol-file ion/src/device/usb/dfu.elf 0x20038000 */ - dfu_bootloader_entry(exitWithKeyboard); + dfu_bootloader_entry(exitWithKeyboard, data); /* 5- Restore interrupts */ Device::Timing::init(); diff --git a/ion/src/device/shared/usb/dfu_xip.cpp b/ion/src/device/shared/usb/dfu_xip.cpp index fd0447c3268..1a223d4f5dc 100644 --- a/ion/src/device/shared/usb/dfu_xip.cpp +++ b/ion/src/device/shared/usb/dfu_xip.cpp @@ -4,7 +4,7 @@ namespace Ion { namespace USB { -void DFU(bool exitWithKeyboard) { +void DFU(bool exitWithKeyboard, void * data) { Ion::updateSlotInfo(); Ion::Device::USB::Calculator::PollAndReset(exitWithKeyboard); } diff --git a/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h index f434352f24b..8b67cabc6ca 100644 --- a/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h @@ -1,5 +1,5 @@ -#ifndef ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H -#define ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABILITY_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABILITY_DESCRIPTOR_H #include "descriptor.h" diff --git a/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h index d2c425d4a40..3c117348c50 100644 --- a/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h @@ -1,5 +1,5 @@ -#ifndef ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H -#define ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABILITY_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABILITY_DESCRIPTOR_H #include "device_capability_descriptor.h" diff --git a/ion/src/device/shared/usb/stack/device.cpp b/ion/src/device/shared/usb/stack/device.cpp index 294b647e12c..6bfcbef7d6a 100644 --- a/ion/src/device/shared/usb/stack/device.cpp +++ b/ion/src/device/shared/usb/stack/device.cpp @@ -108,7 +108,7 @@ bool Device::processSetupInRequest(SetupPacket * request, uint8_t * transferBuff case (int) Request::GetStatus: return getStatus(transferBuffer, transferBufferLength, transferBufferMaxLength); case (int) Request::SetAddress: - // Make sure the request is adress is valid. + // Make sure the request is address is valid. assert(request->wValue() < 128); /* According to the reference manual, the address should be set after the * Status stage of the current transaction, but this is not true. diff --git a/ion/src/external/lz4/lz4.c b/ion/src/external/lz4/lz4.c index 4046102e6de..3021a96f284 100644 --- a/ion/src/external/lz4/lz4.c +++ b/ion/src/external/lz4/lz4.c @@ -837,7 +837,7 @@ LZ4_FORCE_INLINE int LZ4_compress_generic( _next_match: /* at this stage, the following variables must be correctly set : * - ip : at start of LZ operation - * - match : at start of previous pattern occurence; can be within current prefix, or within extDict + * - match : at start of previous pattern occurrence; can be within current prefix, or within extDict * - offset : if maybe_ext_memSegment==1 (constant) * - lowLimit : must be == dictionary to mean "match is within extDict"; must be == source otherwise * - token and *token : position to write 4-bits for match length; higher 4-bits for literal length supposed already written diff --git a/ion/src/external/lz4/lz4.h b/ion/src/external/lz4/lz4.h index 059ef7c1b7d..517667c3fb0 100644 --- a/ion/src/external/lz4/lz4.h +++ b/ion/src/external/lz4/lz4.h @@ -103,7 +103,7 @@ extern "C" { #define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version */ -LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; unseful to check dll version */ +LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version */ /*-************************************ diff --git a/ion/src/shared/dummy/usb.cpp b/ion/src/shared/dummy/usb.cpp index dfe12752e7e..904b056bc82 100644 --- a/ion/src/shared/dummy/usb.cpp +++ b/ion/src/shared/dummy/usb.cpp @@ -15,7 +15,7 @@ bool isEnumerated() { void clearEnumerationInterrupt() { } -void DFU(bool) { +void DFU(bool, void*) { } void enable() { @@ -26,7 +26,3 @@ void disable() { } } - -void Ion::updateSlotInfo() { - -} diff --git a/ion/src/shared/events_benchmark.cpp b/ion/src/shared/events_benchmark.cpp index adad15e5cfc..9080ae27bea 100644 --- a/ion/src/shared/events_benchmark.cpp +++ b/ion/src/shared/events_benchmark.cpp @@ -41,10 +41,10 @@ static constexpr Event scenariPythonMandelbrot[] = { Right, Right, OK, Down, Dow static constexpr Event scenariStatistics[] = { Down, OK, One, OK, Two, OK, Right, Five, OK, One, Zero, OK, Back, Right, OK, Right, Right, Right, OK, One, OK, Down, OK, Back, Right, OK, Back, Right, OK, Down, Down, Down, Down, Down, Down, Down, Down, Down, Down, Up, Up, Up, Up, Up, Up, Up, Up, Up, Home, Home }; -static constexpr Event scenaryProbability[] = { Down, Right, OK, Down, Down, Down, OK, Two, OK, Zero, Dot, Three, OK, OK, Left, Down, Down, OK, Right, Right, Right, Zero, Dot, Eight, OK, Home, Home +static constexpr Event scenariProbability[] = { Down, Right, OK, Down, Down, Down, OK, Two, OK, Zero, Dot, Three, OK, OK, Left, Down, Down, OK, Right, Right, Right, Zero, Dot, Eight, OK, Home, Home }; -static constexpr Event scenaryEquation[] = { Down, Right, Right, OK, OK, Down, Down, OK, Six, OK, Down, Down, OK, Left, Left, Left, Down, Down, Home, Home +static constexpr Event scenariEquation[] = { Down, Right, Right, OK, OK, Down, Down, OK, Six, OK, Down, Down, OK, Left, Left, Left, Down, Down, Home, Home }; static constexpr Scenario scenari[] = { @@ -52,8 +52,8 @@ static constexpr Scenario scenari[] = { Scenario::build("Sin/Cos graph", scenariFunctionCosSin), Scenario::build("Mandelbrot(15)", scenariPythonMandelbrot), Scenario::build("Statistics", scenariStatistics), - Scenario::build("Probability", scenaryProbability), - Scenario::build("Equation", scenaryEquation) + Scenario::build("Probability", scenariProbability), + Scenario::build("Equation", scenariEquation) }; constexpr static int numberOfScenari = sizeof(scenari)/sizeof(Scenario); diff --git a/ion/src/shared/events_keyboard.cpp b/ion/src/shared/events_keyboard.cpp index 72d6533ede5..6d281c482a6 100644 --- a/ion/src/shared/events_keyboard.cpp +++ b/ion/src/shared/events_keyboard.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -40,14 +41,14 @@ static bool canRepeatEvent(Event e) { Event getPlatformEvent(); -void ComputeAndSetRepetionFactor(int eventRepetitionCount) { +void ComputeAndSetRepetitionFactor(int eventRepetitionCount) { // The Repetition factor is increased by 4 every 20 loops in getEvent(2 sec) setLongRepetition((eventRepetitionCount / 20) * 4 + 1); } void resetLongRepetition() { sEventRepetitionCount = 0; - ComputeAndSetRepetionFactor(sEventRepetitionCount); + ComputeAndSetRepetitionFactor(sEventRepetitionCount); } static Keyboard::Key keyFromState(Keyboard::State state) { @@ -59,7 +60,7 @@ static inline Event innerGetEvent(int * timeout) { assert(*timeout > delayBetweenRepeat); int time = 0; uint64_t keysSeenUp = 0; - uint64_t keysSeenTransitionningFromUpToDown = 0; + uint64_t keysSeenTransitioningFromUpToDown = 0; while (true) { Event platformEvent = getPlatformEvent(); if (platformEvent != None) { @@ -68,11 +69,11 @@ static inline Event innerGetEvent(int * timeout) { Keyboard::State state = Keyboard::scan(); keysSeenUp |= ~state; - keysSeenTransitionningFromUpToDown = keysSeenUp & state; + keysSeenTransitioningFromUpToDown = keysSeenUp & state; bool lock = isLockActive(); - if (keysSeenTransitionningFromUpToDown != 0) { + if (keysSeenTransitioningFromUpToDown != 0) { sEventIsRepeating = false; resetLongRepetition(); didPressNewKey(); @@ -81,9 +82,19 @@ static inline Event innerGetEvent(int * timeout) { * processors have an instruction (ARM thumb uses CLZ). * Unfortunately there's no way to express this in standard C, so we have * to resort to using a builtin function. */ - Keyboard::Key key = (Keyboard::Key)(63-__builtin_clzll(keysSeenTransitionningFromUpToDown)); + Keyboard::Key key = (Keyboard::Key)(63-__builtin_clzll(keysSeenTransitioningFromUpToDown)); bool shift = isShiftActive() || state.keyDown(Keyboard::Key::Shift); bool alpha = isAlphaActive() || state.keyDown(Keyboard::Key::Alpha); + + // Allow the detected states to be overriden by the simulated states + // This is used for key mapping + if (state.simulatedShift() != Keyboard::ModSimState::None) { + shift = state.simulatedShift() == Keyboard::ModSimState::ForceOn; + } + if (state.simulatedAlpha() != Keyboard::ModSimState::None) { + alpha = state.simulatedAlpha() == Keyboard::ModSimState::ForceOn; + } + bool lock = isLockActive(); if ( key == Keyboard::Key::Left @@ -114,7 +125,7 @@ static inline Event innerGetEvent(int * timeout) { } time += 10; - // At this point, we know that keysSeenTransitionningFromUpToDown has *always* been zero + // At this point, we know that keysSeenTransitioningFromUpToDown has *always* been zero // In other words, no new key has been pressed if (canRepeatEvent(sLastEvent) && state == sLastKeyboardState @@ -125,7 +136,7 @@ static inline Event innerGetEvent(int * timeout) { if (time >= delay) { sEventIsRepeating = true; sEventRepetitionCount++; - ComputeAndSetRepetionFactor(sEventRepetitionCount); + ComputeAndSetRepetitionFactor(sEventRepetitionCount); return sLastEvent; } } diff --git a/ion/src/shared/internal_storage.cpp b/ion/src/shared/internal_storage.cpp new file mode 100644 index 00000000000..5fc4a90d10a --- /dev/null +++ b/ion/src/shared/internal_storage.cpp @@ -0,0 +1,647 @@ +#include +#include +#include +#include +#include +#if ION_STORAGE_LOG +#include +#endif + +namespace Ion { + +/* We want to implement a simple singleton pattern, to make sure the storage is + * initialized on first use, therefore preventing the static init order fiasco. + * That being said, we rely on knowing where the storage resides in the device's + * memory at compile time. Indeed, we want to advertise the static storage's + * memory address in the PlatformInfo structure (so that we can read and write + * it in DFU). + * Using a "static Storage storage;" variable makes it a local symbol at best, + * preventing the PlatformInfo from retrieving its address. And making the + * Storage variable global yields the static init fiasco issue. We're working + * around both issues by creating a global staticStorageArea buffer, and by + * placement-newing the Storage into that area on first use. */ + +/* The InternalStorage is handle all records. Then the "normal" Storage handle + * a trash */ + +constexpr char InternalStorage::expExtension[]; +constexpr char InternalStorage::funcExtension[]; +constexpr char InternalStorage::seqExtension[]; +constexpr char InternalStorage::eqExtension[]; + +// RECORD + +const char * InternalStorage::Record::fullName() const { + return Storage::sharedStorage()->fullNameOfRecord(*this); +} +InternalStorage::Record::ErrorStatus InternalStorage::Record::setBaseNameWithExtension(const char * baseName, const char * extension) { + return Storage::sharedStorage()->setBaseNameWithExtensionOfRecord(*this, baseName, extension); +} +InternalStorage::Record::ErrorStatus InternalStorage::Record::setName(const char * fullName) { + return Storage::sharedStorage()->setFullNameOfRecord(*this, fullName); +} +InternalStorage::Record::Data InternalStorage::Record::value() const { + return Storage::sharedStorage()->valueOfRecord(*this); +} +InternalStorage::Record::ErrorStatus InternalStorage::Record::setValue(Data data) { + return Storage::sharedStorage()->setValueOfRecord(*this, data); +} +void InternalStorage::Record::destroy() { + return Storage::sharedStorage()->destroyRecord(*this); +} + +InternalStorage::Record::Record(const char * fullName) { + if (fullName == nullptr) { + m_fullNameCRC32 = 0; + return; + } + const char * dotChar = UTF8Helper::CodePointSearch(fullName, k_dotChar); + // If no extension, return empty record + if (*dotChar == 0 || *(dotChar+1) == 0) { + m_fullNameCRC32 = 0; + return; + } + new (this) Record(fullName, dotChar - fullName, dotChar+1, (fullName + strlen(fullName)) - (dotChar+1)); +} + +InternalStorage::Record::Record(const char * baseName, const char * extension) { + if (baseName == nullptr) { + assert(extension == nullptr); + m_fullNameCRC32 = 0; + return; + } + new (this) Record(baseName, strlen(baseName), extension, strlen(extension)); +} + + +#if ION_STORAGE_LOG + +void InternalStorage::Record::log() { + std::cout << "Name: " << fullName() << std::endl; + std::cout << " Value (" << value().size << "): " << (char *)value().buffer << "\n\n" << std::endl; +} +#endif + +uint32_t InternalStorage::Record::checksum() { + uint32_t crc32Results[2]; + crc32Results[0] = m_fullNameCRC32; + Data data = value(); + crc32Results[1] = Ion::crc32Byte((const uint8_t *)data.buffer, data.size); + return Ion::crc32Word(crc32Results, 2); +} + +InternalStorage::Record::Record(const char * basename, int basenameLength, const char * extension, int extensionLength) { + assert(basename != nullptr); + assert(extension != nullptr); + + // We compute the CRC32 of the CRC32s of the basename and the extension + uint32_t crc32Results[2]; + crc32Results[0] = Ion::crc32Byte((const uint8_t *)basename, basenameLength); + crc32Results[1] = Ion::crc32Byte((const uint8_t *)extension, extensionLength); + m_fullNameCRC32 = Ion::crc32Word(crc32Results, 2); +} + +// STORAGE + +#if ION_STORAGE_LOG +void InternalStorage::log() { + for (char * p : *this) { + const char * currentName = fullNameOfRecordStarting(p); + Record(currentName).log(); + } +} +#endif + +size_t InternalStorage::availableSize() { + /* TODO maybe do: availableSize(char ** endBuffer) to get the endBuffer if it + * is needed after calling availableSize */ + assert(k_storageSize >= (endBuffer() - m_buffer) + sizeof(record_size_t)); + return k_storageSize-(endBuffer()-m_buffer)-sizeof(record_size_t); +} + +size_t InternalStorage::putAvailableSpaceAtEndOfRecord(InternalStorage::Record r) { + char * p = pointerOfRecord(r); + size_t previousRecordSize = sizeOfRecordStarting(p); + size_t availableStorageSize = availableSize(); + char * nextRecord = p + previousRecordSize; + memmove(nextRecord + availableStorageSize, + nextRecord, + (m_buffer + k_storageSize - availableStorageSize) - nextRecord); + size_t newRecordSize = previousRecordSize + availableStorageSize; + overrideSizeAtPosition(p, (record_size_t)newRecordSize); + return newRecordSize; +} + +void InternalStorage::getAvailableSpaceFromEndOfRecord(Record r, size_t recordAvailableSpace) { + char * p = pointerOfRecord(r); + size_t previousRecordSize = sizeOfRecordStarting(p); + char * nextRecord = p + previousRecordSize; + memmove(nextRecord - recordAvailableSpace, + nextRecord, + m_buffer + k_storageSize - nextRecord); + overrideSizeAtPosition(p, (record_size_t)(previousRecordSize - recordAvailableSpace)); +} + +uint32_t InternalStorage::checksum() { + return Ion::crc32Byte((const uint8_t *) m_buffer, endBuffer()-m_buffer); +} + +void InternalStorage::notifyChangeToDelegate(const Record record) const { + m_lastRecordRetrieved = Record(nullptr); + m_lastRecordRetrievedPointer = nullptr; + if (m_delegate != nullptr) { + m_delegate->storageDidChangeForRecord(record); + } +} + +InternalStorage::Record::ErrorStatus InternalStorage::notifyFullnessToDelegate() const { + if (m_delegate != nullptr) { + m_delegate->storageIsFull(); + } + return Record::ErrorStatus::NotEnoughSpaceAvailable; +} + +InternalStorage::Record::ErrorStatus InternalStorage::createRecordWithFullName(const char * fullName, const void * data, size_t size) { + size_t recordSize = sizeOfRecordWithFullName(fullName, size); + if (recordSize >= k_maxRecordSize || recordSize > availableSize()) { + return notifyFullnessToDelegate(); + } + if (isFullNameTaken(fullName)) { + return Record::ErrorStatus::NameTaken; + } + // Find the end of data + char * newRecordAddress = endBuffer(); + char * newRecord = newRecordAddress; + // Fill totalSize + newRecord += overrideSizeAtPosition(newRecord, (record_size_t)recordSize); + // Fill name + newRecord += overrideFullNameAtPosition(newRecord, fullName); + // Fill data + newRecord += overrideValueAtPosition(newRecord, data, size); + // Next Record is null-sized + overrideSizeAtPosition(newRecord, 0); + Record r = Record(fullName); + notifyChangeToDelegate(r); + m_lastRecordRetrieved = r; + m_lastRecordRetrievedPointer = newRecordAddress; + return Record::ErrorStatus::None; +} + +InternalStorage::Record::ErrorStatus InternalStorage::createRecordWithExtension(const char * baseName, const char * extension, const void * data, size_t size) { + size_t recordSize = sizeOfRecordWithBaseNameAndExtension(baseName, extension, size); + if (recordSize >= k_maxRecordSize || recordSize > availableSize()) { + return notifyFullnessToDelegate(); + } + if (isBaseNameWithExtensionTaken(baseName, extension)) { + return Record::ErrorStatus::NameTaken; + } + // Find the end of data + char * newRecordAddress = endBuffer(); + char * newRecord = newRecordAddress; + // Fill totalSize + newRecord += overrideSizeAtPosition(newRecord, (record_size_t)recordSize); + // Fill name + newRecord += overrideBaseNameWithExtensionAtPosition(newRecord, baseName, extension); + // Fill data + newRecord += overrideValueAtPosition(newRecord, data, size); + // Next Record is null-sized + overrideSizeAtPosition(newRecord, 0); + Record r = Record(fullNameOfRecordStarting(newRecordAddress)); + notifyChangeToDelegate(r); + m_lastRecordRetrieved = r; + m_lastRecordRetrievedPointer = newRecordAddress; + return Record::ErrorStatus::None; +} + +int InternalStorage::numberOfRecordsWithExtension(const char * extension) { + int count = 0; + size_t extensionLength = strlen(extension); + for (char * p : *this) { + const char * name = fullNameOfRecordStarting(p); + if (FullNameHasExtension(name, extension, extensionLength)) { + count++; + } + } + return count; +} + +int InternalStorage::numberOfRecords() { + int count = 0; + for (char * p : *this) { + const char * name = fullNameOfRecordStarting(p); + count++; + } + return count; +} + +InternalStorage::Record InternalStorage::recordAtIndex(int index) { + int currentIndex = -1; + const char * name = nullptr; + char * recordAddress = nullptr; + for (char * p : *this) { + const char * currentName = fullNameOfRecordStarting(p); + currentIndex++; + if (currentIndex == index) { + recordAddress = p; + name = currentName; + break; + } + } + if (name == nullptr) { + return Record(); + } + Record r = Record(name); + m_lastRecordRetrieved = r; + m_lastRecordRetrievedPointer = recordAddress; + return Record(name); +} + +InternalStorage::Record InternalStorage::recordWithExtensionAtIndex(const char * extension, int index) { + int currentIndex = -1; + const char * name = nullptr; + size_t extensionLength = strlen(extension); + char * recordAddress = nullptr; + for (char * p : *this) { + const char * currentName = fullNameOfRecordStarting(p); + if (FullNameHasExtension(currentName, extension, extensionLength)) { + currentIndex++; + } + if (currentIndex == index) { + recordAddress = p; + name = currentName; + break; + } + } + if (name == nullptr) { + return Record(); + } + Record r = Record(name); + m_lastRecordRetrieved = r; + m_lastRecordRetrievedPointer = recordAddress; + return Record(name); +} + +InternalStorage::Record InternalStorage::recordNamed(const char * fullName) { + if (fullName == nullptr) { + return Record(); + } + Record r = Record(fullName); + char * p = pointerOfRecord(r); + if (p != nullptr) { + return r; + } + return Record(); +} + +InternalStorage::Record InternalStorage::recordBaseNamedWithExtension(const char * baseName, const char * extension) { + const char * extensions[1] = {extension}; + return recordBaseNamedWithExtensions(baseName, extensions, 1); +} + +InternalStorage::Record InternalStorage::recordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions) { + return privateRecordAndExtensionOfRecordBaseNamedWithExtensions(baseName, extensions, numberOfExtensions); +} + +const char * InternalStorage::extensionOfRecordBaseNamedWithExtensions(const char * baseName, int baseNameLength, const char * const extensions[], size_t numberOfExtensions) { + const char * result = nullptr; + privateRecordAndExtensionOfRecordBaseNamedWithExtensions(baseName, extensions, numberOfExtensions, &result, baseNameLength); + return result; +} + +void InternalStorage::destroyAllRecords() { + overrideSizeAtPosition(m_buffer, 0); + notifyChangeToDelegate(); +} + +void InternalStorage::destroyRecordWithBaseNameAndExtension(const char * baseName, const char * extension) { + recordBaseNamedWithExtension(baseName, extension).destroy(); +} + +void InternalStorage::destroyRecordsWithExtension(const char * extension) { + size_t extensionLength = strlen(extension); + char * currentRecordStart = (char *)m_buffer; + bool didChange = false; + while (currentRecordStart != nullptr && sizeOfRecordStarting(currentRecordStart) != 0) { + const char * currentFullName = fullNameOfRecordStarting(currentRecordStart); + if (FullNameHasExtension(currentFullName, extension, extensionLength)) { + Record currentRecord(currentFullName); + currentRecord.destroy(); + didChange = true; + continue; + } + currentRecordStart = *(RecordIterator(currentRecordStart).operator++()); + } + if (didChange) { + notifyChangeToDelegate(); + } +} + +InternalStorage::InternalStorage() : + m_magicHeader(Magic), + m_buffer(), + m_magicFooter(Magic), + m_delegate(nullptr), + m_lastRecordRetrieved(nullptr), + m_lastRecordRetrievedPointer(nullptr) +{ + assert(m_magicHeader == Magic); + assert(m_magicFooter == Magic); + // Set the size of the first record to 0 + overrideSizeAtPosition(m_buffer, 0); +} + +// PRIVATE + +const char * InternalStorage::fullNameOfRecord(const Record record) { + char * p = pointerOfRecord(record); + if (p != nullptr) { + return fullNameOfRecordStarting(p); + } + return nullptr; +} + +InternalStorage::Record::ErrorStatus InternalStorage::setFullNameOfRecord(const Record record, const char * fullName) { + if (!FullNameCompliant(fullName)) { + return Record::ErrorStatus::NonCompliantName; + } + if (isFullNameTaken(fullName, &record)) { + return Record::ErrorStatus::NameTaken; + } + size_t nameSize = strlen(fullName) + 1; + char * p = pointerOfRecord(record); + if (p != nullptr) { + size_t previousNameSize = strlen(fullNameOfRecordStarting(p))+1; + record_size_t previousRecordSize = sizeOfRecordStarting(p); + size_t newRecordSize = previousRecordSize-previousNameSize+nameSize; + if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+sizeof(record_size_t)+previousNameSize, nameSize-previousNameSize)) { + return notifyFullnessToDelegate(); + } + overrideSizeAtPosition(p, newRecordSize); + overrideFullNameAtPosition(p+sizeof(record_size_t), fullName); + notifyChangeToDelegate(record); + m_lastRecordRetrieved = record; + m_lastRecordRetrievedPointer = p; + return Record::ErrorStatus::None; + } + return Record::ErrorStatus::RecordDoesNotExist; +} + +InternalStorage::Record::ErrorStatus InternalStorage::setBaseNameWithExtensionOfRecord(Record record, const char * baseName, const char * extension) { + if (isBaseNameWithExtensionTaken(baseName, extension, &record)) { + return Record::ErrorStatus::NameTaken; + } + size_t nameSize = sizeOfBaseNameAndExtension(baseName, extension); + char * p = pointerOfRecord(record); + if (p != nullptr) { + size_t previousNameSize = strlen(fullNameOfRecordStarting(p))+1; + record_size_t previousRecordSize = sizeOfRecordStarting(p); + size_t newRecordSize = previousRecordSize-previousNameSize+nameSize; + if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+sizeof(record_size_t)+previousNameSize, nameSize-previousNameSize)) { + return notifyFullnessToDelegate(); + } + overrideSizeAtPosition(p, newRecordSize); + char * fullNamePosition = p + sizeof(record_size_t); + overrideBaseNameWithExtensionAtPosition(fullNamePosition, baseName, extension); + // Recompute the CRC32 + record = Record(fullNamePosition); + notifyChangeToDelegate(record); + m_lastRecordRetrieved = record; + m_lastRecordRetrievedPointer = p; + return Record::ErrorStatus::None; + } + return Record::ErrorStatus::RecordDoesNotExist; +} + +InternalStorage::Record::Data InternalStorage::valueOfRecord(const Record record) { + char * p = pointerOfRecord(record); + if (p != nullptr) { + const char * fullName = fullNameOfRecordStarting(p); + record_size_t size = sizeOfRecordStarting(p); + const void * value = valueOfRecordStarting(p); + return {.buffer = value, .size = size-strlen(fullName)-1-sizeof(record_size_t)}; + } + return {.buffer= nullptr, .size= 0}; +} + +InternalStorage::Record::ErrorStatus InternalStorage::setValueOfRecord(Record record, Record::Data data) { + char * p = pointerOfRecord(record); + /* TODO: if data.buffer == p, assert that size hasn't change and do not do any + * memcopy, but still notify the delegate. Beware of scripts and the accordion + * routine.*/ + if (p != nullptr) { + record_size_t previousRecordSize = sizeOfRecordStarting(p); + const char * fullName = fullNameOfRecordStarting(p); + size_t newRecordSize = sizeOfRecordWithFullName(fullName, data.size); + if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+previousRecordSize, newRecordSize-previousRecordSize)) { + return notifyFullnessToDelegate(); + } + record_size_t fullNameSize = strlen(fullName)+1; + overrideSizeAtPosition(p, newRecordSize); + overrideValueAtPosition(p+sizeof(record_size_t)+fullNameSize, data.buffer, data.size); + notifyChangeToDelegate(record); + m_lastRecordRetrieved = record; + m_lastRecordRetrievedPointer = p; + return Record::ErrorStatus::None; + } + return Record::ErrorStatus::RecordDoesNotExist; +} + +void InternalStorage::destroyRecord(Record record) { + if (record.isNull()) { + return; + } + char * p = pointerOfRecord(record); + if (p != nullptr) { + record_size_t previousRecordSize = sizeOfRecordStarting(p); + slideBuffer(p+previousRecordSize, -previousRecordSize); + notifyChangeToDelegate(); + } +} + +char * InternalStorage::pointerOfRecord(const Record record) const { + if (record.isNull()) { + return nullptr; + } + if (!m_lastRecordRetrieved.isNull() && record == m_lastRecordRetrieved) { + assert(m_lastRecordRetrievedPointer != nullptr); + return m_lastRecordRetrievedPointer; + } + for (char * p : *this) { + Record currentRecord(fullNameOfRecordStarting(p)); + if (record == currentRecord) { + m_lastRecordRetrieved = record; + m_lastRecordRetrievedPointer = p; + return p; + } + } + return nullptr; +} + +InternalStorage::record_size_t InternalStorage::sizeOfRecordStarting(char * start) const { + return StorageHelper::unalignedShort(start); +} + +const char * InternalStorage::fullNameOfRecordStarting(char * start) const { + return start+sizeof(record_size_t); +} + +const void * InternalStorage::valueOfRecordStarting(char * start) const { + char * currentChar = start+sizeof(record_size_t); + size_t fullNameLength = strlen(currentChar); + return currentChar+fullNameLength+1; +} + +size_t InternalStorage::overrideSizeAtPosition(char * position, record_size_t size) { + StorageHelper::writeUnalignedShort(size, position); + return sizeof(record_size_t); +} + +size_t InternalStorage::overrideFullNameAtPosition(char * position, const char * fullName) { + return strlcpy(position, fullName, strlen(fullName)+1) + 1; +} + +size_t InternalStorage::overrideBaseNameWithExtensionAtPosition(char * position, const char * baseName, const char * extension) { + size_t result = strlcpy(position, baseName, strlen(baseName)+1); // strlcpy copies the null terminating char + assert(UTF8Decoder::CharSizeOfCodePoint(k_dotChar) == 1); + *(position+result) = k_dotChar; // Replace the null terminating char with a dot + result++; + result += strlcpy(position+result, extension, strlen(extension)+1); + return result+1; +} + +size_t InternalStorage::overrideValueAtPosition(char * position, const void * data, record_size_t size) { + memcpy(position, data, size); + return size; +} + +bool InternalStorage::isFullNameTaken(const char * fullName, const Record * recordToExclude) { + Record r = Record(fullName); + return isNameOfRecordTaken(r, recordToExclude); +} + +bool InternalStorage::isBaseNameWithExtensionTaken(const char * baseName, const char * extension, Record * recordToExclude) { + Record r = Record(baseName, extension); + return isNameOfRecordTaken(r, recordToExclude); +} + +bool InternalStorage::isNameOfRecordTaken(Record r, const Record * recordToExclude) { + if (r == Record()) { + /* If the CRC32 of fullName is 0, we want to refuse the name as it would + * interfere with our escape case in the Record constructor, when the given + * name is nullptr. */ + return true; + } + for (char * p : *this) { + Record s(fullNameOfRecordStarting(p)); + if (recordToExclude && s == *recordToExclude) { + continue; + } + if (s == r) { + return true; + } + } + return false; +} + +bool InternalStorage::FullNameCompliant(const char * fullName) { + // We check that there is one dot and one dot only. + const char * dotChar = UTF8Helper::CodePointSearch(fullName, k_dotChar); + if (*dotChar == 0) { + return false; + } + if (*(UTF8Helper::CodePointSearch(dotChar+1, k_dotChar)) == 0) { + return true; + } + return false; +} + +bool InternalStorage::FullNameHasExtension(const char * fullName, const char * extension, size_t extensionLength) { + if (fullName == nullptr) { + return false; + } + size_t fullNameLength = strlen(fullName); + if (fullNameLength > extensionLength) { + const char * ext = fullName + fullNameLength - extensionLength; + if (UTF8Helper::PreviousCodePointIs(fullName, ext, k_dotChar) && strcmp(ext, extension) == 0) { + return true; + } + } + return false; +} + +char * InternalStorage::endBuffer() { + char * currentBuffer = m_buffer; + for (char * p : *this) { + currentBuffer += sizeOfRecordStarting(p); + } + return currentBuffer; +} + +size_t InternalStorage::sizeOfBaseNameAndExtension(const char * baseName, const char * extension) const { + // +1 for the dot and +1 for the null terminating char + return strlen(baseName)+1+strlen(extension)+1; +} + +size_t InternalStorage::sizeOfRecordWithBaseNameAndExtension(const char * baseName, const char * extension, size_t dataSize) const { + return sizeOfBaseNameAndExtension(baseName, extension) + dataSize + sizeof(record_size_t); +} + +size_t InternalStorage::sizeOfRecordWithFullName(const char * fullName, size_t dataSize) const { + size_t nameSize = strlen(fullName)+1; + return nameSize+dataSize+sizeof(record_size_t); +} + +bool InternalStorage::slideBuffer(char * position, int delta) { + if (delta > (int)availableSize()) { + return false; + } + memmove(position+delta, position, endBuffer()+sizeof(record_size_t)-position); + return true; +} + +InternalStorage::Record InternalStorage::privateRecordAndExtensionOfRecordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions, const char * * extensionResult, int baseNameLength) { + size_t nameLength = baseNameLength < 0 ? strlen(baseName) : baseNameLength; + { + const char * lastRetrievedRecordFullName = fullNameOfRecordStarting(m_lastRecordRetrievedPointer); + if (m_lastRecordRetrievedPointer != nullptr && strncmp(baseName, lastRetrievedRecordFullName, nameLength) == 0) { + for (size_t i = 0; i < numberOfExtensions; i++) { + if (strcmp(lastRetrievedRecordFullName+nameLength+1 /*+1 to pass the dot*/, extensions[i]) == 0) { + assert(UTF8Helper::CodePointIs(lastRetrievedRecordFullName + nameLength, '.')); + if (extensionResult != nullptr) { + *extensionResult = extensions[i]; + } + return m_lastRecordRetrieved; + } + } + } + } + for (char * p : *this) { + const char * currentName = fullNameOfRecordStarting(p); + if (strncmp(baseName, currentName, nameLength) == 0) { + for (size_t i = 0; i < numberOfExtensions; i++) { + if (strcmp(currentName+nameLength+1 /*+1 to pass the dot*/, extensions[i]) == 0) { + assert(UTF8Helper::CodePointIs(currentName + nameLength, '.')); + if (extensionResult != nullptr) { + *extensionResult = extensions[i]; + } + return Record(currentName); + } + } + } + } + if (extensionResult != nullptr) { + *extensionResult = nullptr; + } + return Record(); +} + +InternalStorage::RecordIterator & InternalStorage::RecordIterator::operator++() { + assert(m_recordStart); + record_size_t size = StorageHelper::unalignedShort(m_recordStart); + char * nextRecord = m_recordStart+size; + record_size_t newRecordSize = StorageHelper::unalignedShort(nextRecord); + m_recordStart = (newRecordSize == 0 ? nullptr : nextRecord); + return *this; +} + +} diff --git a/ion/src/shared/keyboard/layout_B2/layout_events.cpp b/ion/src/shared/keyboard/layout_B2/layout_events.cpp index 33909259764..035d4509054 100644 --- a/ion/src/shared/keyboard/layout_B2/layout_events.cpp +++ b/ion/src/shared/keyboard/layout_B2/layout_events.cpp @@ -12,11 +12,11 @@ const EventData s_dataForEvent[4 * Event::PageSize] = { T("ℯ^(\x11)"), T("ln(\x11)"), T("log(\x11)"), T("𝐢"), T(","), T("^"), T("sin(\x11)"), T("cos(\x11)"), T("tan(\x11)"), T("π"), T("√(\x11)"), T("^2"), T("7"), T("8"), T("9"), T("("), T(")"), U(), - T("4"), T("5"), T("6"), T("×"), T("/"), U(), + T("4"), T("5"), T("6"), T("*"), T("/"), U(), T("1"), T("2"), T("3"), T("+"), T("-"), U(), T("0"), T("."), T("ᴇ"), TL(), TL(), U(), // Shift - TL(), TL(), TL(), TL(), U(), U(), + TL(), TL(), TL(), TL(), TL(), TL(), TL(), U(), U(), U(), U(), U(), U(), U(), TL(), TL(), TL(), TL(), T("["), T("]"), T("{"), T("}"), T("_"), T("→"), @@ -24,7 +24,7 @@ const EventData s_dataForEvent[4 * Event::PageSize] = { T("μ"), TL(), T("Ω"), T("(\x11)"), U(), U(), TL(), TL(), TL(), TL(), TL(), U(), TL(), TL(), TL(), TL(), TL(), U(), - TL(), TL(), TL(), TL(), U(), U(), + TL(), TL(), TL(), TL(), TL(), U(), // Alpha TL(), TL(), TL(), TL(), U(), U(), U(), U(), U(), U(), U(), U(), @@ -61,7 +61,7 @@ const char * const s_nameForEvent[255] = { "One", "Two", "Three", "Plus", "Minus", nullptr, "Zero", "Dot", "EE", "Ans", "EXE", nullptr, //Shift, - "ShiftLeft", "ShiftUp", "ShiftDown", "ShiftRight", nullptr, nullptr, + "ShiftLeft", "ShiftUp", "ShiftDown", "ShiftRight", "ShiftOK", "ShiftBack", "ShiftHome", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "AlphaLock", "Cut", "Copy", "Paste", "Clear", "LeftBracket", "RightBracket", "LeftBrace", "RightBrace", "Underscore", "Sto", @@ -69,7 +69,7 @@ const char * const s_nameForEvent[255] = { "Micro", nullptr, "Omega", nullptr, "DoubleParenthesis", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "BrightnessPlus", "BrightnessMinus", nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, "ShiftEXE", nullptr, //Alpha, "AlphaLeft", "AlphaUp", "AlphaDown", "AlphaRight", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, diff --git a/ion/src/shared/keyboard/layout_B3/layout_events.cpp b/ion/src/shared/keyboard/layout_B3/layout_events.cpp index 2cd94ce5353..2ea81e4a79b 100644 --- a/ion/src/shared/keyboard/layout_B3/layout_events.cpp +++ b/ion/src/shared/keyboard/layout_B3/layout_events.cpp @@ -16,7 +16,7 @@ const EventData s_dataForEvent[4 * Event::PageSize] = { T("1"), T("2"), T("3"), T("+"), T("-"), U(), T("0"), T("."), T("ᴇ"), TL(), TL(), U(), // Shift - TL(), TL(), TL(), TL(), U(), U(), + TL(), TL(), TL(), TL(), TL(), TL(), TL(), U(), U(), U(), U(), U(), U(), U(), TL(), TL(), TL(), TL(), T("["), T("]"), T("{"), T("}"), T("_"), T("→"), @@ -24,7 +24,7 @@ const EventData s_dataForEvent[4 * Event::PageSize] = { T("μ"), TL(), T("Ω"), T("(\x11)"), U(), U(), TL(), TL(), TL(), TL(), TL(), U(), TL(), TL(), TL(), TL(), TL(), U(), - TL(), TL(), TL(), TL(), U(), U(), + TL(), TL(), TL(), TL(), TL(), U(), // Alpha TL(), TL(), TL(), TL(), U(), U(), U(), U(), U(), U(), U(), U(), @@ -61,7 +61,7 @@ const char * const s_nameForEvent[255] = { "One", "Two", "Three", "Plus", "Minus", nullptr, "Zero", "Dot", "EE", "Ans", "EXE", nullptr, //Shift, - "ShiftLeft", "ShiftUp", "ShiftDown", "ShiftRight", nullptr, nullptr, + "ShiftLeft", "ShiftUp", "ShiftDown", "ShiftRight", "ShiftOK", "ShiftBack", "ShiftHome", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "AlphaLock", "Cut", "Copy", "Paste", "Clear", "LeftBracket", "RightBracket", "LeftBrace", "RightBrace", "Underscore", "Sto", @@ -69,7 +69,7 @@ const char * const s_nameForEvent[255] = { "Micro", nullptr, "Omega", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "BrightnessPlus", "BrightnessMinus", nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, "ShiftEXE", nullptr, //Alpha, "AlphaLeft", "AlphaUp", "AlphaDown", "AlphaRight", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, diff --git a/ion/src/shared/storage.cpp b/ion/src/shared/storage.cpp index 3419e3d8c53..50a8a4c7007 100644 --- a/ion/src/shared/storage.cpp +++ b/ion/src/shared/storage.cpp @@ -1,243 +1,67 @@ -#include -#include +#include #include #include -#if ION_STORAGE_LOG -#include -#endif +#include +#include namespace Ion { -/* We want to implement a simple singleton pattern, to make sure the storage is - * initialized on first use, therefore preventing the static init order fiasco. - * That being said, we rely on knowing where the storage resides in the device's - * memory at compile time. Indeed, we want to advertise the static storage's - * memory address in the PlatformInfo structure (so that we can read and write - * it in DFU). - * Using a "static Storage storage;" variable makes it a local symbol at best, - * preventing the PlatformInfo from retrieving its address. And making the - * Storage variable global yields the static init fiasco issue. We're working - * around both issues by creating a global staticStorageArea buffer, and by - * placement-newing the Storage into that area on first use. */ - uint32_t staticStorageArea[sizeof(Storage)/sizeof(uint32_t)] = {0}; -constexpr char Storage::expExtension[]; -constexpr char Storage::funcExtension[]; -constexpr char Storage::seqExtension[]; -constexpr char Storage::eqExtension[]; - Storage * Storage::sharedStorage() { static Storage * storage = new (staticStorageArea) Storage(); return storage; } -// RECORD - -Storage::Record::Record(const char * fullName) { - if (fullName == nullptr) { - m_fullNameCRC32 = 0; - return; - } - const char * dotChar = UTF8Helper::CodePointSearch(fullName, k_dotChar); - // If no extension, return empty record - if (*dotChar == 0 || *(dotChar+1) == 0) { - m_fullNameCRC32 = 0; - return; - } - new (this) Record(fullName, dotChar - fullName, dotChar+1, (fullName + strlen(fullName)) - (dotChar+1)); -} - -Storage::Record::Record(const char * baseName, const char * extension) { - if (baseName == nullptr) { - assert(extension == nullptr); - m_fullNameCRC32 = 0; - return; - } - new (this) Record(baseName, strlen(baseName), extension, strlen(extension)); -} - - -#if ION_STORAGE_LOG - -void Storage::Record::log() { - std::cout << "Name: " << fullName() << std::endl; - std::cout << " Value (" << value().size << "): " << (char *)value().buffer << "\n\n" << std::endl; -} -#endif - -uint32_t Storage::Record::checksum() { - uint32_t crc32Results[2]; - crc32Results[0] = m_fullNameCRC32; - Data data = value(); - crc32Results[1] = Ion::crc32Byte((const uint8_t *)data.buffer, data.size); - return Ion::crc32Word(crc32Results, 2); -} - -Storage::Record::Record(const char * basename, int basenameLength, const char * extension, int extensionLength) { - assert(basename != nullptr); - assert(extension != nullptr); - - // We compute the CRC32 of the CRC32s of the basename and the extension - uint32_t crc32Results[2]; - crc32Results[0] = Ion::crc32Byte((const uint8_t *)basename, basenameLength); - crc32Results[1] = Ion::crc32Byte((const uint8_t *)extension, extensionLength); - m_fullNameCRC32 = Ion::crc32Word(crc32Results, 2); -} - -// STORAGE - -#if ION_STORAGE_LOG -void Storage::log() { - for (char * p : *this) { - const char * currentName = fullNameOfRecordStarting(p); - Record(currentName).log(); - } -} -#endif - size_t Storage::availableSize() { - /* TODO maybe do: availableSize(char ** endBuffer) to get the endBuffer if it - * is needed after calling availableSize */ - assert(k_storageSize >= (endBuffer() - m_buffer) + sizeof(record_size_t)); - return k_storageSize-(endBuffer()-m_buffer)-sizeof(record_size_t); + if (m_trashRecord != Record()) { + int bufferSize = 0; + for (char * p : *this) { + if (Record(fullNameOfRecordStarting(p)) != m_trashRecord) { + bufferSize += sizeOfRecordStarting(p); + } + } + return k_storageSize-bufferSize-sizeof(record_size_t); + } else { + return InternalStorage::availableSize(); + } } -size_t Storage::putAvailableSpaceAtEndOfRecord(Storage::Record r) { - char * p = pointerOfRecord(r); - size_t previousRecordSize = sizeOfRecordStarting(p); - size_t availableStorageSize = availableSize(); - char * nextRecord = p + previousRecordSize; - memmove(nextRecord + availableStorageSize, - nextRecord, - (m_buffer + k_storageSize - availableStorageSize) - nextRecord); - size_t newRecordSize = previousRecordSize + availableStorageSize; - overrideSizeAtPosition(p, (record_size_t)newRecordSize); - return newRecordSize; +size_t Storage::putAvailableSpaceAtEndOfRecord(Record r) { + emptyTrash(); + return InternalStorage::putAvailableSpaceAtEndOfRecord(r); } void Storage::getAvailableSpaceFromEndOfRecord(Record r, size_t recordAvailableSpace) { - char * p = pointerOfRecord(r); - size_t previousRecordSize = sizeOfRecordStarting(p); - char * nextRecord = p + previousRecordSize; - memmove(nextRecord - recordAvailableSpace, - nextRecord, - m_buffer + k_storageSize - nextRecord); - overrideSizeAtPosition(p, (record_size_t)(previousRecordSize - recordAvailableSpace)); -} - -uint32_t Storage::checksum() { - return Ion::crc32Byte((const uint8_t *) m_buffer, endBuffer()-m_buffer); + emptyTrash(); + InternalStorage::getAvailableSpaceFromEndOfRecord(r, recordAvailableSpace); } -void Storage::notifyChangeToDelegate(const Record record) const { - m_lastRecordRetrieved = Record(nullptr); - m_lastRecordRetrievedPointer = nullptr; - if (m_delegate != nullptr) { - m_delegate->storageDidChangeForRecord(record); - } -} - -Storage::Record::ErrorStatus Storage::notifyFullnessToDelegate() const { - if (m_delegate != nullptr) { - m_delegate->storageIsFull(); +int Storage::numberOfRecordsWithExtension(const char * extension) { + int trashRecord = 0; + if (FullNameHasExtension(m_trashRecord.fullName(), extension, strlen(extension))) { + trashRecord = 1; } - return Record::ErrorStatus::NotEnoughSpaceAvailable; + return InternalStorage::numberOfRecordsWithExtension(extension) - trashRecord; } Storage::Record::ErrorStatus Storage::createRecordWithFullName(const char * fullName, const void * data, size_t size) { - size_t recordSize = sizeOfRecordWithFullName(fullName, size); - if (recordSize >= k_maxRecordSize || recordSize > availableSize()) { - return notifyFullnessToDelegate(); - } - if (isFullNameTaken(fullName)) { - return Record::ErrorStatus::NameTaken; - } - // Find the end of data - char * newRecordAddress = endBuffer(); - char * newRecord = newRecordAddress; - // Fill totalSize - newRecord += overrideSizeAtPosition(newRecord, (record_size_t)recordSize); - // Fill name - newRecord += overrideFullNameAtPosition(newRecord, fullName); - // Fill data - newRecord += overrideValueAtPosition(newRecord, data, size); - // Next Record is null-sized - overrideSizeAtPosition(newRecord, 0); - Record r = Record(fullName); - notifyChangeToDelegate(r); - m_lastRecordRetrieved = r; - m_lastRecordRetrievedPointer = newRecordAddress; - return Record::ErrorStatus::None; + emptyTrash(); + return InternalStorage::createRecordWithFullName(fullName, data, size); } Storage::Record::ErrorStatus Storage::createRecordWithExtension(const char * baseName, const char * extension, const void * data, size_t size) { - size_t recordSize = sizeOfRecordWithBaseNameAndExtension(baseName, extension, size); - if (recordSize >= k_maxRecordSize || recordSize > availableSize()) { - return notifyFullnessToDelegate(); - } - if (isBaseNameWithExtensionTaken(baseName, extension)) { - return Record::ErrorStatus::NameTaken; - } - // Find the end of data - char * newRecordAddress = endBuffer(); - char * newRecord = newRecordAddress; - // Fill totalSize - newRecord += overrideSizeAtPosition(newRecord, (record_size_t)recordSize); - // Fill name - newRecord += overrideBaseNameWithExtensionAtPosition(newRecord, baseName, extension); - // Fill data - newRecord += overrideValueAtPosition(newRecord, data, size); - // Next Record is null-sized - overrideSizeAtPosition(newRecord, 0); - Record r = Record(fullNameOfRecordStarting(newRecordAddress)); - notifyChangeToDelegate(r); - m_lastRecordRetrieved = r; - m_lastRecordRetrievedPointer = newRecordAddress; - return Record::ErrorStatus::None; + emptyTrash(); + return InternalStorage::createRecordWithExtension(baseName, extension, data, size); } -int Storage::numberOfRecordsWithExtension(const char * extension) { - int count = 0; - size_t extensionLength = strlen(extension); - for (char * p : *this) { - const char * name = fullNameOfRecordStarting(p); - if (FullNameHasExtension(name, extension, extensionLength)) { - count++; - } - } - return count; +bool Storage::hasRecord(Record r) { + return InternalStorage::hasRecord(r) && r != m_trashRecord; } -int Storage::numberOfRecords() { - int count = 0; - for (char * p : *this) { - const char * name = fullNameOfRecordStarting(p); - count++; - } - return count; -} - -Storage::Record Storage::recordAtIndex(int index) { - int currentIndex = -1; - const char * name = nullptr; - char * recordAddress = nullptr; - for (char * p : *this) { - const char * currentName = fullNameOfRecordStarting(p); - currentIndex++; - if (currentIndex == index) { - recordAddress = p; - name = currentName; - break; - } - } - if (name == nullptr) { - return Record(); - } - Record r = Record(name); - m_lastRecordRetrieved = r; - m_lastRecordRetrievedPointer = recordAddress; - return Record(name); +void Storage::destroyRecord(Record record) { + emptyTrash(); + m_trashRecord = record; } Storage::Record Storage::recordWithExtensionAtIndex(const char * extension, int index) { @@ -247,385 +71,110 @@ Storage::Record Storage::recordWithExtensionAtIndex(const char * extension, int char * recordAddress = nullptr; for (char * p : *this) { const char * currentName = fullNameOfRecordStarting(p); - if (FullNameHasExtension(currentName, extension, extensionLength)) { + if (FullNameHasExtension(currentName, extension, extensionLength) && Record(currentName) != m_trashRecord) { currentIndex++; - } - if (currentIndex == index) { - recordAddress = p; - name = currentName; - break; + if (currentIndex == index) { + recordAddress = p; + name = currentName; + break; + } } } if (name == nullptr) { return Record(); } - Record r = Record(name); + Storage::Record r = Record(name); m_lastRecordRetrieved = r; m_lastRecordRetrievedPointer = recordAddress; return Record(name); } Storage::Record Storage::recordNamed(const char * fullName) { - if (fullName == nullptr) { - return Record(); - } - Record r = Record(fullName); - char * p = pointerOfRecord(r); - if (p != nullptr) { - return r; - } - return Record(); + Storage::Record r = InternalStorage::recordNamed(fullName); + return r == m_trashRecord ? Record() : r; } Storage::Record Storage::recordBaseNamedWithExtension(const char * baseName, const char * extension) { - const char * extensions[1] = {extension}; - return recordBaseNamedWithExtensions(baseName, extensions, 1); -} - -Storage::Record Storage::recordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions) { - return privateRecordAndExtensionOfRecordBaseNamedWithExtensions(baseName, extensions, numberOfExtensions); -} - -const char * Storage::extensionOfRecordBaseNamedWithExtensions(const char * baseName, int baseNameLength, const char * const extensions[], size_t numberOfExtensions) { - const char * result = nullptr; - privateRecordAndExtensionOfRecordBaseNamedWithExtensions(baseName, extensions, numberOfExtensions, &result, baseNameLength); - return result; -} - -void Storage::destroyAllRecords() { - overrideSizeAtPosition(m_buffer, 0); - notifyChangeToDelegate(); -} - -void Storage::destroyRecordWithBaseNameAndExtension(const char * baseName, const char * extension) { - recordBaseNamedWithExtension(baseName, extension).destroy(); -} - -void Storage::destroyRecordsWithExtension(const char * extension) { - size_t extensionLength = strlen(extension); - char * currentRecordStart = (char *)m_buffer; - bool didChange = false; - while (currentRecordStart != nullptr && sizeOfRecordStarting(currentRecordStart) != 0) { - const char * currentFullName = fullNameOfRecordStarting(currentRecordStart); - if (FullNameHasExtension(currentFullName, extension, extensionLength)) { - Record currentRecord(currentFullName); - currentRecord.destroy(); - didChange = true; - continue; - } - currentRecordStart = *(RecordIterator(currentRecordStart).operator++()); - } - if (didChange) { - notifyChangeToDelegate(); - } -} - -// PRIVATE - -Storage::Storage() : - m_magicHeader(Magic), - m_buffer(), - m_magicFooter(Magic), - m_delegate(nullptr), - m_lastRecordRetrieved(nullptr), - m_lastRecordRetrievedPointer(nullptr) -{ - assert(m_magicHeader == Magic); - assert(m_magicFooter == Magic); - // Set the size of the first record to 0 - overrideSizeAtPosition(m_buffer, 0); -} - -const char * Storage::fullNameOfRecord(const Record record) { - char * p = pointerOfRecord(record); - if (p != nullptr) { - return fullNameOfRecordStarting(p); - } - return nullptr; -} - -Storage::Record::ErrorStatus Storage::setFullNameOfRecord(const Record record, const char * fullName) { - if (!FullNameCompliant(fullName)) { - return Record::ErrorStatus::NonCompliantName; - } - if (isFullNameTaken(fullName, &record)) { - return Record::ErrorStatus::NameTaken; - } - size_t nameSize = strlen(fullName) + 1; - char * p = pointerOfRecord(record); - if (p != nullptr) { - size_t previousNameSize = strlen(fullNameOfRecordStarting(p))+1; - record_size_t previousRecordSize = sizeOfRecordStarting(p); - size_t newRecordSize = previousRecordSize-previousNameSize+nameSize; - if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+sizeof(record_size_t)+previousNameSize, nameSize-previousNameSize)) { - return notifyFullnessToDelegate(); - } - overrideSizeAtPosition(p, newRecordSize); - overrideFullNameAtPosition(p+sizeof(record_size_t), fullName); - notifyChangeToDelegate(record); - m_lastRecordRetrieved = record; - m_lastRecordRetrievedPointer = p; - return Record::ErrorStatus::None; - } - return Record::ErrorStatus::RecordDoesNotExist; + Storage::Record r = InternalStorage::recordBaseNamedWithExtension(baseName, extension); + return r == m_trashRecord ? Record() : r; } -Storage::Record::ErrorStatus Storage::setBaseNameWithExtensionOfRecord(Record record, const char * baseName, const char * extension) { - if (isBaseNameWithExtensionTaken(baseName, extension, &record)) { - return Record::ErrorStatus::NameTaken; - } - size_t nameSize = sizeOfBaseNameAndExtension(baseName, extension); - char * p = pointerOfRecord(record); - if (p != nullptr) { - size_t previousNameSize = strlen(fullNameOfRecordStarting(p))+1; - record_size_t previousRecordSize = sizeOfRecordStarting(p); - size_t newRecordSize = previousRecordSize-previousNameSize+nameSize; - if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+sizeof(record_size_t)+previousNameSize, nameSize-previousNameSize)) { - return notifyFullnessToDelegate(); - } - overrideSizeAtPosition(p, newRecordSize); - char * fullNamePosition = p + sizeof(record_size_t); - overrideBaseNameWithExtensionAtPosition(fullNamePosition, baseName, extension); - // Recompute the CRC32 - record = Record(fullNamePosition); - notifyChangeToDelegate(record); - m_lastRecordRetrieved = record; - m_lastRecordRetrievedPointer = p; - return Record::ErrorStatus::None; - } - return Record::ErrorStatus::RecordDoesNotExist; +Storage::Record Storage::recordBaseNamedWithExtensions(const char * baseName, const char * const extension[], size_t numberOfExtensions) { + Storage::Record r = InternalStorage::recordBaseNamedWithExtensions(baseName, extension, numberOfExtensions); + return r == m_trashRecord ? Record() : r; } -Storage::Record::Data Storage::valueOfRecord(const Record record) { - char * p = pointerOfRecord(record); - if (p != nullptr) { - const char * fullName = fullNameOfRecordStarting(p); - record_size_t size = sizeOfRecordStarting(p); - const void * value = valueOfRecordStarting(p); - return {.buffer = value, .size = size-strlen(fullName)-1-sizeof(record_size_t)}; - } - return {.buffer= nullptr, .size= 0}; -} - -Storage::Record::ErrorStatus Storage::setValueOfRecord(Record record, Record::Data data) { - char * p = pointerOfRecord(record); - /* TODO: if data.buffer == p, assert that size hasn't change and do not do any - * memcopy, but still notify the delegate. Beware of scripts and the accordion - * routine.*/ - if (p != nullptr) { - record_size_t previousRecordSize = sizeOfRecordStarting(p); - const char * fullName = fullNameOfRecordStarting(p); - size_t newRecordSize = sizeOfRecordWithFullName(fullName, data.size); - if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+previousRecordSize, newRecordSize-previousRecordSize)) { - return notifyFullnessToDelegate(); +const char * Storage::extensionOfRecordBaseNamedWithExtensions(const char * baseName, int baseNameLength, const char * const extension[], size_t numberOfExtensions) { + size_t nameLength = baseNameLength < 0 ? strlen(baseName) : baseNameLength; + { + const char * lastRetrievedRecordFullName = fullNameOfRecordStarting(m_lastRecordRetrievedPointer); + if (m_lastRecordRetrievedPointer != nullptr && strncmp(baseName, lastRetrievedRecordFullName, nameLength) == 0 && Record(lastRetrievedRecordFullName) != m_trashRecord) { + for (size_t i = 0; i < numberOfExtensions; i++) { + if (strcmp(lastRetrievedRecordFullName+nameLength+1 /*+1 to pass the dot*/, extension[i]) == 0) { + assert(UTF8Helper::CodePointIs(lastRetrievedRecordFullName + nameLength, '.')); + return extension[i]; + } + } } - record_size_t fullNameSize = strlen(fullName)+1; - overrideSizeAtPosition(p, newRecordSize); - overrideValueAtPosition(p+sizeof(record_size_t)+fullNameSize, data.buffer, data.size); - notifyChangeToDelegate(record); - m_lastRecordRetrieved = record; - m_lastRecordRetrievedPointer = p; - return Record::ErrorStatus::None; - } - return Record::ErrorStatus::RecordDoesNotExist; -} - -void Storage::destroyRecord(Record record) { - if (record.isNull()) { - return; - } - char * p = pointerOfRecord(record); - if (p != nullptr) { - record_size_t previousRecordSize = sizeOfRecordStarting(p); - slideBuffer(p+previousRecordSize, -previousRecordSize); - notifyChangeToDelegate(); - } -} - -char * Storage::pointerOfRecord(const Record record) const { - if (record.isNull()) { - return nullptr; - } - if (!m_lastRecordRetrieved.isNull() && record == m_lastRecordRetrieved) { - assert(m_lastRecordRetrievedPointer != nullptr); - return m_lastRecordRetrievedPointer; } for (char * p : *this) { - Record currentRecord(fullNameOfRecordStarting(p)); - if (record == currentRecord) { - m_lastRecordRetrieved = record; - m_lastRecordRetrievedPointer = p; - return p; + const char * currentName = fullNameOfRecordStarting(p); + if (strncmp(baseName, currentName, nameLength) == 0 && Record(currentName) != m_trashRecord) { + for (size_t i = 0; i < numberOfExtensions; i++) { + if (strcmp(currentName+nameLength+1 /*+1 to pass the dot*/, extension[i]) == 0) { + assert(UTF8Helper::CodePointIs(currentName + nameLength, '.')); + return extension[i]; + } + } } } - return nullptr; -} - -Storage::record_size_t Storage::sizeOfRecordStarting(char * start) const { - return StorageHelper::unalignedShort(start); -} - -const char * Storage::fullNameOfRecordStarting(char * start) const { - return start+sizeof(record_size_t); -} - -const void * Storage::valueOfRecordStarting(char * start) const { - char * currentChar = start+sizeof(record_size_t); - size_t fullNameLength = strlen(currentChar); - return currentChar+fullNameLength+1; -} -size_t Storage::overrideSizeAtPosition(char * position, record_size_t size) { - StorageHelper::writeUnalignedShort(size, position); - return sizeof(record_size_t); -} - -size_t Storage::overrideFullNameAtPosition(char * position, const char * fullName) { - return strlcpy(position, fullName, strlen(fullName)+1) + 1; -} - -size_t Storage::overrideBaseNameWithExtensionAtPosition(char * position, const char * baseName, const char * extension) { - size_t result = strlcpy(position, baseName, strlen(baseName)+1); // strlcpy copies the null terminating char - assert(UTF8Decoder::CharSizeOfCodePoint(k_dotChar) == 1); - *(position+result) = k_dotChar; // Replace the null terminating char with a dot - result++; - result += strlcpy(position+result, extension, strlen(extension)+1); - return result+1; -} - -size_t Storage::overrideValueAtPosition(char * position, const void * data, record_size_t size) { - memcpy(position, data, size); - return size; -} - -bool Storage::isFullNameTaken(const char * fullName, const Record * recordToExclude) { - Record r = Record(fullName); - return isNameOfRecordTaken(r, recordToExclude); + return nullptr; } -bool Storage::isBaseNameWithExtensionTaken(const char * baseName, const char * extension, Record * recordToExclude) { - Record r = Record(baseName, extension); - return isNameOfRecordTaken(r, recordToExclude); +int Storage::numberOfRecords() { + return InternalStorage::numberOfRecords() - (m_trashRecord != NULL ? 1 : 0); } -bool Storage::isNameOfRecordTaken(Record r, const Record * recordToExclude) { - if (r == Record()) { - /* If the CRC32 of fullName is 0, we want to refuse the name as it would - * interfere with our escape case in the Record constructor, when the given - * name is nullptr. */ - return true; - } +InternalStorage::Record Storage::recordAtIndex(int index) { + int currentIndex = -1; + const char * name = nullptr; + char * recordAddress = nullptr; for (char * p : *this) { - Record s(fullNameOfRecordStarting(p)); - if (recordToExclude && s == *recordToExclude) { - continue; - } - if (s == r) { - return true; + const char * currentName = fullNameOfRecordStarting(p); + if (Record(currentName) != m_trashRecord) { + currentIndex++; + if (currentIndex == index) { + recordAddress = p; + name = currentName; + break; + } } } - return false; -} - -bool Storage::FullNameCompliant(const char * fullName) { - // We check that there is one dot and one dot only. - const char * dotChar = UTF8Helper::CodePointSearch(fullName, k_dotChar); - if (*dotChar == 0) { - return false; - } - if (*(UTF8Helper::CodePointSearch(dotChar+1, k_dotChar)) == 0) { - return true; + if (name == nullptr) { + return Record(); } - return false; + Record r = Record(name); + m_lastRecordRetrieved = r; + m_lastRecordRetrievedPointer = recordAddress; + return Record(name); } -bool Storage::FullNameHasExtension(const char * fullName, const char * extension, size_t extensionLength) { - if (fullName == nullptr) { - return false; - } - size_t fullNameLength = strlen(fullName); - if (fullNameLength > extensionLength) { - const char * ext = fullName + fullNameLength - extensionLength; - if (UTF8Helper::PreviousCodePointIs(fullName, ext, k_dotChar) && strcmp(ext, extension) == 0) { - return true; +void Storage::reinsertTrash(const char * extension) { + if (!m_trashRecord.isNull()) { + char * p = pointerOfRecord(m_trashRecord); + const char * fullName = fullNameOfRecordStarting(p); + if (FullNameHasExtension(fullName, extension, strlen(extension))) { + m_trashRecord = Record(); } } - return false; } -char * Storage::endBuffer() { - char * currentBuffer = m_buffer; - for (char * p : *this) { - currentBuffer += sizeOfRecordStarting(p); +void Storage::emptyTrash() { + if (!m_trashRecord.isNull()) { + InternalStorage::destroyRecord(m_trashRecord); + m_trashRecord = Record(); } - return currentBuffer; -} - -size_t Storage::sizeOfBaseNameAndExtension(const char * baseName, const char * extension) const { - // +1 for the dot and +1 for the null terminating char - return strlen(baseName)+1+strlen(extension)+1; -} - -size_t Storage::sizeOfRecordWithBaseNameAndExtension(const char * baseName, const char * extension, size_t dataSize) const { - return sizeOfBaseNameAndExtension(baseName, extension) + dataSize + sizeof(record_size_t); -} - -size_t Storage::sizeOfRecordWithFullName(const char * fullName, size_t dataSize) const { - size_t nameSize = strlen(fullName)+1; - return nameSize+dataSize+sizeof(record_size_t); -} - -bool Storage::slideBuffer(char * position, int delta) { - if (delta > (int)availableSize()) { - return false; - } - memmove(position+delta, position, endBuffer()+sizeof(record_size_t)-position); - return true; -} - -Storage::Record Storage::privateRecordAndExtensionOfRecordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions, const char * * extensionResult, int baseNameLength) { - size_t nameLength = baseNameLength < 0 ? strlen(baseName) : baseNameLength; - { - const char * lastRetrievedRecordFullName = fullNameOfRecordStarting(m_lastRecordRetrievedPointer); - if (m_lastRecordRetrievedPointer != nullptr && strncmp(baseName, lastRetrievedRecordFullName, nameLength) == 0) { - for (size_t i = 0; i < numberOfExtensions; i++) { - if (strcmp(lastRetrievedRecordFullName+nameLength+1 /*+1 to pass the dot*/, extensions[i]) == 0) { - assert(UTF8Helper::CodePointIs(lastRetrievedRecordFullName + nameLength, '.')); - if (extensionResult != nullptr) { - *extensionResult = extensions[i]; - } - return m_lastRecordRetrieved; - } - } - } - } - for (char * p : *this) { - const char * currentName = fullNameOfRecordStarting(p); - if (strncmp(baseName, currentName, nameLength) == 0) { - for (size_t i = 0; i < numberOfExtensions; i++) { - if (strcmp(currentName+nameLength+1 /*+1 to pass the dot*/, extensions[i]) == 0) { - assert(UTF8Helper::CodePointIs(currentName + nameLength, '.')); - if (extensionResult != nullptr) { - *extensionResult = extensions[i]; - } - return Record(currentName); - } - } - } - } - if (extensionResult != nullptr) { - *extensionResult = nullptr; - } - return Record(); -} - -Storage::RecordIterator & Storage::RecordIterator::operator++() { - assert(m_recordStart); - record_size_t size = StorageHelper::unalignedShort(m_recordStart); - char * nextRecord = m_recordStart+size; - record_size_t newRecordSize = StorageHelper::unalignedShort(nextRecord); - m_recordStart = (newRecordSize == 0 ? nullptr : nextRecord); - return *this; } } diff --git a/ion/src/shared/unicode/utf8_helper.cpp b/ion/src/shared/unicode/utf8_helper.cpp index 9393069a6d2..0481df94134 100644 --- a/ion/src/shared/unicode/utf8_helper.cpp +++ b/ion/src/shared/unicode/utf8_helper.cpp @@ -475,6 +475,20 @@ const char * EndOfWord(const char * word) { return result; } +const char * EndOfWord(const char * word, const char * end) { + UTF8Decoder decoder(word); + CodePoint codePoint = decoder.nextCodePoint(); + const char * result = word; + while (!CodePointIsEndOfWord(codePoint)) { + result = decoder.stringPosition(); + if (result >= end) { + break; + } + codePoint = decoder.nextCodePoint(); + } + return result; +} + void countGlyphsInLine(const char * text, int * before, int * after, const char * beforeLocation, const char *afterLocation) { UTF8Helper::CodePointAction countGlyph = [](int, void * glyphCount, int, int) { int * castedCount = (int *) glyphCount; diff --git a/ion/src/simulator/3ds/Makefile b/ion/src/simulator/3ds/Makefile index 4c424f65c2e..5ca58d89160 100644 --- a/ion/src/simulator/3ds/Makefile +++ b/ion/src/simulator/3ds/Makefile @@ -19,22 +19,29 @@ ion_src += $(addprefix ion/src/simulator/3ds/, \ ion_src += ion/src/shared/collect_registers.cpp -sdl_simu_needs_to_be_removed += $(addprefix ion/src/simulator/shared/, \ +sdl_simu_needs_to_be_removed += $(addprefix ion/src/shared/, \ dummy/display.cpp \ dummy/led.cpp \ dummy/usb.cpp \ dummy/battery.cpp \ + dummy/store_script.cpp \ +) + +sdl_simu_needs_to_be_removed += $(addprefix ion/src/simulator/shared/, \ clipboard.cpp \ display.cpp:-headless \ events_keyboard.cpp:-headless \ events.cpp \ + events_platform.cpp \ framebuffer_base.cpp \ + framebuffer.cpp \ keyboard_sdl.cpp:-headless \ + keyboard.cpp \ main_sdl.cpp:-headless \ + main.cpp \ layout.cpp:-headless \ - dummy/store_script.cpp \ + timing.cpp \ ) -# Remove the dummy diplay (re-implemented) and the SDL simulator stuff. +# Remove the dummy display (re-implemented) and the SDL simulator stuff. ion_src := $(filter-out $(sdl_simu_needs_to_be_removed),$(ion_src)) - diff --git a/ion/src/simulator/3ds/assets/app.rsf b/ion/src/simulator/3ds/assets/app.rsf index 8ebf29bc2a6..8e1a5900dc1 100644 --- a/ion/src/simulator/3ds/assets/app.rsf +++ b/ion/src/simulator/3ds/assets/app.rsf @@ -1,7 +1,7 @@ BasicInfo: - Title : Omega + Title : Upsilon CompanyCode : "00" - ProductCode : CTR-E-OMEGA + ProductCode : CTR-E-Upsilon Logo : Homebrew # Nintendo / Licensed / Distributed / iQue / iQueForSystem RomFs: diff --git a/ion/src/simulator/3ds/driver/led.cpp b/ion/src/simulator/3ds/driver/led.cpp index 819390f57ce..8c403b5134d 100644 --- a/ion/src/simulator/3ds/driver/led.cpp +++ b/ion/src/simulator/3ds/driver/led.cpp @@ -21,7 +21,7 @@ void setColor(KDColor c) { /* * According to https://www.3dbrew.org/wiki/MCURTC:SetInfoLEDPattern - * annimation pattern is as follow + * animation pattern is as follow * u8 ??? | u8 loop_delay | u8 smoothing | u8 delay */ RGBLedPattern pat; @@ -44,10 +44,10 @@ void setBlinking(uint16_t period, float dutyCycle) { /* * According to https://www.3dbrew.org/wiki/MCURTC:SetInfoLEDPattern - * annimation pattern is as follow + * animation pattern is as follow * u8 ??? | u8 loop_delay | u8 smoothing | u8 delay * - * Se, we seet ??? to 0, loop_delay to 0 (to have it loop) + * Se, we set ??? to 0, loop_delay to 0 (to have it loop) */ RGBLedPattern pat; memset(&pat, 0, sizeof(pat)); diff --git a/ion/src/simulator/3ds/driver/usb.cpp b/ion/src/simulator/3ds/driver/usb.cpp index 59e83bea5bb..27d63274cc4 100644 --- a/ion/src/simulator/3ds/driver/usb.cpp +++ b/ion/src/simulator/3ds/driver/usb.cpp @@ -13,7 +13,7 @@ bool Ion::USB::isEnumerated() { void Ion::USB::clearEnumerationInterrupt() { } -void Ion::USB::DFU(bool) { +void Ion::USB::DFU(bool, void *) { } void Ion::USB::enable() { diff --git a/ion/src/simulator/3ds/main.cpp b/ion/src/simulator/3ds/main.cpp index c0b38e9c49a..d95a248c82d 100644 --- a/ion/src/simulator/3ds/main.cpp +++ b/ion/src/simulator/3ds/main.cpp @@ -19,6 +19,10 @@ void Ion::Timing::msleep(uint32_t ms) { svcSleepThread((s64) ms * 1000); } +uint64_t Ion::Timing::millis() { + return svcGetSystemTick() / (1000 * 1000); +} + int main(int argc, char * argv[]) { Ion::Simulator::Main::init(); diff --git a/ion/src/simulator/android/Makefile b/ion/src/simulator/android/Makefile index b4afe8767b7..0af9abb9798 100644 --- a/ion/src/simulator/android/Makefile +++ b/ion/src/simulator/android/Makefile @@ -19,8 +19,8 @@ $(call object_for,ion/src/simulator/shared/main.cpp) : SFLAGS += -DEPSILON_SDL_F LDFLAGS += -ljnigraphics -llog -# If ARCH is not defined, we will re-trigger a build for each avaialble ARCH. -# This is used to build APKs, which needs to embbed a binary for each ARCH. +# If ARCH is not defined, we will re-trigger a build for each available ARCH. +# This is used to build APKs, which needs to embed a binary for each ARCH. ifndef ARCH diff --git a/ion/src/simulator/android/build.gradle b/ion/src/simulator/android/build.gradle index bb48650fd6c..c2e36530e41 100644 --- a/ion/src/simulator/android/build.gradle +++ b/ion/src/simulator/android/build.gradle @@ -55,6 +55,8 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt') if (projectVariable('SIGNING_STORE_FILE')) { signingConfig signingConfigs.environment + } else { + signingConfig signingConfigs.debug } } } diff --git a/ion/src/simulator/android/src/cpp/platform_language.cpp b/ion/src/simulator/android/src/cpp/platform_language.cpp index b41b1ae4695..7aa29c2eea8 100644 --- a/ion/src/simulator/android/src/cpp/platform_language.cpp +++ b/ion/src/simulator/android/src/cpp/platform_language.cpp @@ -12,7 +12,7 @@ const char * languageCode() { JNIEnv * env = static_cast(SDL_AndroidGetJNIEnv()); jobject activity = static_cast(SDL_AndroidGetActivity()); - jclass j_class = env->FindClass("io/github/omega/OmegaActivity"); + jclass j_class = env->FindClass("io/github/omega/simulator/OmegaActivity"); jmethodID j_methodId = env->GetMethodID( j_class, "retrieveLanguage", diff --git a/ion/src/simulator/android/src/java/io/github/omega/simulator/OmegaActivity.java b/ion/src/simulator/android/src/java/io/github/omega/simulator/OmegaActivity.java index 25992236565..317847f3aa8 100644 --- a/ion/src/simulator/android/src/java/io/github/omega/simulator/OmegaActivity.java +++ b/ion/src/simulator/android/src/java/io/github/omega/simulator/OmegaActivity.java @@ -32,7 +32,7 @@ public Bitmap retrieveBitmapAsset(String identifier) { this.getResources().getAssets().open(identifier) ); } catch (Exception e) { - Log.w("LoadTexture", "Coundn't load a file:" + identifier); + Log.w("LoadTexture", "Couldn't load a file:" + identifier); } return bitmap; } diff --git a/ion/src/simulator/android/src/java/org/libsdl/app/SDLActivity.java b/ion/src/simulator/android/src/java/org/libsdl/app/SDLActivity.java index 082ae4be174..8d6ad984586 100644 --- a/ion/src/simulator/android/src/java/org/libsdl/app/SDLActivity.java +++ b/ion/src/simulator/android/src/java/org/libsdl/app/SDLActivity.java @@ -164,7 +164,7 @@ protected String[] getArguments() { } public static void initialize() { - // The static nature of the singleton and Android quirkyness force us to initialize everything here + // The static nature of the singleton and Android quirkiness force us to initialize everything here // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values mSingleton = null; mSurface = null; diff --git a/ion/src/simulator/assets/background.jpg b/ion/src/simulator/assets/background.jpg index 64c16f8af17..ca044c376b4 100644 Binary files a/ion/src/simulator/assets/background.jpg and b/ion/src/simulator/assets/background.jpg differ diff --git a/ion/src/simulator/assets/logoAUR.png b/ion/src/simulator/assets/logoAUR.png index cfbc7166121..84e55656580 100644 Binary files a/ion/src/simulator/assets/logoAUR.png and b/ion/src/simulator/assets/logoAUR.png differ diff --git a/ion/src/simulator/assets/logoMacOS.png b/ion/src/simulator/assets/logoMacOS.png index 295a4454dc5..15cc124e931 100644 Binary files a/ion/src/simulator/assets/logoMacOS.png and b/ion/src/simulator/assets/logoMacOS.png differ diff --git a/ion/src/simulator/assets/logoWin.ico b/ion/src/simulator/assets/logoWin.ico index b2927c075ae..783e1141442 100644 Binary files a/ion/src/simulator/assets/logoWin.ico and b/ion/src/simulator/assets/logoWin.ico differ diff --git a/ion/src/simulator/external/config.fxcg.mak b/ion/src/simulator/external/config.fxcg.mak new file mode 100644 index 00000000000..5c9e873ab47 --- /dev/null +++ b/ion/src/simulator/external/config.fxcg.mak @@ -0,0 +1,2 @@ +undefine sdl_src +undefine ion_simulator_sdl_src diff --git a/ion/src/simulator/external/config.nspire.mak b/ion/src/simulator/external/config.nspire.mak new file mode 100644 index 00000000000..5c9e873ab47 --- /dev/null +++ b/ion/src/simulator/external/config.nspire.mak @@ -0,0 +1,2 @@ +undefine sdl_src +undefine ion_simulator_sdl_src diff --git a/ion/src/simulator/external/config.web.mak b/ion/src/simulator/external/config.web.mak index 003fcf83971..0821a80a37e 100644 --- a/ion/src/simulator/external/config.web.mak +++ b/ion/src/simulator/external/config.web.mak @@ -36,3 +36,6 @@ sdl_src += $(addprefix ion/src/simulator/external/sdl/src/, \ video/emscripten/SDL_emscriptenopengles.c \ video/emscripten/SDL_emscriptenvideo.c \ ) + +# Add SDL_JOYSTICK_EMSCRIPTEN flag +SDL_SFLAGS += -DSDL_JOYSTICK_EMSCRIPTEN diff --git a/ion/src/simulator/external/sdl/include/SDL_stdinc.h b/ion/src/simulator/external/sdl/include/SDL_stdinc.h index a95700af2fd..8845cd771a8 100644 --- a/ion/src/simulator/external/sdl/include/SDL_stdinc.h +++ b/ion/src/simulator/external/sdl/include/SDL_stdinc.h @@ -309,8 +309,13 @@ typedef uint64_t Uint64; #endif #endif /* SDL_DISABLE_ANALYZE_MACROS */ +#ifndef __3DS__ #define SDL_COMPILE_TIME_ASSERT(name, x) \ - typedef int SDL_compile_time_assert_ ## name[(x) * 2 - 1] + typedef int SDL_dummy_ ## name[(x) * 2 - 1] +#else +#define SDL_COMPILE_TIME_ASSERT(name, x) \ + typedef int SDL_dummy_ ## name[0] +#endif /** \cond */ #ifndef DOXYGEN_SHOULD_IGNORE_THIS SDL_COMPILE_TIME_ASSERT(uint8, sizeof(Uint8) == 1); diff --git a/ion/src/simulator/external/sdl/src/core/winrt/SDL_winrtapp_direct3d.cpp b/ion/src/simulator/external/sdl/src/core/winrt/SDL_winrtapp_direct3d.cpp index a6f76db52b1..d01225f38f7 100644 --- a/ion/src/simulator/external/sdl/src/core/winrt/SDL_winrtapp_direct3d.cpp +++ b/ion/src/simulator/external/sdl/src/core/winrt/SDL_winrtapp_direct3d.cpp @@ -586,7 +586,7 @@ void SDL_WinRTApp::OnVisibilityChanged(CoreWindow^ sender, VisibilityChangedEven } // HACK: Prevent SDL's window-hide handling code, which currently - // triggers a fake window resize (possibly erronously), from + // triggers a fake window resize (possibly erroneously), from // marking the SDL window's surface as invalid. // // A better solution to this probably involves figuring out if the diff --git a/ion/src/simulator/external/sdl/src/hidapi/ios/hid.m b/ion/src/simulator/external/sdl/src/hidapi/ios/hid.m index a0ca7cd0222..9470d44d4c8 100644 --- a/ion/src/simulator/external/sdl/src/hidapi/ios/hid.m +++ b/ion/src/simulator/external/sdl/src/hidapi/ios/hid.m @@ -337,7 +337,7 @@ - (void)centralManagerDidUpdateState:(CBCentralManager *)central NSLog( @"CoreBluetooth BLE hardware is powered on and ready" ); // at startup, if we have no already attached peripherals, do a 20s scan for new unpaired devices, - // otherwise callers should occaisionally do additional scans. we don't want to continuously be + // otherwise callers should occasionally do additional scans. we don't want to continuously be // scanning because it drains battery, causes other nearby people to have a hard time pairing their // Steam Controllers, and may also trigger firmware weirdness when a device attempts to start // the pairing sequence multiple times concurrently @@ -684,7 +684,7 @@ - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CB - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { - NSLog( @"didUpdateNotifcationStateForCharacteristic %@ (%@)", characteristic, error ); + NSLog( @"didUpdateNotificationStateForCharacteristic %@ (%@)", characteristic, error ); } @end diff --git a/ion/src/simulator/external/sdl/src/hidapi/linux/hid.c b/ion/src/simulator/external/sdl/src/hidapi/linux/hid.c index 2cc2d8b9892..f4e05a2649b 100644 --- a/ion/src/simulator/external/sdl/src/hidapi/linux/hid.c +++ b/ion/src/simulator/external/sdl/src/hidapi/linux/hid.c @@ -143,7 +143,7 @@ static wchar_t *utf8_to_wchar_t(const char *utf8) return ret; } -/* Get an attribute value from a udev_device and return it as a whar_t +/* Get an attribute value from a udev_device and return it as a wchar_t string. The returned string must be freed with free() when done.*/ static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name) { diff --git a/ion/src/simulator/external/sdl/src/hidapi/mac/hid.c b/ion/src/simulator/external/sdl/src/hidapi/mac/hid.c index 55daf57820e..98522a60f6c 100644 --- a/ion/src/simulator/external/sdl/src/hidapi/mac/hid.c +++ b/ion/src/simulator/external/sdl/src/hidapi/mac/hid.c @@ -644,7 +644,7 @@ static void hid_report_callback(void *context, IOReturn result, void *sender, } -/* This gets called when the read_thred's run loop gets signaled by +/* This gets called when the read_thread's run loop gets signaled by hid_close(), and serves to stop the read_thread's run loop. */ static void perform_signal_callback(void *context) { @@ -702,7 +702,7 @@ static void *read_thread(void *param) /* Now that the read thread is stopping, Wake any threads which are waiting on data (in hid_read_timeout()). Do this under a mutex to make sure that a thread which is about to go to sleep waiting on - the condition acutally will go to sleep before the condition is + the condition actually will go to sleep before the condition is signaled. */ pthread_mutex_lock(&dev->mutex); pthread_cond_broadcast(&dev->condition); @@ -874,7 +874,7 @@ static int cond_wait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_ return res; /* A res of 0 means we may have been signaled or it may - be a spurious wakeup. Check to see that there's acutally + be a spurious wakeup. Check to see that there's actually data in the queue before returning, and if not, go back to sleep. See the pthread_cond_timedwait() man page for details. */ @@ -894,7 +894,7 @@ static int cond_timedwait(const hid_device *dev, pthread_cond_t *cond, pthread_m return res; /* A res of 0 means we may have been signaled or it may - be a spurious wakeup. Check to see that there's acutally + be a spurious wakeup. Check to see that there's actually data in the queue before returning, and if not, go back to sleep. See the pthread_cond_timedwait() man page for details. */ diff --git a/ion/src/simulator/external/sdl/src/joystick/emscripten/SDL_sysjoystick.c b/ion/src/simulator/external/sdl/src/joystick/emscripten/SDL_sysjoystick.c index 92b831a05b6..83eb3c65d95 100644 --- a/ion/src/simulator/external/sdl/src/joystick/emscripten/SDL_sysjoystick.c +++ b/ion/src/simulator/external/sdl/src/joystick/emscripten/SDL_sysjoystick.c @@ -415,6 +415,23 @@ SDL_JoystickDriver SDL_EMSCRIPTEN_JoystickDriver = EMSCRIPTEN_JoystickQuit, }; +// Alias SDL_DUMMY_JoystickDriver to SDL_EMSCRIPTEN_JoystickDriver +SDL_JoystickDriver SDL_DUMMY_JoystickDriver = +{ + EMSCRIPTEN_JoystickInit, + EMSCRIPTEN_JoystickGetCount, + EMSCRIPTEN_JoystickDetect, + EMSCRIPTEN_JoystickGetDeviceName, + EMSCRIPTEN_JoystickGetDevicePlayerIndex, + EMSCRIPTEN_JoystickGetDeviceGUID, + EMSCRIPTEN_JoystickGetDeviceInstanceID, + EMSCRIPTEN_JoystickOpen, + EMSCRIPTEN_JoystickRumble, + EMSCRIPTEN_JoystickUpdate, + EMSCRIPTEN_JoystickClose, + EMSCRIPTEN_JoystickQuit, +}; + #endif /* SDL_JOYSTICK_EMSCRIPTEN */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/ion/src/simulator/external/sdl/src/libm/e_log.c b/ion/src/simulator/external/sdl/src/libm/e_log.c index 208df815c38..e4f4c393e6e 100644 --- a/ion/src/simulator/external/sdl/src/libm/e_log.c +++ b/ion/src/simulator/external/sdl/src/libm/e_log.c @@ -15,7 +15,7 @@ #endif /* __ieee754_log(x) - * Return the logrithm of x + * Return the logarithm of x * * Method : * 1. Argument Reduction: find k and f such that diff --git a/ion/src/simulator/external/sdl/src/libm/e_pow.c b/ion/src/simulator/external/sdl/src/libm/e_pow.c index a3d24ced9c6..eed93d076b8 100644 --- a/ion/src/simulator/external/sdl/src/libm/e_pow.c +++ b/ion/src/simulator/external/sdl/src/libm/e_pow.c @@ -16,7 +16,7 @@ * 1. Compute and return log2(x) in two pieces: * log2(x) = w1 + w2, * where w1 has 53-24 = 29 bit trailing zeros. - * 2. Perform y*log2(x) = n+y' by simulating muti-precision + * 2. Perform y*log2(x) = n+y' by simulating multi-precision * arithmetic, where |y'|<=0.5. * 3. Return x**y = 2**n*exp(y'*log2) * diff --git a/ion/src/simulator/external/sdl/src/libm/e_sqrt.c b/ion/src/simulator/external/sdl/src/libm/e_sqrt.c index 39c83e1f334..652fc01c3e9 100644 --- a/ion/src/simulator/external/sdl/src/libm/e_sqrt.c +++ b/ion/src/simulator/external/sdl/src/libm/e_sqrt.c @@ -37,7 +37,7 @@ * If (2) is false, then q = q ; otherwise q = q + 2 . * i+1 i i+1 i * - * With some algebric manipulation, it is not difficult to see + * With some algebraic manipulation, it is not difficult to see * that (2) is equivalent to * -(i+1) * s + 2 <= y (3) @@ -116,7 +116,7 @@ double attribute_hidden __ieee754_sqrt(double x) ix0 |= (ix1>>(32-i)); ix1 <<= i; } - m -= 1023; /* unbias exponent */ + m -= 1023; /* unbiased exponent */ ix0 = (ix0&0x000fffff)|0x00100000; if(m&1){ /* odd m, double x to make it even */ ix0 += ix0 + ((ix1&sign)>>31); @@ -280,7 +280,7 @@ A. sqrt(x) by Newton Iteration This formula has one division fewer than the one above; however, it requires more multiplications and additions. Also x must be scaled in advance to avoid spurious overflow in evaluating the - expression 3y*y+x. Hence it is not recommended uless division + expression 3y*y+x. Hence it is not recommended unless division is slow. If division is very slow, then one should use the reciproot algorithm given in section B. @@ -330,7 +330,7 @@ B. sqrt(x) by Reciproot Iteration Let x0 and x1 be the leading and the trailing 32-bit words of a floating point number x (in IEEE double format) respectively - (see section A). By performing shifs and subtracts on x0 and y0, + (see section A). By performing shifts and subtracts on x0 and y0, we obtain a 7.8-bit approximation of 1/sqrt(x) as follows. k := 0x5fe80000 - (x0>>1); diff --git a/ion/src/simulator/external/sdl/src/libm/k_rem_pio2.c b/ion/src/simulator/external/sdl/src/libm/k_rem_pio2.c index 393db541d18..d4b8155b2de 100644 --- a/ion/src/simulator/external/sdl/src/libm/k_rem_pio2.c +++ b/ion/src/simulator/external/sdl/src/libm/k_rem_pio2.c @@ -47,7 +47,7 @@ * 64-bit precision 2 * 113-bit precision 3 * The actual value is the sum of them. Thus for 113-bit - * precison, one may have to do something like: + * precision, one may have to do something like: * * long double t,w,r_head, r_tail; * t = (long double)y[2] + (long double)y[1]; @@ -184,7 +184,7 @@ int32_t attribute_hidden __kernel_rem_pio2(double *x, double *y, int e0, int nx, jz = jk; recompute: - /* distill q[] into iq[] reversingly */ + /* distill q[] into iq[] reversely */ for(i=0,j=jz,z=q[jz];j>0;i++,j--) { fw = (double)((int32_t)(twon24* z)); iq[i] = (int32_t)(z-two24*fw); diff --git a/ion/src/simulator/external/sdl/src/video/emscripten/SDL_emscriptenvideo.c b/ion/src/simulator/external/sdl/src/video/emscripten/SDL_emscriptenvideo.c index ad9b33cbcb4..36de89e3d7d 100644 --- a/ion/src/simulator/external/sdl/src/video/emscripten/SDL_emscriptenvideo.c +++ b/ion/src/simulator/external/sdl/src/video/emscripten/SDL_emscriptenvideo.c @@ -268,9 +268,6 @@ Emscripten_DestroyWindow(_THIS, SDL_Window * window) data->egl_surface = EGL_NO_SURFACE; } #endif - - /* We can't destroy the canvas, so resize it to zero instead */ - emscripten_set_canvas_element_size(data->canvas_id, 0, 0); SDL_free(data->canvas_id); SDL_free(window->driverdata); diff --git a/ion/src/simulator/external/sdl/src/video/khronos/vulkan/vk_icd.h b/ion/src/simulator/external/sdl/src/video/khronos/vulkan/vk_icd.h index b935fa1786e..e0835b7abf4 100644 --- a/ion/src/simulator/external/sdl/src/video/khronos/vulkan/vk_icd.h +++ b/ion/src/simulator/external/sdl/src/video/khronos/vulkan/vk_icd.h @@ -33,7 +33,7 @@ // Version 2 - Add Loader/ICD Interface version negotiation // via vk_icdNegotiateLoaderICDInterfaceVersion. // Version 3 - Add ICD creation/destruction of KHR_surface objects. -// Version 4 - Add unknown physical device extension qyering via +// Version 4 - Add unknown physical device extension querying via // vk_icdGetPhysicalDeviceProcAddr. // Version 5 - Tells ICDs that the loader is now paying attention to the // application version of Vulkan passed into the ApplicationInfo diff --git a/ion/src/simulator/external/sdl/src/video/khronos/vulkan/vk_layer.h b/ion/src/simulator/external/sdl/src/video/khronos/vulkan/vk_layer.h index 823c88ab759..631353d5827 100644 --- a/ion/src/simulator/external/sdl/src/video/khronos/vulkan/vk_layer.h +++ b/ion/src/simulator/external/sdl/src/video/khronos/vulkan/vk_layer.h @@ -52,7 +52,7 @@ typedef PFN_vkVoidFunction (VKAPI_PTR *PFN_GetPhysicalDeviceProcAddr)(VkInstance // Version negotiation values typedef enum VkNegotiateLayerStructType { - LAYER_NEGOTIATE_UNINTIALIZED = 0, + LAYER_NEGOTIATE_UNINITIALIZED = 0, LAYER_NEGOTIATE_INTERFACE_STRUCT = 1, } VkNegotiateLayerStructType; diff --git a/ion/src/simulator/external/sdl/src/video/qnx/gl.c b/ion/src/simulator/external/sdl/src/video/qnx/gl.c index 19e1bd4f710..2aebec675b3 100644 --- a/ion/src/simulator/external/sdl/src/video/qnx/gl.c +++ b/ion/src/simulator/external/sdl/src/video/qnx/gl.c @@ -25,7 +25,7 @@ static EGLDisplay egl_disp; /** - * Detertmines the pixel format to use based on the current display and EGL + * Determines the pixel format to use based on the current display and EGL * configuration. * @param egl_conf EGL configuration to use * @return A SCREEN_FORMAT* constant for the pixel format to use diff --git a/ion/src/simulator/external/sdl/src/video/qnx/keyboard.c b/ion/src/simulator/external/sdl/src/video/qnx/keyboard.c index 86c6395bafe..c7288a215e9 100644 --- a/ion/src/simulator/external/sdl/src/video/qnx/keyboard.c +++ b/ion/src/simulator/external/sdl/src/video/qnx/keyboard.c @@ -27,7 +27,7 @@ #include /** - * A map thta translates Screen key names to SDL scan codes. + * A map that translates Screen key names to SDL scan codes. * This map is incomplete, but should include most major keys. */ static int key_to_sdl[] = { diff --git a/ion/src/simulator/external/sdl/src/video/qnx/video.c b/ion/src/simulator/external/sdl/src/video/qnx/video.c index ff8223c77a1..84ed13cf5a7 100644 --- a/ion/src/simulator/external/sdl/src/video/qnx/video.c +++ b/ion/src/simulator/external/sdl/src/video/qnx/video.c @@ -182,7 +182,7 @@ createWindowFramebuffer(_THIS, SDL_Window * window, Uint32 * format, * Informs the window manager that the window needs to be updated. * @param _THIS * @param window The window to update - * @param rects An array of reectangular areas to update + * @param rects An array of rectangular areas to update * @param numrects Rect array length * @return 0 if successful, -1 on error */ diff --git a/ion/src/simulator/external/sdl/src/video/winrt/SDL_winrtvideo.cpp b/ion/src/simulator/external/sdl/src/video/winrt/SDL_winrtvideo.cpp index 87c61c928a3..fa10a49c01c 100644 --- a/ion/src/simulator/external/sdl/src/video/winrt/SDL_winrtvideo.cpp +++ b/ion/src/simulator/external/sdl/src/video/winrt/SDL_winrtvideo.cpp @@ -597,7 +597,7 @@ WINRT_CreateWindow(_THIS, SDL_Window * window) /* OpenGL ES 2 wasn't requested. Don't set up an EGL surface. */ data->egl_surface = EGL_NO_SURFACE; } else { - /* OpenGL ES 2 was reuqested. Set up an EGL surface. */ + /* OpenGL ES 2 was requested. Set up an EGL surface. */ SDL_VideoData * video_data = (SDL_VideoData *)_this->driverdata; /* Call SDL_EGL_ChooseConfig and eglCreateWindowSurface directly, diff --git a/ion/src/simulator/fxcg/Makefile b/ion/src/simulator/fxcg/Makefile new file mode 100644 index 00000000000..02a9ed71776 --- /dev/null +++ b/ion/src/simulator/fxcg/Makefile @@ -0,0 +1,66 @@ + +ion_src += $(addprefix ion/src/simulator/fxcg/, \ + main.cpp \ + clipboard.cpp \ + display.cpp \ + framebuffer.cpp \ + telemetry_init.cpp \ + keyboard.cpp \ + events_keyboard.cpp \ + events.cpp \ + timing.cpp \ + console.cpp \ + backlight.cpp \ + power.cpp \ + menuHandler.cpp \ +) + +liba_src += $(addprefix liba/src/, \ + strlcat.c \ + strlcpy.c \ +) + +ion_src += ion/src/shared/collect_registers.cpp + +sdl_simu_needs_to_be_removed += $(addprefix ion/src/simulator/shared/, \ + main.cpp \ + clipboard.cpp \ + display.cpp \ + framebuffer.cpp \ + keyboard.cpp \ + events_keyboard.cpp \ + events_platform.cpp \ + events.cpp \ + layout.cpp \ + actions.cpp \ + window.cpp \ + timing.cpp \ + console.cpp \ +) + +sdl_simu_needs_to_be_removed += $(addprefix ion/src/shared/dummy/, \ + backlight.cpp \ + power.cpp \ +) + +#sdl_simu_needs_to_be_removed += $(addprefix ion/src/simulator/shared/dummy/, \ +# display.cpp \ +# led.cpp \ +# usb.cpp \ +# battery.cpp \ +# store_script.cpp \ +#) + +# Remove the dummy diaplay (re-implemented) and the SDL simulator stuff. +ion_src := $(filter-out $(sdl_simu_needs_to_be_removed),$(ion_src)) + +SFLAGS := $(filter-out -Iion/src/simulator/external/sdl/include,$(SFLAGS)) + +SFLAGS += -DFXCG50 -DTARGET_FXCG50 -m4-nofpu -mb -ffreestanding -nostdlib -Wa,--dsp -fstrict-volatile-bitfields -g -Os +LDFLAGS += -nostdlib -Wl,--no-warn-rwx-segments -lgint-cg -lc -lgint-cg -lc -lgcc -lopenlibm -lstdc++ -lgcc + +ifdef FASTLOAD + LDFLAGS += -T fxcg50_fastload.ld +else + LDFLAGS += -T fxcg50.ld +endif diff --git a/ion/src/simulator/fxcg/assets/icon-sel.png b/ion/src/simulator/fxcg/assets/icon-sel.png new file mode 100644 index 00000000000..a016c36b39d Binary files /dev/null and b/ion/src/simulator/fxcg/assets/icon-sel.png differ diff --git a/ion/src/simulator/fxcg/assets/icon-uns.png b/ion/src/simulator/fxcg/assets/icon-uns.png new file mode 100644 index 00000000000..c2f71847222 Binary files /dev/null and b/ion/src/simulator/fxcg/assets/icon-uns.png differ diff --git a/ion/src/simulator/fxcg/backlight.cpp b/ion/src/simulator/fxcg/backlight.cpp new file mode 100644 index 00000000000..18360bc0bfc --- /dev/null +++ b/ion/src/simulator/fxcg/backlight.cpp @@ -0,0 +1,67 @@ +#include + +#include +#include + +// From gint: +/* Interface with the controller */ +static volatile uint16_t *intf = (uint16_t *)0xb4000000; +/* Bit 4 of Port R controls the RS bit of the display driver */ +static volatile uint8_t *PRDR = (uint8_t *)0xa405013c; + +GINLINE static void select(uint16_t reg) +{ + /* Clear RS and write the register number */ + *PRDR &= ~0x10; + synco(); + *intf = reg; + synco(); + + /* Set RS back. We don't do this in read()/write() because the display + driver is optimized for consecutive GRAM access. LCD-transfers will + be faster when executing select() followed by several calls to + write(). (Although most applications should use the DMA instead.) */ + *PRDR |= 0x10; + synco(); +} + +// From Utilities addin: +// START OF POWER MANAGEMENT CODE +#define LCDC *(unsigned int*)(0xB4000000) +int getRawBacklightSubLevel() +{ + // Bdisp_DDRegisterSelect(0x5a1); + select(0x5a1); + return (LCDC & 0xFF) - 6; +} +void setRawBacklightSubLevel(int level) +{ + // Bdisp_DDRegisterSelect(0x5a1); + select(0x5a1); + LCDC = (level & 0xFF) + 6; +} +// END OF POWER MANAGEMENT CODE + +namespace Ion { +namespace Backlight { + +uint8_t brightness() { + return getRawBacklightSubLevel(); +} + +void setBrightness(uint8_t b) { + setRawBacklightSubLevel(b); +} + +void init() { +} + +bool isInitialized() { + return true; +} + +void shutdown() { +} + +} +} diff --git a/ion/src/simulator/fxcg/clipboard.cpp b/ion/src/simulator/fxcg/clipboard.cpp new file mode 100644 index 00000000000..c3ce45627c9 --- /dev/null +++ b/ion/src/simulator/fxcg/clipboard.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + +namespace Ion { +namespace Clipboard { + +uint32_t localClipboardVersion; + +void write(const char * text) { +} + +const char * read() { + return nullptr; +} + +} +} diff --git a/ion/src/simulator/fxcg/console.cpp b/ion/src/simulator/fxcg/console.cpp new file mode 100644 index 00000000000..d8957c6e6ca --- /dev/null +++ b/ion/src/simulator/fxcg/console.cpp @@ -0,0 +1,24 @@ +#include +#include "main.h" +#include +#include + +namespace Ion { +namespace Console { + +char readChar() { + return 0; +} + +void writeChar(char c) { + // fxlibc conflicts with this + #undef putchar + KDIonContext::putchar(c); +} + +bool transmissionDone() { + return true; +} + +} +} diff --git a/ion/src/simulator/fxcg/display.cpp b/ion/src/simulator/fxcg/display.cpp new file mode 100644 index 00000000000..27e896beef9 --- /dev/null +++ b/ion/src/simulator/fxcg/display.cpp @@ -0,0 +1,30 @@ +#include "display.h" +#include "framebuffer.h" +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace Ion { +namespace Simulator { +namespace Display { + +void init() { +} + +void quit() { +} + +void draw() { + dupdate(); +} + +} +} +} diff --git a/ion/src/simulator/fxcg/display.h b/ion/src/simulator/fxcg/display.h new file mode 100644 index 00000000000..ab524fd3c55 --- /dev/null +++ b/ion/src/simulator/fxcg/display.h @@ -0,0 +1,19 @@ +#ifndef ION_SIMULATOR_DISPLAY_H +#define ION_SIMULATOR_DISPLAY_H + +#include + +namespace Ion { +namespace Simulator { +namespace Display { + +void init(); +void quit(); + +void draw(); + +} +} +} + +#endif diff --git a/ion/src/simulator/fxcg/events.cpp b/ion/src/simulator/fxcg/events.cpp new file mode 100644 index 00000000000..9b8d0055751 --- /dev/null +++ b/ion/src/simulator/fxcg/events.cpp @@ -0,0 +1,23 @@ +#include "events.h" +#include + +namespace Ion { +namespace Events { + +void didPressNewKey() { +} + +char * sharedExternalTextBuffer() { + static char buffer[sharedExternalTextBufferSize]; + return buffer; +} + +const char * Event::text() const { + if (*this == ExternalText) { + return const_cast(sharedExternalTextBuffer()); + } + return defaultText(); +} + +} +} diff --git a/ion/src/simulator/fxcg/events.h b/ion/src/simulator/fxcg/events.h new file mode 100644 index 00000000000..b012a43c5e5 --- /dev/null +++ b/ion/src/simulator/fxcg/events.h @@ -0,0 +1,24 @@ +#ifndef ION_SIMULATOR_EVENTS_H +#define ION_SIMULATOR_EVENTS_H + +#include + +namespace Ion { +namespace Simulator { +namespace Events { + +void dumpEventCount(int i); +void logAfter(int numberOfEvents); + +} +} + +namespace Events { + +static constexpr int sharedExternalTextBufferSize = 2; +char * sharedExternalTextBuffer(); + +} +} + +#endif diff --git a/ion/src/simulator/fxcg/events_keyboard.cpp b/ion/src/simulator/fxcg/events_keyboard.cpp new file mode 100644 index 00000000000..615c327d54c --- /dev/null +++ b/ion/src/simulator/fxcg/events_keyboard.cpp @@ -0,0 +1,15 @@ +#include + +namespace Ion { +namespace Events { + + +Event getPlatformEvent() { + Event result = None; + + return result; +} + + +} +} diff --git a/ion/src/simulator/fxcg/framebuffer.cpp b/ion/src/simulator/fxcg/framebuffer.cpp new file mode 100644 index 00000000000..d680467c129 --- /dev/null +++ b/ion/src/simulator/fxcg/framebuffer.cpp @@ -0,0 +1,56 @@ +#include "framebuffer.h" +#include +#include +#include "main.h" + +#include +#include + +// static KDColor sPixels[Ion::Display::Width * Ion::Display::Height]; +static_assert(sizeof(KDColor) == sizeof(uint16_t), "KDColor is not 16 bits"); +static KDColor* sPixels = (KDColor*) gint_vram; +static bool sFrameBufferActive = true; + +namespace Ion { +namespace Display { + +static KDFrameBuffer sFrameBuffer = KDFrameBuffer(sPixels, KDSize(Ion::Display::Width, Ion::Display::Height)); + +void pushRect(KDRect r, const KDColor * pixels) { + if (sFrameBufferActive) { + Simulator::Main::setNeedsRefresh(); + sFrameBuffer.pushRect(r, pixels); + } +} + +void pushRectUniform(KDRect r, KDColor c) { + if (sFrameBufferActive) { + Simulator::Main::setNeedsRefresh(); + sFrameBuffer.pushRectUniform(r, c); + } +} + +void pullRect(KDRect r, KDColor * pixels) { + if (sFrameBufferActive) { + sFrameBuffer.pullRect(r, pixels); + } +} + +} +} + +namespace Ion { +namespace Simulator { +namespace Framebuffer { + +const KDColor * address() { + return sPixels; +} + +void setActive(bool enabled) { + sFrameBufferActive = enabled; +} + +} +} +} \ No newline at end of file diff --git a/ion/src/simulator/fxcg/framebuffer.h b/ion/src/simulator/fxcg/framebuffer.h new file mode 100644 index 00000000000..dba4dbd3278 --- /dev/null +++ b/ion/src/simulator/fxcg/framebuffer.h @@ -0,0 +1,17 @@ +#ifndef ION_SIMULATOR_FRAMEBUFFER_H +#define ION_SIMULATOR_FRAMEBUFFER_H + +#include + +namespace Ion { +namespace Simulator { +namespace Framebuffer { + +const KDColor * address(); +void setActive(bool enabled); + +} +} +} + +#endif \ No newline at end of file diff --git a/ion/src/simulator/fxcg/keyboard.cpp b/ion/src/simulator/fxcg/keyboard.cpp new file mode 100644 index 00000000000..12fc46ccc7d --- /dev/null +++ b/ion/src/simulator/fxcg/keyboard.cpp @@ -0,0 +1,262 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "keyboard.h" +#include "layout_keyboard.h" +#include "main.h" +#include "menuHandler.h" + + +using namespace Ion::Keyboard; + +class KeyPair { +public: + constexpr KeyPair(Key key, bool numworksShift, bool numworksAlpha, int gintKey, bool gintShift, bool gintAlpha, bool ignoreShiftAlpha = false) : + m_key(key), + m_numworksShift(numworksShift), + m_numworksAlpha(numworksAlpha), + m_gintKey(gintKey), + m_gintShift(gintShift), + m_gintAlpha(gintAlpha), + m_ignoreShiftAlpha(ignoreShiftAlpha) + {} + Key key() const { return m_key; } + bool numworksShift() const { return m_numworksShift; } + bool numworksAlpha() const { return m_numworksAlpha; } + int gintKey() const { return m_gintKey; } + bool gintShift() const { return m_gintShift; } + bool gintAlpha() const { return m_gintAlpha; } + bool ignoreShiftAlpha() const { return m_ignoreShiftAlpha; } +private: + Key m_key; + bool m_numworksShift; + bool m_numworksAlpha; + int m_gintKey; + bool m_gintShift; + bool m_gintAlpha; + bool m_ignoreShiftAlpha; +}; + +constexpr static KeyPair sKeyPairs[] = { + KeyPair(Key::Down, false, false, KEY_DOWN, false, false, true), + KeyPair(Key::Left, false, false, KEY_LEFT, false, false, true), + KeyPair(Key::Right, false, false, KEY_RIGHT, false, false, true), + KeyPair(Key::Up, false, false, KEY_UP, false, false, true), + KeyPair(Key::Back, false, false, KEY_EXIT, false, false), + KeyPair(Key::Home, false, false, KEY_MENU, false, false), + KeyPair(Key::Shift, false, false, KEY_SHIFT, false, false, true), + KeyPair(Key::Alpha, false, false, KEY_ALPHA, false, false, true), + KeyPair(Key::XNT, false, false, KEY_XOT, false, false), + KeyPair(Key::Var, false, false, KEY_VARS, false, false), + KeyPair(Key::Toolbox, false, false, KEY_OPTN, false, false), + KeyPair(Key::Backspace, false, false, KEY_DEL, false, false), + KeyPair(Key::Exp, false, false, KEY_LN, true, false), + KeyPair(Key::Ln, false, false, KEY_LN, false, false), + KeyPair(Key::Log, false, false, KEY_LOG, false, false), + KeyPair(Key::Imaginary, false, false, KEY_0, true, false), + KeyPair(Key::Comma, false, false, KEY_COMMA, false, false), + KeyPair(Key::Power, false, false, KEY_POWER, false, false), + KeyPair(Key::Sine, false, false, KEY_SIN, false, false), + KeyPair(Key::Cosine, false, false, KEY_COS, false, false), + KeyPair(Key::Tangent, false, false, KEY_TAN, false, false), + KeyPair(Key::Pi, false, false, KEY_EXP, true, false), + KeyPair(Key::Sqrt, false, false, KEY_SQUARE, true, false), + KeyPair(Key::Square, false, false, KEY_SQUARE, false, false), + KeyPair(Key::Seven, false, false, KEY_7, false, false), + KeyPair(Key::Eight, false, false, KEY_8, false, false), + KeyPair(Key::Nine, false, false, KEY_9, false, false), + KeyPair(Key::LeftParenthesis, false, false, KEY_LEFTP, false, false), + KeyPair(Key::RightParenthesis, false, false, KEY_RIGHTP, false, false), + KeyPair(Key::Four, false, false, KEY_4, false, false), + KeyPair(Key::Five, false, false, KEY_5, false, false), + KeyPair(Key::Six, false, false, KEY_6, false, false), + KeyPair(Key::Multiplication, false, false, KEY_MUL, false, false), + KeyPair(Key::Division, false, false, KEY_DIV, false, false), + KeyPair(Key::Division, false, false, KEY_FRAC, false, false), + KeyPair(Key::One, false, false, KEY_1, false, false), + KeyPair(Key::Two, false, false, KEY_2, false, false), + KeyPair(Key::Three, false, false, KEY_3, false, false), + KeyPair(Key::Plus, false, false, KEY_ADD, false, false), + KeyPair(Key::Minus, false, false, KEY_SUB, false, false), + KeyPair(Key::Zero, false, false, KEY_0, false, false), + KeyPair(Key::Dot, false, false, KEY_DOT, false, false), + KeyPair(Key::EE, false, false, KEY_EXP, false, false), + KeyPair(Key::Ans, false, false, KEY_NEG, true, false), + KeyPair(Key::EXE, false, false, KEY_EXE, false, false, true), + KeyPair(Key::OnOff, false, false, KEY_ACON, true, false), + + // Cut + // Not assigned + // Copy + KeyPair(Key::Var, true, false, KEY_8, true, false), + // Paste + KeyPair(Key::Toolbox, true, false, KEY_9, true, false), + // Clear + KeyPair(Key::Backspace, true, false, KEY_ACON, false, false), + // [ + KeyPair(Key::Exp, true, false, KEY_ADD, true, false), + // ] + KeyPair(Key::Ln, true, false, KEY_SUB, true, false), + // { + KeyPair(Key::Log, true, false, KEY_MUL, true, false), + // } + KeyPair(Key::Imaginary, true, false, KEY_DIV, true, false), + // _ + KeyPair(Key::Comma, true, false, KEY_NEG, false, false), + // -> + KeyPair(Key::Power, true, false, KEY_STORE, false, false), + // asin + KeyPair(Key::Sine, true, false, KEY_SIN, true, false), + // acos + KeyPair(Key::Cosine, true, false, KEY_COS, true, false), + // atan + KeyPair(Key::Tangent, true, false, KEY_TAN, true, false), + // = + KeyPair(Key::Pi, true, false, KEY_DOT, true, false), + // < + KeyPair(Key::Sqrt, true, false, KEY_F1, false, false), + // > + KeyPair(Key::Square, true, false, KEY_F2, false, false), + + // : + KeyPair(Key::XNT, false, true, KEY_F3, false, false), + // ; + KeyPair(Key::Var, false, true, KEY_F4, false, false), + // " + KeyPair(Key::Toolbox, false, true, KEY_EXP, false, true), + // % + KeyPair(Key::Backspace, false, true, KEY_F5, false, false), + // A + KeyPair(Key::Exp, false, true, KEY_XOT, false, true), + // B + KeyPair(Key::Ln, false, true, KEY_LOG, false, true), + // C + KeyPair(Key::Log, false, true, KEY_LN, false, true), + // D + KeyPair(Key::Imaginary, false, true, KEY_SIN, false, true), + // E + KeyPair(Key::Comma, false, true, KEY_COS, false, true), + // F + KeyPair(Key::Power, false, true, KEY_TAN, false, true), + // G + KeyPair(Key::Sine, false, true, KEY_FRAC, false, true), + // H + KeyPair(Key::Cosine, false, true, KEY_FD, false, true), + // I + KeyPair(Key::Tangent, false, true, KEY_LEFTP, false, true), + // J + KeyPair(Key::Pi, false, true, KEY_RIGHTP, false, true), + // K + KeyPair(Key::Sqrt, false, true, KEY_COMMA, false, true), + // L + KeyPair(Key::Square, false, true, KEY_ARROW, false, true), + // M + KeyPair(Key::Seven, false, true, KEY_7, false, true), + // N + KeyPair(Key::Eight, false, true, KEY_8, false, true), + // O + KeyPair(Key::Nine, false, true, KEY_9, false, true), + // P + KeyPair(Key::LeftParenthesis, false, true, KEY_4, false, true), + // Q + KeyPair(Key::RightParenthesis, false, true, KEY_5, false, true), + // R + KeyPair(Key::Four, false, true, KEY_6, false, true), + // S + KeyPair(Key::Five, false, true, KEY_TIMES, false, true), + // T + KeyPair(Key::Six, false, true, KEY_DIV, false, true), + // U + KeyPair(Key::Multiplication, false, true, KEY_1, false, true), + // V + KeyPair(Key::Division, false, true, KEY_2, false, true), + // W + KeyPair(Key::One, false, true, KEY_3, false, true), + // X + KeyPair(Key::Two, false, true, KEY_PLUS, false, true), + // Y + KeyPair(Key::Three, false, true, KEY_MINUS, false, true), + // Z + KeyPair(Key::Plus, false, true, KEY_0, false, true), + // Space + KeyPair(Key::Minus, false, true, KEY_DOT, false, true), + // ? + KeyPair(Key::Zero, false, true, KEY_F6, true, false), + // ! + KeyPair(Key::Dot, false, true, KEY_F6, false, false), + + // Brightness control shortcut in Upsilon + KeyPair(Key::Plus, true, false, KEY_UP, false, true), + KeyPair(Key::Minus, true, false, KEY_DOWN, false, true), +}; + +constexpr int sNumberOfKeyPairs = sizeof(sKeyPairs)/sizeof(KeyPair); + +namespace Ion { +namespace Keyboard { + +int menuHeldFor = 0; + +State scan() { + State state = 0; + + // Grab this opportunity to refresh the display if needed + Simulator::Main::refresh(); + + clearevents(); + if (keydown(KEY_MENU)) { + state.setKey(Key::Home); + menuHeldFor++; + if (menuHeldFor > 30) { + Simulator::FXCGMenuHandler::openMenu(); + dupdate(); + // Wait until EXE and MENU are released + do { + sleep_ms(10); + clearevents(); + } while (keydown(KEY_EXE) || keydown(KEY_MENU)); + } + } else { + menuHeldFor = 0; + } + + for (int i = 0; i < sNumberOfKeyPairs; i++) { + const KeyPair & keyPair = sKeyPairs[i]; + if (!keyPair.ignoreShiftAlpha() && + (keyPair.gintShift() != Events::isShiftActive() || + keyPair.gintAlpha() != Events::isAlphaActive())) { + continue; + } + if (keydown(keyPair.gintKey())) { + if (!keyPair.ignoreShiftAlpha()) { + state.setSimulatedShift(keyPair.numworksShift() ? ModSimState::ForceOn : ModSimState::ForceOff); + state.setSimulatedAlpha(keyPair.numworksAlpha() ? ModSimState::ForceOn : ModSimState::ForceOff); + } + state.setKey(keyPair.key()); + } + } + + return state; +} + +} +} + +namespace Ion { +namespace Simulator { +namespace Keyboard { + +} +} +} diff --git a/ion/src/simulator/fxcg/keyboard.h b/ion/src/simulator/fxcg/keyboard.h new file mode 100644 index 00000000000..0e379642fb8 --- /dev/null +++ b/ion/src/simulator/fxcg/keyboard.h @@ -0,0 +1,15 @@ +#ifndef ION_SIMULATOR_KEYBOARD_H +#define ION_SIMULATOR_KEYBOARD_H + +#include +// #include + +namespace Ion { +namespace Simulator { +namespace Keyboard { + +} +} +} + +#endif \ No newline at end of file diff --git a/ion/src/simulator/fxcg/main.cpp b/ion/src/simulator/fxcg/main.cpp new file mode 100644 index 00000000000..3add310d4fb --- /dev/null +++ b/ion/src/simulator/fxcg/main.cpp @@ -0,0 +1,164 @@ +#include "main.h" +#include "display.h" +#include "platform.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include "menuHandler.h" + +using namespace Ion::Simulator::Main; + +constexpr static const char * storage_name="nwstore.nws"; + +int save_state(const char * fname); // apps/home/controller.cpp + +extern "C" { + extern const int prizm_heap_size; + const int prizm_heap_size = 192 * 1024; + __attribute__((aligned(4))) char prizm_heap[prizm_heap_size]; + + CalculatorType calculator = CalculatorType::Unchecked; + + int main() { + /* Allow the user to use memory past the 2 MB line on tested OS versions */ + char const * os_version = (char const *)0x80020020; + if (!strncmp(os_version, "03.", 3) && os_version[3] <= '8'){ // 3.80 or earlier + char buf[256]; + strncpy(buf,os_version,8); + buf[8]=0; + } else { + char buf1[10], buf[256]; + // sprintf(buf,"%i",availram); + strncpy(buf1,os_version,8); + buf1[8]=0; + sprintf(buf,"OS %s not checked",buf1); + calculator = CalculatorType::Unchecked; + dclear(C_WHITE); + dtext(1,10,C_BLACK,buf); + dtext(1,27,C_BLACK,"F6: continue anyway"); + dupdate(); + int key = getkey().key; + if (key != KEY_F6){ + Ion::Simulator::FXCGMenuHandler::openMenu(); + } + } + bool is_emulator = *(volatile uint32_t *)0xff000044 == 0x00000000; + uint32_t stack; + __asm__("mov r15, %0" : "=r"(stack)); + // Check if the calculator is a Prizm or an emulator + if (stack < 0x8c000000) { + calculator = is_emulator ? CalculatorType::Emulator : CalculatorType::Other; + } else { + calculator = CalculatorType::Physical; // 0x8c200000, size 0x300000 is free + } + + Ion::Simulator::Main::init(); + ion_main(0, nullptr); + Ion::Simulator::Main::quit(); + + return 0; + } +} + +namespace Ion { +namespace Simulator { +namespace Main { + +static bool sNeedsRefresh = false; + +void init() { + Ion::Simulator::Display::init(); + setNeedsRefresh(); +} + +void setNeedsRefresh() { + sNeedsRefresh = true; +} + +void refresh() { + if (!sNeedsRefresh) { + return; + } + + Display::draw(); + + sNeedsRefresh = false; +} + +void quit() { + Ion::Simulator::Display::quit(); +} + +void EnableStatusArea(int /*opt*/) { + __asm__ __volatile__ ( + ".align 2 \n\t" + "mov.l 2f, r2 \n\t" + "mov.l 1f, r0 \n\t" + "jmp @r2 \n\t" + "nop \n\t" + ".align 2 \n\t" + "1: \n\t" + ".long 0x02B7 \n\t" + ".align 4 \n\t" + "2: \n\t" + ".long 0x80020070 \n\t" + ); +} + +extern "C" void *__GetVRAMAddress(void); + +uint8_t xyram_backup[16 * 1024]; +uint8_t ilram_backup[4 * 1024]; + +void worldSwitchHandler(void (*worldSwitchFunction)(), bool prepareVRAM) { + // Back up XYRAM + uint8_t* xyram = (uint8_t*) 0xe500e000; + memcpy(xyram_backup, xyram, 16 * 1024); + // Back up ILRAM + uint8_t* ilram = (uint8_t*) 0xe5200000; + memcpy(ilram_backup, ilram, 4 * 1024); + + if (prepareVRAM) { + // Copying the screen to the OS's VRAM avoids a flicker when powering on + uint16_t* dst = (uint16_t *) __GetVRAMAddress(); + uint16_t* src = gint_vram + 6; + + for (int y = 0; y < 216; y++, dst += 384, src += 396) { + for (int x = 0; x < 384; x++) { + dst[x] = src[x]; + } + } + + // Disable the status area + EnableStatusArea(3); + } + + worldSwitchFunction(); + + // Restore XYRAM + memcpy(xyram, xyram_backup, 16 * 1024); + // Restore ILRAM + memcpy(ilram, ilram_backup, 4 * 1024); +} + +void runPowerOffSafe(void (*powerOffSafeFunction)(), bool prepareVRAM) { + + gint_world_switch(GINT_CALL(save_state,storage_name)); + + gint_world_switch(GINT_CALL(worldSwitchHandler, powerOffSafeFunction, prepareVRAM)); +} + +} +} +} diff --git a/ion/src/simulator/fxcg/main.h b/ion/src/simulator/fxcg/main.h new file mode 100644 index 00000000000..41807fae825 --- /dev/null +++ b/ion/src/simulator/fxcg/main.h @@ -0,0 +1,29 @@ +#ifndef ION_SIMULATOR_MAIN_H +#define ION_SIMULATOR_MAIN_H +#include + +namespace Ion { +namespace Simulator { +namespace Main { + +void init(); +void quit(); + +void setNeedsRefresh(); +void refresh(); + +void runPowerOffSafe(void (*powerOffSafeFunction)(), bool prepareVRAM); + +enum class CalculatorType : uint8_t { + Unchecked = (uint8_t)-1, + Unknown = 0, + Physical = 1, + Emulator = 2, + Other = 3 +}; + +} +} +} + +#endif \ No newline at end of file diff --git a/ion/src/simulator/fxcg/menuHandler.cpp b/ion/src/simulator/fxcg/menuHandler.cpp new file mode 100644 index 00000000000..51425160904 --- /dev/null +++ b/ion/src/simulator/fxcg/menuHandler.cpp @@ -0,0 +1,97 @@ +#include "main.h" + +namespace Ion { +namespace Simulator { +namespace FXCGMenuHandler { + +int saveAndOpenMainMenu(void) { + int addr; + + // get the address of the syscall table in it + addr = *(unsigned int *)0x8002007C; + + if (addr < (int)0x80020070) + return 1; + if (addr >= (int)0x81000000) + return 1; + + // get the pointer to syscall 1E58 - SwitchToMainMenu + addr += 0x1E58 * 4; + if (addr < (int)0x80020070) + return 1; + if (addr >= (int)0x81000000) + return 1; + + addr = *(unsigned int *)addr; + if (addr < (int)0x80020070) + return 1; + if (addr >= (int)0x81000000) + return 1; + + // Now addr has the address of the first operation in %1e58 + + // Run up to 150 times (300/2). OS 3.60's is 59 instructions, so this should + // be plenty, but will let it stop if nothing is found + for (unsigned short *currentAddr = (unsigned short *)addr; + (unsigned int)currentAddr < ((unsigned int)addr + 300); currentAddr++) { + // MOV.L GetkeyToMainFunctionReturn Flag, r14 + if (*(unsigned char *)currentAddr != 0xDE) + continue; + + // MOV #3, 2 + if (*(currentAddr + 1) != 0xE203) + continue; + + // BSR + if ((*(unsigned char *)(currentAddr + 2) & 0xF0) != 0xB0) + continue; + + // MOV.B r2, @r14 + if (*(currentAddr + 3) != 0x2E20) + continue; + + // BRA + if ((*(unsigned char *)(currentAddr + 4) & 0xF0) != 0xA0) + continue; + + // NOP + if (*(currentAddr + 5) != 0x0009) + continue; + + unsigned short branchInstruction = *(currentAddr + 2); + + // Clear first 4 bits (BSR identifier) + branchInstruction <<= 4; + branchInstruction >>= 4; + + // branchInstruction is now the displacement of BSR + + // Create typedef so we can cast the pointer + typedef void (*voidFunc)(void); + + // JMP to disp*2 + PC + 4 + ((voidFunc)((unsigned int)branchInstruction * 2 + + (unsigned int)currentAddr + 4 + 4))(); + + return 0; + } + + return 1; +} + +extern "C" void gint_osmenu_native(void); + +void openMenuWrapper(void) { + if (saveAndOpenMainMenu() != 0) { + // Fallback + gint_osmenu_native(); + } +} + +void openMenu(void) { + Simulator::Main::runPowerOffSafe(openMenuWrapper, true); +} + +} +} +} diff --git a/ion/src/simulator/fxcg/menuHandler.h b/ion/src/simulator/fxcg/menuHandler.h new file mode 100644 index 00000000000..6c455dfd96e --- /dev/null +++ b/ion/src/simulator/fxcg/menuHandler.h @@ -0,0 +1,14 @@ +#ifndef ION_SIMULATOR_MENUHANDLER_H +#define ION_SIMULATOR_MENUHANDLER_H + +namespace Ion { +namespace Simulator { +namespace FXCGMenuHandler { + +void openMenu(void); + +} +} +} + +#endif \ No newline at end of file diff --git a/ion/src/simulator/fxcg/platform.h b/ion/src/simulator/fxcg/platform.h new file mode 100644 index 00000000000..af5ddfcb60a --- /dev/null +++ b/ion/src/simulator/fxcg/platform.h @@ -0,0 +1,23 @@ +#ifndef ION_SIMULATOR_PLATFORM_H +#define ION_SIMULATOR_PLATFORM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Those functions should be implemented per-platform. + * They are defined as C function for easier interop. */ + +const char * IonSimulatorGetLanguageCode(); + +void IonSimulatorKeyboardKeyDown(int keyNumber); +void IonSimulatorKeyboardKeyUp(int keyNumber); +void IonSimulatorEventsPushEvent(int eventNumber); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/ion/src/simulator/fxcg/power.cpp b/ion/src/simulator/fxcg/power.cpp new file mode 100644 index 00000000000..7375befecad --- /dev/null +++ b/ion/src/simulator/fxcg/power.cpp @@ -0,0 +1,42 @@ +#include +#include +#include + +#include "main.h" + +#include +#include + +void PowerOff(int displayLogo) { + __asm__ __volatile__ ( + ".align 2 \n\t" + "mov.l 2f, r2 \n\t" + "mov.l 1f, r0 \n\t" + "jmp @r2 \n\t" + "nop \n\t" + ".align 2 \n\t" + "1: \n\t" + ".long 0x1839 \n\t" + ".align 4 \n\t" + "2: \n\t" + ".long 0x80020070 \n\t" + ); +} + +void powerOff(void) { + PowerOff(1); +} + +namespace Ion { +namespace Power { + +void suspend(bool checkIfOnOffKeyReleased) { + Simulator::Main::runPowerOffSafe(powerOff, true); +} + +void standby() { + Simulator::Main::runPowerOffSafe(powerOff, true); +} + +} +} diff --git a/ion/src/simulator/fxcg/telemetry_init.cpp b/ion/src/simulator/fxcg/telemetry_init.cpp new file mode 100644 index 00000000000..7a69b2d8c83 --- /dev/null +++ b/ion/src/simulator/fxcg/telemetry_init.cpp @@ -0,0 +1,15 @@ +#include "platform.h" + +namespace Ion { +namespace Simulator { +namespace Telemetry { + +void init() { +} + +void shutdown() { +} + +} +} +} \ No newline at end of file diff --git a/ion/src/simulator/fxcg/timing.cpp b/ion/src/simulator/fxcg/timing.cpp new file mode 100644 index 00000000000..9a6eedfcab7 --- /dev/null +++ b/ion/src/simulator/fxcg/timing.cpp @@ -0,0 +1,27 @@ +#include +#include "main.h" +#include +// #include + +#include + +static auto start = std::chrono::steady_clock::now(); + +namespace Ion { +namespace Timing { + +uint64_t millis() { + auto elapsed = std::chrono::steady_clock::now() - start; + return std::chrono::duration_cast(elapsed).count(); +} + +void usleep(uint32_t us) { + sleep_us(us); +} + +void msleep(uint32_t ms) { + sleep_us(ms * 1000); +} + +} +} \ No newline at end of file diff --git a/ion/src/simulator/ios/Info.plist b/ion/src/simulator/ios/Info.plist index 244f1c4d3a7..c3376034ece 100644 --- a/ion/src/simulator/ios/Info.plist +++ b/ion/src/simulator/ios/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable Epsilon CFBundleIdentifier - io.github.omega.simulator + io.github.upsilon.simulator CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/ion/src/simulator/linux/Makefile b/ion/src/simulator/linux/Makefile index 0a58ad996f8..5042f976c6d 100644 --- a/ion/src/simulator/linux/Makefile +++ b/ion/src/simulator/linux/Makefile @@ -47,5 +47,5 @@ $(eval $(call rule_for, \ $(call object_for,ion/src/simulator/linux/platform_images.cpp): $(BUILD_DIR)/ion/src/simulator/linux/platform_images.h -# The header is refered to as so make sure it's findable this way +# The header is referred to as so make sure it's findable this way $(call object_for,ion/src/simulator/linux/platform_images.cpp): SFLAGS += -I$(BUILD_DIR) diff --git a/ion/src/simulator/nspire/Makefile b/ion/src/simulator/nspire/Makefile new file mode 100644 index 00000000000..3d6d70f7ae9 --- /dev/null +++ b/ion/src/simulator/nspire/Makefile @@ -0,0 +1,53 @@ + +ion_src += $(addprefix ion/src/simulator/nspire/, \ + main.cpp \ + clipboard.cpp \ + display.cpp \ + framebuffer.cpp \ + telemetry_init.cpp \ + keyboard.cpp \ + events_keyboard.cpp \ + events.cpp \ + timing.cpp \ + console.cpp \ + backlight.cpp \ + power.cpp \ + k_csdk.c \ +) + +liba_src += $(addprefix liba/src/, \ + strlcat.c \ + strlcpy.c \ +) + +ion_src += ion/src/shared/collect_registers.cpp + +sdl_simu_needs_to_be_removed += $(addprefix ion/src/simulator/shared/, \ + main.cpp \ + clipboard.cpp \ + display.cpp \ + framebuffer.cpp \ + keyboard.cpp \ + events_keyboard.cpp \ + events_platform.cpp \ + events.cpp \ + layout.cpp \ + actions.cpp \ + window.cpp \ + timing.cpp \ + console.cpp \ +) + +sdl_simu_needs_to_be_removed += $(addprefix ion/src/shared/dummy/, \ + backlight.cpp \ + power.cpp \ +) + +# Remove the dummy diaplay (re-implemented) and the SDL simulator stuff. +ion_src := $(filter-out $(sdl_simu_needs_to_be_removed),$(ion_src)) + +SFLAGS := $(filter-out -Iion/src/simulator/external/sdl/include,$(SFLAGS)) + +SFLAGS += -marm -DNSPIRE_NEWLIB -I/Users/parisse/Ndless-r2015/ndless-sdk/ndless/include -I/Users/parisse/Ndless-r2015/ndless-sdk/toolchain/install/include -fno-strict-aliasing -I. -I.. -fno-exceptions -ffreestanding -nostdlib -fstrict-volatile-bitfields -g -Os +LDFLAGS += -Wl,--nspireio,--gc-sections -L/Users/parisse/Ndless-r2015/ndless-sdk/lib -L/Users/parisse/Ndless-r2015/ndless-sdk/toolchain/install/lib -nostdlib -lc -lm -lstdc++ -lgcc + diff --git a/ion/src/simulator/nspire/assets/icon-sel.png b/ion/src/simulator/nspire/assets/icon-sel.png new file mode 100644 index 00000000000..a016c36b39d Binary files /dev/null and b/ion/src/simulator/nspire/assets/icon-sel.png differ diff --git a/ion/src/simulator/nspire/assets/icon-uns.png b/ion/src/simulator/nspire/assets/icon-uns.png new file mode 100644 index 00000000000..c2f71847222 Binary files /dev/null and b/ion/src/simulator/nspire/assets/icon-uns.png differ diff --git a/ion/src/simulator/nspire/backlight.cpp b/ion/src/simulator/nspire/backlight.cpp new file mode 100644 index 00000000000..5f8575af972 --- /dev/null +++ b/ion/src/simulator/nspire/backlight.cpp @@ -0,0 +1,24 @@ +#include + +namespace Ion { +namespace Backlight { + +uint8_t brightness() { + return 128; +} + +void setBrightness(uint8_t b) { +} + +void init() { +} + +bool isInitialized() { + return true; +} + +void shutdown() { +} + +} +} diff --git a/ion/src/simulator/nspire/clipboard.cpp b/ion/src/simulator/nspire/clipboard.cpp new file mode 100644 index 00000000000..4bbee1fa382 --- /dev/null +++ b/ion/src/simulator/nspire/clipboard.cpp @@ -0,0 +1,15 @@ +#include +#include +#include + +namespace Ion { +namespace Clipboard { + +void write(const char * text) {} + +const char * read() { + return nullptr; +} + +} +} diff --git a/ion/src/simulator/nspire/console.cpp b/ion/src/simulator/nspire/console.cpp new file mode 100644 index 00000000000..d8957c6e6ca --- /dev/null +++ b/ion/src/simulator/nspire/console.cpp @@ -0,0 +1,24 @@ +#include +#include "main.h" +#include +#include + +namespace Ion { +namespace Console { + +char readChar() { + return 0; +} + +void writeChar(char c) { + // fxlibc conflicts with this + #undef putchar + KDIonContext::putchar(c); +} + +bool transmissionDone() { + return true; +} + +} +} diff --git a/ion/src/simulator/nspire/display.cpp b/ion/src/simulator/nspire/display.cpp new file mode 100644 index 00000000000..dbd4a0022b5 --- /dev/null +++ b/ion/src/simulator/nspire/display.cpp @@ -0,0 +1,32 @@ +#include "display.h" +#include "framebuffer.h" +#include +#include +#include +#include + +#include + +#include "k_csdk.h" +#include + +namespace Ion { +namespace Simulator { +namespace Display { + +void init() { +} + +void quit() { +} + +void draw() { + unsigned short * ionFramebuffer = (unsigned short *) Ion::Simulator::Framebuffer::address(); + // we specify the screen fmt here because the "native" fmt varies between calculator revisions + // some default to a 240x320 specification, which results in a 90-degree framebuffer rotation and other terribleness. + lcd_blit(ionFramebuffer,SCR_320x240_565); +} + +} +} +} diff --git a/ion/src/simulator/nspire/display.h b/ion/src/simulator/nspire/display.h new file mode 100644 index 00000000000..ab524fd3c55 --- /dev/null +++ b/ion/src/simulator/nspire/display.h @@ -0,0 +1,19 @@ +#ifndef ION_SIMULATOR_DISPLAY_H +#define ION_SIMULATOR_DISPLAY_H + +#include + +namespace Ion { +namespace Simulator { +namespace Display { + +void init(); +void quit(); + +void draw(); + +} +} +} + +#endif diff --git a/ion/src/simulator/nspire/events.cpp b/ion/src/simulator/nspire/events.cpp new file mode 100644 index 00000000000..9b8d0055751 --- /dev/null +++ b/ion/src/simulator/nspire/events.cpp @@ -0,0 +1,23 @@ +#include "events.h" +#include + +namespace Ion { +namespace Events { + +void didPressNewKey() { +} + +char * sharedExternalTextBuffer() { + static char buffer[sharedExternalTextBufferSize]; + return buffer; +} + +const char * Event::text() const { + if (*this == ExternalText) { + return const_cast(sharedExternalTextBuffer()); + } + return defaultText(); +} + +} +} diff --git a/ion/src/simulator/nspire/events.h b/ion/src/simulator/nspire/events.h new file mode 100644 index 00000000000..b012a43c5e5 --- /dev/null +++ b/ion/src/simulator/nspire/events.h @@ -0,0 +1,24 @@ +#ifndef ION_SIMULATOR_EVENTS_H +#define ION_SIMULATOR_EVENTS_H + +#include + +namespace Ion { +namespace Simulator { +namespace Events { + +void dumpEventCount(int i); +void logAfter(int numberOfEvents); + +} +} + +namespace Events { + +static constexpr int sharedExternalTextBufferSize = 2; +char * sharedExternalTextBuffer(); + +} +} + +#endif diff --git a/ion/src/simulator/nspire/events_keyboard.cpp b/ion/src/simulator/nspire/events_keyboard.cpp new file mode 100644 index 00000000000..615c327d54c --- /dev/null +++ b/ion/src/simulator/nspire/events_keyboard.cpp @@ -0,0 +1,15 @@ +#include + +namespace Ion { +namespace Events { + + +Event getPlatformEvent() { + Event result = None; + + return result; +} + + +} +} diff --git a/ion/src/simulator/nspire/framebuffer.cpp b/ion/src/simulator/nspire/framebuffer.cpp new file mode 100644 index 00000000000..677515ecf63 --- /dev/null +++ b/ion/src/simulator/nspire/framebuffer.cpp @@ -0,0 +1,53 @@ +#include "framebuffer.h" +#include +#include "main.h" +#include + + +static KDColor sPixels[Ion::Display::Width * Ion::Display::Height]; +static_assert(sizeof(KDColor) == sizeof(uint16_t), "KDColor is not 16 bits"); +static bool sFrameBufferActive = true; + +namespace Ion { +namespace Display { + +static KDFrameBuffer sFrameBuffer = KDFrameBuffer(sPixels, KDSize(Ion::Display::Width, Ion::Display::Height)); + +void pushRect(KDRect r, const KDColor * pixels) { + if (sFrameBufferActive) { + Simulator::Main::setNeedsRefresh(); + sFrameBuffer.pushRect(r, pixels); + } +} + +void pushRectUniform(KDRect r, KDColor c) { + if (sFrameBufferActive) { + Simulator::Main::setNeedsRefresh(); + sFrameBuffer.pushRectUniform(r, c); + } +} + +void pullRect(KDRect r, KDColor * pixels) { + if (sFrameBufferActive) { + sFrameBuffer.pullRect(r, pixels); + } +} + +} +} + +namespace Ion { +namespace Simulator { +namespace Framebuffer { + +const KDColor * address() { + return sPixels; +} + +void setActive(bool enabled) { + sFrameBufferActive = enabled; +} + +} +} +} diff --git a/ion/src/simulator/nspire/framebuffer.h b/ion/src/simulator/nspire/framebuffer.h new file mode 100644 index 00000000000..dba4dbd3278 --- /dev/null +++ b/ion/src/simulator/nspire/framebuffer.h @@ -0,0 +1,17 @@ +#ifndef ION_SIMULATOR_FRAMEBUFFER_H +#define ION_SIMULATOR_FRAMEBUFFER_H + +#include + +namespace Ion { +namespace Simulator { +namespace Framebuffer { + +const KDColor * address(); +void setActive(bool enabled); + +} +} +} + +#endif \ No newline at end of file diff --git a/ion/src/simulator/nspire/gint.h b/ion/src/simulator/nspire/gint.h new file mode 100644 index 00000000000..bd8d65a70f9 --- /dev/null +++ b/ion/src/simulator/nspire/gint.h @@ -0,0 +1,61 @@ +#ifndef GINT_H +#define GINT_H +#include "k_defs.h" +#define KEY_DOWN KEY_CTRL_DOWN +#define KEY_LEFT KEY_CTRL_LEFT +#define KEY_RIGHT KEY_CTRL_RIGHT +#define KEY_UP KEY_CTRL_UP +#define KEY_EXIT KEY_CTRL_EXIT +#define KEY_MENU KEY_CTRL_MENU +#define KEY_SHIFT KEY_CTRL_SHIFT +#define KEY_ALPHA KEY_CTRL_ALPHA +#define KEY_XOT KEY_CTRL_XTT +#define KEY_VARS KEY_CTRL_VARS +#define KEY_OPTN KEY_CTRL_OPTN +#define KEY_DEL KEY_CTRL_DEL +#define KEY_OPTN KEY_CTRL_OPTN +#define KEY_DEL KEY_CTRL_DEL +#define KEY_LN KEY_CHAR_LN +#define KEY_LOG KEY_CHAR_LOG +#define KEY_0 KEY_CHAR_0 +#define KEY_COMMA KEY_CHAR_COMMA +#define KEY_SIN KEY_CHAR_SIN +#define KEY_COS KEY_CHAR_COS +#define KEY_TAN KEY_CHAR_TAN +#define KEY_EXP KEY_CHAR_EXP +#define KEY_SQUARE KEY_CHAR_SQUARE +#define KEY_7 KEY_CHAR_7 +#define KEY_8 KEY_CHAR_8 +#define KEY_9 KEY_CHAR_9 +#define KEY_LEFTP KEY_CHAR_LPAR +#define KEY_RIGHTP KEY_CHAR_RPAR +#define KEY_4 KEY_CHAR_4 +#define KEY_5 KEY_CHAR_5 +#define KEY_6 KEY_CHAR_6 +#define KEY_MUL KEY_CHAR_MULT +#define KEY_DIV KEY_CHAR_DIV +#define KEY_FRAC KEY_CHAR_FRAC +#define KEY_1 KEY_CHAR_1 +#define KEY_2 KEY_CHAR_2 +#define KEY_3 KEY_CHAR_3 +#define KEY_ADD KEY_CHAR_PLUS +#define KEY_PLUS KEY_CHAR_PLUS +#define KEY_SUB KEY_CHAR_MINUS +#define KEY_MINUS KEY_CHAR_MINUS +#define KEY_NEG KEY_CHAR_PMINUS +#define KEY_0 KEY_CHAR_0 +#define KEY_DOT KEY_CHAR_DP +#define KEY_EXE KEY_CTRL_EXE +#define KEY_ACON KEY_CTRL_AC +#define KEY_POWER KEY_CHAR_POW +#define KEY_STORE KEY_CHAR_STORE +#define KEY_F1 KEY_CTRL_F1 +#define KEY_F2 KEY_CTRL_F2 +#define KEY_F3 KEY_CTRL_F3 +#define KEY_F4 KEY_CTRL_F4 +#define KEY_F5 KEY_CTRL_F5 +#define KEY_F6 KEY_CTRL_F6 +#define KEY_FD KEY_CTRL_FD +#define KEY_ARROW KEY_CTRL_SD +#define KEY_TIMES KEY_CHAR_MULT +#endif // GINT_H diff --git a/ion/src/simulator/nspire/k_csdk.c b/ion/src/simulator/nspire/k_csdk.c new file mode 100755 index 00000000000..6c1accc8c39 --- /dev/null +++ b/ion/src/simulator/nspire/k_csdk.c @@ -0,0 +1,1557 @@ +// implementation of the minimal C SDK for KhiCAS +int (*shutdown)()=0; + +short shutdown_state=0; +short exam_mode=0,nspire_exam_mode=0; +unsigned exam_start=0; // RTC start +int exam_duration=0; +// <0: indicative duration, ==0 time displayed during exam, >0 end exam_mode after +const int exam_bg1=0x4321,exam_bg2=0x1234; +int exam_bg(){ + return exam_mode?(exam_duration>0?exam_bg1:exam_bg2):0x50719; +} + +void SetQuitHandler( void (*f)(void)){} +#ifdef TICE +int clip_ymin=0; +// TI83 +const int STATUS_AREA_PX=18; +// debug: dbg_printf() Add #include to a source file, and use make debug instead of make to build a debug program. You may need to run make clean beforehand in order to ensure all source files are rebuilt. +// ASM syscalls: https://wikiti.brandonw.net/index.php?title=Category:84PCE:Syscalls:By_Name +// doc: https://ce-programming.github.io/toolchain/index.html +// Makefile options https://ce-programming.github.io/toolchain/static/makefile-options.html +// memory layout: https://ce-programming.github.io/toolchain/static/faq.html +// parameters are in CEdev/meta (and app_tools if present) +// makefile.mk: +// BSSHEAP_LOW ?= D052C6 +// BSSHEAP_HIGH ?= D13FD8 +// STACK_HIGH ?= D1A87E +// INIT_LOC ?= D1A87F +// Can we set STACK_HIGH to another value? I think the global area stack+data could be "reversed", I mean stack top at 0xD2A87F and data(+code+ro_data for RAM programs) at a new position: INIT_LOC=D1987E (maybe +1 or +2) + +// TI stack 4K D1A87Eh: Top of the SPL stack. +// change stack pointer (if STACK_HIGH change does not work) +// requires assembly code (https://0x04.net/~mwk/doc/z80/eZ80.pdf), +// save stack pointer +// LD (Mmn), SP +// set HL to the new stack address (top of the area-3) +// LD SP,HL +// call main +// restore stack pointer +// LD SP,(Mmn) +// 1023 bytes: uint8_t[1023] os_RamCode (do not use if flash write occurs) +// 0xD052C6: 60989 bytes used for bss+heap (temp buffers in TI OS) +// 0xD1A881: Start of UserMem. 64K for code, data, ro data +// size_t os_MemChk(void **free) size and position of free ram area +// Or we could create a VarApp in RAM with no real data inside and use this area for temporary storage. +// 0xD40000: Start of VRAM. 320x240x2 bytes = 153600 bytes. +// half may be used in 8 bits palette mode (graphx) +#include "k_csdk.h" +#include +#include +#include +#include +#include +#include // boot_GetTime(uint8_t *seconds, uint8_t *minutes, uint8_t *hours), boot_SetTime(uint8_t seconds, uint8_t minutes, uint8_t hours) +#include +#include +#include +#include +#include +#define FILENAME_MAXRECORDS 32 +#define FILENAME_MAXSIZE 9 +#define FILE_MAXSIZE 16384 +char os_filenames[FILENAME_MAXRECORDS][FILENAME_MAXSIZE]; + +void sdk_init(){ + dbg_printf("SDK Init\n"); + gfx_Begin(); + unsigned short * addr=gfx_palette; + for (int r=0;r<4;r++){ + for (int g=0;g<8;g++){ + for (int b=0;b<4;b++){ + int R=r*255/3,G=g*255/7,B=b*255/3; + addr[(r<<5)|(g<<2)|b]=gfx_RGBTo1555(R,G,B); + // dbg_printf("palette %i %i %i %i\n",(r<<5)|(g<<2)|b,R,G,B); + } + } + } + // 128-254 arc-en-ciel? 255 should remain white +} + +void sdk_end(){ + dbg_printf("SDK End\n"); + gfx_End(); +} + +void clear_screen(void){ + gfx_FillScreen(255); // gfx_ZeroScreen(void); +} + +int alpha=0,alphalock=0,prevalpha=0,shift=0; +int handle_f5(){ + if (alphalock) + alphalock=3-alphalock; + else + alphalock=2; +} +void dbgprint(int i){ + char buf[16]={0}; + buf[0]='0'+i/100; + buf[1]='0'+(i % 100)/10; + buf[2]='0'+(i % 10); + os_draw_string(20,60,SDK_WHITE,SDK_BLACK,buf,false); +} +int getkey(int allow_suspend){ + sync_screen(); + statusline(0); + for (;;){ + int i=0; + while (!i){ + i=os_GetCSC(); + } + // dbgprint(i); + int decal=(alpha>>1)<<5; // 0 or 32 for upper or lowercase + int Alpha=alpha,Shift=shift; + shift=0; prevalpha=alpha; + if (!alphalock) + alpha=0; + switch (i){ + case sk_Fx: + return Alpha?KEY_CTRL_F11:Shift?KEY_CTRL_F6:KEY_CTRL_F1; + case sk_Fenetre: + return Alpha?KEY_CTRL_F12:Shift?KEY_CTRL_F7:KEY_CTRL_F2; + case sk_Zoom: + return Alpha?KEY_CTRL_F13:Shift?KEY_CTRL_F8:KEY_CTRL_F3; + case sk_Trace: + return Alpha?KEY_CTRL_F14:Shift?KEY_CTRL_F9:KEY_CTRL_F4; + case sk_Graph: + return Alpha?KEY_CTRL_F15:Shift?KEY_CTRL_F10:KEY_CTRL_F5; + case sk_Mode: + return KEY_CTRL_SETUP; + case sk_Del: + return KEY_CTRL_DEL; + case sk_GraphVar: + return KEY_CTRL_XTT; + // sk_Stats + case sk_Right: + return Shift?KEY_SHIFT_RIGHT:KEY_CTRL_RIGHT; + case sk_Left: + return Shift?KEY_SHIFT_LEFT:KEY_CTRL_LEFT; + case sk_Up: + return Shift?KEY_CTRL_PAGEUP:KEY_CTRL_UP; + case sk_Down: + return Shift?KEY_CTRL_PAGEDOWN:KEY_CTRL_DOWN; + case sk_Enter: + return Alpha?KEY_SHIFT_ANS:KEY_CTRL_EXE; + case sk_Alpha: + if (alphalock){ + alpha=alphalock=0; + } + else { + if (Shift) + alphalock=alpha=2; + else { + alpha=2; + if (prevalpha) + alphalock=alpha=prevalpha; + } + } + statusline(0); + continue; + case sk_2nd: + if (alphalock) + alpha=3-alpha; // maj <> min + else + shift=!Shift; + statusline(0); + continue; + case sk_Math: + return Alpha?KEY_CHAR_A+decal:KEY_CTRL_F6; + case sk_Matrice: + return Alpha?KEY_CHAR_B+decal:KEY_CHAR_MAT; + case sk_Prgm: + return Alpha?KEY_CHAR_C+decal:KEY_CTRL_PRGM; + case sk_Vars: + return KEY_CTRL_VARS; + case sk_Annul: + return Shift?KEY_CTRL_AC:KEY_CTRL_EXIT; + case sk_TglExact: + return KEY_CHAR_D+decal; + case sk_Trig: + return Alpha?KEY_CHAR_E+decal:(Shift?KEY_CHAR_PI:KEY_CHAR_SIN); + case sk_Cos: + return Alpha?KEY_CHAR_F+decal:KEY_CHAR_COS; + case sk_Tan: + return Alpha?KEY_CHAR_G+decal:KEY_CHAR_TAN; + case sk_Power: + return Alpha?KEY_CHAR_H+decal:KEY_CHAR_POW; + case sk_Square: + return Alpha?KEY_CHAR_I+decal:Shift?KEY_CHAR_ROOT:KEY_CHAR_SQUARE; + case sk_Comma: + return Alpha?KEY_CHAR_J+decal:Shift?KEY_CHAR_E:KEY_CHAR_COMMA; + case sk_LParen: + return Alpha?KEY_CHAR_K+decal:Shift?KEY_CHAR_LBRACE:KEY_CHAR_LPAR; + case sk_RParen: + return Alpha?KEY_CHAR_L+decal:Shift?KEY_CHAR_RBRACE:KEY_CHAR_RPAR; + case sk_Div: + return Alpha?KEY_CHAR_M+decal:Shift?KEY_CHAR_E+32:KEY_CHAR_DIV; + case sk_Log: + return Alpha?KEY_CHAR_N+decal:Shift?KEY_CHAR_EXPN10:KEY_CHAR_LOG; + case sk_7: + return Alpha?KEY_CHAR_O+decal:KEY_CHAR_7; + case sk_8: + return Alpha?KEY_CHAR_P+decal:KEY_CHAR_8; + case sk_9: + return Alpha?KEY_CHAR_Q+decal:KEY_CHAR_9; + case sk_Mul: + return Alpha?KEY_CHAR_R+decal:Shift?KEY_CHAR_LBRCKT:KEY_CHAR_MULT; + case sk_Ln: + return Alpha?KEY_CHAR_S+decal:Shift?KEY_CHAR_EXP:KEY_CHAR_LN; + case sk_4: + return Alpha?KEY_CHAR_T+decal:KEY_CHAR_4; + case sk_5: + return Alpha?KEY_CHAR_U+decal:KEY_CHAR_5; + case sk_6: + return Alpha?KEY_CHAR_V+decal:KEY_CHAR_6; + case sk_Sub: + return Alpha?KEY_CHAR_W+decal:Shift?KEY_CHAR_RBRCKT:KEY_CHAR_MINUS; + case sk_Store: + return Alpha?KEY_CHAR_X+decal:KEY_CHAR_STORE; + case sk_1: + return Alpha?KEY_CHAR_Y+decal:KEY_CHAR_1; + case sk_2: + return Alpha?KEY_CHAR_Z+decal:KEY_CHAR_2; + case sk_3: + return Alpha?KEY_CHAR_THETA:KEY_CHAR_3; + case sk_Add: + return KEY_CHAR_PLUS; + case sk_0: + return Alpha?KEY_CHAR_SPACE:Shift?KEY_CTRL_CATALOG:KEY_CHAR_0; + case sk_DecPnt: + return Alpha?':':Shift?KEY_CHAR_I+32:KEY_CHAR_DP; + case sk_Chs: + return Alpha?'?':Shift?KEY_CHAR_ANS:KEY_CHAR_PMINUS; + default: + return i; + } + } +} +void GetKey(int * key){ + *key=getkey(0); +} +int iskeydown(int key){ + kb_Scan(); + return kb_IsDown(key); +} + +// if (kb_On) ... +void enable_back_interrupt(){ + kb_EnableOnLatch(); +} +void disable_back_interrupt(){ + kb_DisableOnLatch(); +} +int isalphaactive(){ + return alpha; +} +int alphawasactive(int * key){ + return prevalpha; +} +void lock_alpha(){ + alpha=alphalock=1; +} +void reset_kbd(){ + shift=alpha=alphalock=0; +} +int GetSetupSetting(int k){ + if (k!=0x14) return -1; + if (!alpha) return 0; + if (!alphalock) return alpha==2?8:4; + return alpha==2?0x88:0x84; +} + +void os_wait_1ms(int ms){ + msleep(ms); // delay(ms)? +} +double millis(){ + return rtc_Days*86400.0+rtc_Hours*3600.+rtc_Minutes*60.+rtc_Seconds; +} +int os_set_angle_unit(int mode){ + if (mode) os_ResetFlag(TRIG,DEGREES); else os_SetFlag(TRIG,DEGREES); + return true; +} + +int os_get_angle_unit(){ + int i=os_TestFlag(TRIG,DEGREES); + return i?0:1; +} +int file_exists(const char * filename){ + int h=ti_Open(filename, "r"); + if (!h) + return false; + ti_Close(h); + return true; +} +int erase_file(const char * filename){ + if (!file_exists(filename)) + return false; + ti_Delete(filename); + return true; +} +const char * read_file(const char * filename){ + const char * ext=0; + int l=strlen(filename); + char var[9]={0}; + strncpy(var,filename,8); + for (--l;l>0;--l){ + if (filename[l]=='.'){ + ext=filename+l+1; + if (l<9) + var[l]=0; + break; + } + } + int h=ti_Open(var, "r"); + if (!h) + return 0; + int s=ti_GetSize(h); + if (s>7){ + //unsigned short u; + //ti_Read(&u,1,2,h); + char subtype[8]={0}; + ti_Read(subtype,1,4,h); + if (strncmp(subtype,"PYCD",4)==0 || strncmp(subtype,"XCAS",4)==0){ + unsigned char dx; + ti_Read(&dx,1,1,h); + if (dx!=0){ + // skip desktop filename + char buf[256]={0}; + ti_Read(buf,1,1,dx); + s -= 4+dx; + dbg_printf("subtype=%s filename=%s %i %i\n",subtype,buf,dx,s); + } + else + s -= 4; + } + else + ti_Seek(0,SEEK_SET,h); + } + char * ptr=0; +#if 0 + // Direct access to the data, ptr should not be used if any change to the TI variables occurs, unfortunately there is no 0 at end of string + ptr= ti_GetDataPtr(h); + ti_Close(h); + dbg_printf("data=%x %x %x %x %x %x %x %x\n",ptr[0],ptr[1],ptr[2],ptr[3],ptr[4],ptr[5],ptr[6],ptr[7]); + return ptr; +#endif + // Code requiring a copy + // if it starts with + // char * ptr=(char *) gfx_vram+LCD_WIDTH_PX*LCD_HEIGHT_PX; // pointer in vram buffer + int S=os_MemChk((void **)&ptr); + if (s>=S) + return 0; + S=ti_Read(ptr,1,s,h); + ptr[S]=0; + ti_Close(h); + dbg_printf("data=%s\n",ptr); + return ptr; +} +int write_file(const char * filename,const char * s,int len){ + // find extension + const char * ext=0; + int l=strlen(filename); + char var[9]={0}; + strncpy(var,filename,8); + for (--l;l>0;--l){ + if (filename[l]=='.'){ + ext=filename+l+1; + if (l<9) + var[l]=0; + break; + } + } + int h=ti_Open(var,"w"); + if (!h) return false; + if (ext){ + bool ispy=strncmp(ext,"py",2)==0; + bool isxw=strncmp(ext,"xw",2)==0; + if (ispy || isxw){ + const char * subtype=isxw?"XCAS":"PYCD"; + ti_Write(subtype,strlen(subtype),1,h); + unsigned char dx=strlen(filename)+1; + ti_Write(&dx,1,1,h); + ti_Write(filename,dx-1,1,h); + } + } + int Len=ti_Write(s,1,len,h); + ti_Close(h); + return Len==len; +} + +int os_file_browser(const char ** filenames,int maxrecords,const char * extension,int storage){ + if (maxrecords>FILENAME_MAXRECORDS) + maxrecords=FILENAME_MAXRECORDS; + void * ptr=os_GetSymTablePtr(); + int cur=0; + for (int count=0;cur=FILENAME_MAXSIZE || !dataptr) + continue; + s[l]=0; + dbg_printf("filebrowser %s %i %x %x %x %x %x %x %x %x %x %x %x %x %x\n",s,type,dataptr[0]&0xff,dataptr[1]&0xff,dataptr[2]&0xff,dataptr[3]&0xff,dataptr[4]&0xff,dataptr[5]&0xff,dataptr[6]&0xff,dataptr[7]&0xff,dataptr[8]&0xff,dataptr[9]&0xff,dataptr[10]&0xff,dataptr[11]&0xff,dataptr[12]&0xff); + // if type==21 dataptr[1]*256+dataptr[0]==size, then data + // xcas session begins with 4 bytes size, on the 83 should be 00 00 xx xx + if (type==21 && dataptr[2]==0 && dataptr[3]==0) + ext="xw"; + // python app, starts with 2 bytes size, "PYCD" or "PYSC" + // the script ifself begins at data.begin() + 6 + scriptOffset + // where scriptOffset = dataptr[6] + 1 + if (!ext){ + if (strncmp(&dataptr[2],"PYCD",4)==0 || strncmp(&dataptr[2],"PYSC",4)==0) + ext="py"; + else if (strncmp(&dataptr[2],"XCAS",4)==0) + ext="xw"; + else { // extension from filename _xw or _py or _... + //dbg_printf("os_file_browser %i %i %x\n",type,l,dataptr); + //dbg_printf("filename %i %s\n",count,s); + for (j=l-1;j>0;--j){ + if (s[j]=='_'){ + ext=s+j+1; + break; + } + } + } + } + if (ext && strcmp(ext,extension)==0){ + if (exam_mode && + (strcmp(s,"session")!=0 + ) + ) + continue; + strncpy(os_filenames[cur],s,FILENAME_MAXSIZE); + filenames[cur]=os_filenames[cur]; + dbg_printf("extension match %i %s %s\n",cur,s,filenames[cur]); + ++cur; + } + } + dbg_printf("filebrowser %i\n",cur); + return cur; +} +// gfx_Begin, gfx_SetDrawBuffer(); gfx_End +// GFX_LCD_WIDTH, HEIGHT, gfx_vbuffer=LCD RAM buffer 76800 bytes +// gfx_vram Total of 153600 bytes in size = 320x240x2 +// gfx_SetDrawBuffer()gfx_SetDrawScreen() +// uint8_t gfx_SetColor(uint8_t index) +// gfx_SetPixel(uint24_t x, uint8_t y) +// uint8_t gfx_GetPixel(uint24_t x, uint8_t y) +// gfx_FillRectangle(int x, int y, int width, int height) +// gfx_FillRectangle_NoClip(uint24_t x, uint8_t y, uint24_t width, uint8_t height) +// gfx_Wait(void) +// gfx_PrintStringXY(const char *string, int x, int y) +//gfx_SetTextFGColor(uint8_t color) +// gfx_SetTextScale(uint8_t width_scale, uint8_t height_scale) +// gfx_SetTextConfig +void sync_screen(){ + //gfx_Wait(); + // gfx_BlitBuffer(); // shoud be done if gfx_SetDrawBuffer() is active; +} +int c_rgb565to888(int c){ + c &= 0xffff; + int r=(c>>11)&0x1f,g=(c>>5)&0x3f,b=c&0x1f; + return (r<<19)|(g<<10)|(b<<3); +} + +int convertcolor(int c){ + // convert 16 bits to default palette + c &= 0xffff; + int r=(c>>11)&0x1f,g=(c>>5)&0x3f,b=c&0x1f; + int R = ((r>>3)<<5) | ((g>>3)<<2) | (b>>3); + //dbg_printf("convert %i r=%i g=%i b=%i to %i\n",c,r,g,b,R); + return R; +} +void setcolor(int c){ + gfx_SetColor(convertcolor(c)); + //gfx_SetTextTransparentColor(0); +} +void os_set_pixel(int x,int y,int c){ + setcolor(c); + gfx_SetPixel(x,y); +} +void os_fill_rect(int x,int y,int w,int h,int c){ + setcolor(c); + gfx_FillRectangle(x,y,w,h); +} +int os_get_pixel(int x,int y){ + return gfx_GetPixel(x,y); +} + +// FIXME? use gfx_SetTransparentColor with a value != FG and BG instead of fill rectangle +int os_draw_string_small(int x,int y,int c,int bg,const char * s,int fake){ + y+=STATUS_AREA_PX; + gfx_SetTextScale(1,1); + int dx=gfx_GetStringWidth(s); + if (!fake){ + gfx_SetColor(bg); + gfx_FillRectangle(x,y,dx,8); + int c_=gfx_SetTextFGColor(c); + int bg_=gfx_SetTextBGColor(bg); + gfx_PrintStringXY(s,x,y); + gfx_SetTextFGColor(c_); + gfx_SetTextBGColor(bg_); + } + return x+dx; +} +int os_draw_string_medium(int x,int y,int c,int bg,const char * s,int fake){ + y+=STATUS_AREA_PX; + gfx_SetTextScale(1,2); + //gfx_SetFontHeight(12); + int dx=gfx_GetStringWidth(s); + if (!fake){ + gfx_SetColor(bg); + gfx_FillRectangle(x,y,dx,16); + int c_=gfx_SetTextFGColor(c); + int bg_=gfx_SetTextBGColor(bg); + gfx_PrintStringXY(s,x,y); + gfx_SetTextFGColor(c_); + gfx_SetTextBGColor(bg_); + } + return x+dx; +} +int os_draw_string(int x,int y,int c,int bg,const char * s,int fake){ + y+=STATUS_AREA_PX; + gfx_SetTextScale(2,2); + int dx=gfx_GetStringWidth(s); + if (!fake){ + gfx_SetColor(bg); + gfx_FillRectangle(x,y,dx,16); + int c_=gfx_SetTextFGColor(c); + int bg_=gfx_SetTextBGColor(bg); + gfx_PrintStringXY(s,x,y); + gfx_SetTextFGColor(c_); + gfx_SetTextBGColor(bg_); + } + return x+dx; +} + +const int statuscolor=12345; +void statuslinemsg(const char * msg){ + os_draw_string(0,-STATUS_AREA_PX,statuscolor,SDK_BLACK,msg,false); +} + +void set_time(int h,int m){ + rtc_Set(rtc_Seconds,m,h,rtc_Days); +} + +void get_time(int *h,int *m){ + *h=rtc_Hours; + *m=rtc_Minutes; +} + +void display_time(){ + int h=rtc_Hours,m=rtc_Minutes; + char msg[10]; + msg[0]=' '; + msg[1]='0'+(h/10); + msg[2]='0'+(h%10); + msg[3]= 'h'; + msg[4]= ('0'+(m/10)); + msg[5]= ('0'+(m%10)); + msg[6]=0; + //msg[6]= 'm'; + //msg[7] = ('0'+(s/10)); + //msg[8] = ('0'+(s%10)); + //msg[9]=0; + os_fill_rect(270,0,LCD_WIDTH_PX-270,15,SDK_BLACK); + os_draw_string_medium(270,-STATUS_AREA_PX,statuscolor,SDK_BLACK,msg,false); +} + +void statusflags(){ + char *msg=0; + if (alpha==2){ + msg=alphalock?"alock":"alpha"; + } + else if (alpha==1){ + msg=alphalock?"ALOCK":"ALPHA"; + } + else { + if (shift) + msg="2nd"; + else + msg=""; + } + os_fill_rect(0,0,LCD_WIDTH_PX,16,SDK_BLACK); + os_draw_string_medium(225,-STATUS_AREA_PX,statuscolor,SDK_BLACK,msg,false); + os_draw_string_medium(160,-STATUS_AREA_PX,statuscolor,SDK_BLACK,os_get_angle_unit()?" rad ":" deg ",false); + display_time(); +} +void statusline(int mode){ + statusflags(); + if (mode==0) + os_draw_string_medium(190,-STATUS_AREA_PX,statuscolor,SDK_BLACK," CAS ",false); + if (mode==0) + return; + sync_screen(); +} +#endif + +#ifdef NSPIRE_NEWLIB +// NB changes for the nspire cx ii +// on_key_pressed() should be modified (returns always true) +// https://hackspire.org/index.php?title=Memory-mapped_I/O_ports_on_CX_II#90140000_-_Power_management +// cx ii power management 0x90140000, +// cx 900B0018 (R/W), 900B0020 (?) +// cx ii 90140050 (R/W): Disable bus access to peripherals. Reads will just return the last word read from anywhere in the address range, and writes will be ignored. +// cx 900F0020 (R/W): LCD contrast/backlight. Valid range for contrast: 0x11a to 0x1ce; normal value is 0x174. However, it can range from 0x100 (backlight off) to about 0x1d0 (about max brightness). +// -> cx ii The OS controls the LCD backlight by writing to 90130018. +#include +#include "os.h" // Ndless/ndless-sdk/include/os.h +#include +#include +#include +#include +#include "k_defs.h" + +void sdk_init(void){ + lcd_init(lcd_type()); +} + +void sdk_end(void){ + lcd_init(SCR_TYPE_INVALID); + refresh_osscr(); +} + +int c_rgb565to888(int c){ + c &= 0xffff; + int r=(c>>11)&0x1f,g=(c>>5)&0x3f,b=c&0x1f; + return (r<<19)|(g<<10)|(b<<3); +} + +const int nspire_statusarea=18; +int nspireemu=false; + +int waitforvblank(){ + return 0; +} + +int back_key_pressed(){ + return isKeyPressed(KEY_NSPIRE_DEL); +} +// next 3 functions may be void if not inside a window class hierarchy +void os_show_graph(){} // show graph inside Python shell (Numworks), not used +void os_hide_graph(){} // hide graph, not used anymore +void os_redraw(){} // force redraw of window class hierarchy + +int os_set_angle_unit(int mode){ + return false; +} +int os_get_angle_unit(){ + return 0; +} + +double millis(){ + unsigned NSPIRE_RTC_ADDR=0x90090000; + unsigned t1= * (volatile unsigned *) NSPIRE_RTC_ADDR; + return 1000.0*t1; +} + + +void get_hms(int *h,int *m,int *s){ + unsigned NSPIRE_RTC_ADDR=0x90090000; + unsigned t1= * (volatile unsigned *) NSPIRE_RTC_ADDR; + if (exam_mode){ + unsigned t=t1-exam_start; + if (exam_duration>0 && t>exam_duration){ + ;//set_exam_mode(0); + } + else { + if (exam_duration>0) + t1=exam_duration-t; + else { + if (exam_duration<0 && t<-exam_duration) + t1=-exam_duration-t; + } + } + } + unsigned d=t1/86400; + *s=t1%86400; + *h=*s/3600; + *m=(*s-3600* *h)/60; + *s%=60; +} + +void get_time(int *h,int *m){ + int s; + get_hms(h,m,&s); +} + +void set_time(int h,int m){ + // FIXME +} + +#ifndef is_cx2 +#define is_cx2 false +#endif + +double loopsleep(int ms){ + double n=ms*(is_cx2?3000:1000),j=0.0; + for (double i=0;iNSPIRE_FILEBUFFER-1){ + fclose(f); + return 0; + } + for (int i=0;iFILENAME_MAXRECORDS-1) + maxrecords=FILENAME_MAXRECORDS-1; + dp = opendir ("."); + if (dp == NULL){ + filenames[0]=0; + return 0; + } + int cur=0; + while ( (ep = readdir (dp)) && curd_name,*ext=0; + int l=strlen(s_),j; + char s[l+1]; + strcpy(s,s_); + for (j=l-1;j>0;--j){ + if (s[j]=='.'){ + ext=s+j+1; + break; + } + } + if (ext && strcmp(ext,"tns")==0){ + s[j]=0; + for (;j>0;--j){ + if (s[j]=='.'){ + ext=s+j+1; + break; + } + } + } + if (ext && strcmp(ext,extension)==0){ + if (exam_mode && + (strcmp(s_,"session.xw")!=0 && + strcmp(s_,"session.xw.tns")!=0 && + strcmp(s_,"session.py")!=0 && + strcmp(s_,"session.py.tns")!=0 + ) + ) + continue; + strncpy(os_filenames[cur],s_,FILENAME_MAXSIZE); + filenames[cur]=os_filenames[cur]; + ++cur; + } + } + closedir (dp); + filenames[cur]=NULL; +#if 0 + qsort(filenames,cur,sizeof(char *),c_trialpha); +#else + // qsort would be faster for large n, but here n0){ + finished=false; + const char * tmp=filenames[i-1]; + filenames[i-1]=filenames[i]; + filenames[i]=tmp; + } + } + if (finished) + break; + } +#endif + return cur; +} + +Gc nspire_gc=0; + +void reset_gc(){ + if (nspire_gc){ + gui_gc_finish(nspire_gc); + //gui_gc_free(nspire_gc); + } + nspire_gc=0; +} + +Gc * get_gc(){ + if (!nspire_gc){ + nspire_gc=gui_gc_global_GC(); + gui_gc_setRegion(nspire_gc, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + gui_gc_begin(nspire_gc); + } + return &nspire_gc; +} + +void os_set_pixel(int x,int y,int c){ + get_gc(); + gui_gc_setColor(nspire_gc,c_rgb565to888(c)); + gui_gc_drawRect(nspire_gc,x,y+nspire_statusarea,0,0); +} + +void os_fill_rect(int x,int y,int w,int h,int c){ + get_gc(); + gui_gc_setColor(nspire_gc,c_rgb565to888(c)); + gui_gc_fillRect(nspire_gc,x,y+nspire_statusarea,w,h); +} + +int os_get_pixel(int x,int y){ + if (x<0 || x>=SCREEN_WIDTH || y<0 || y>=SCREEN_HEIGHT) + return -1; +#if 1 + get_gc(); + char ** off_buff = ((((char *****)nspire_gc)[9])[0])[0x8]; + int res = *(unsigned short *) (off_buff[y+nspire_statusarea] + 2*x); + return res; +#else + unsigned short * addr=*(unsigned short **) 0xC0000010; + int r=addr[(y+nspire_statusarea)*SCREEN_WIDTH+x]; + return r; +#endif +} + +int nspire_draw_string(int x,int y,int c,int bg,int f,const char * s,int fake){ + // void ascii2utf16(void *buf, const char *str, int max_size): converts the UTF-8 string str to the UTF-16 string buf of size max_size. + int l=strlen(s); + char utf16[2*l+2]; + ascii2utf16(utf16,s,l); + utf16[2*l]=0; + utf16[2*l+1]=0; + get_gc(); + gui_gc_setFont(nspire_gc,f); + int dx=gui_gc_getStringWidth(nspire_gc, f, utf16, 0, l) ; + if (fake) + return x+dx; + int dy=17; + if (f==Regular9) + dy=13; + if (f==Regular11) + dy=16; + gui_gc_setColor(nspire_gc,c_rgb565to888(bg)); + gui_gc_fillRect(nspire_gc,x,y,dx,dy); + gui_gc_setColor(nspire_gc,c_rgb565to888(c)); + //gui_gc_setPen(nspire_gc, GC_PS_MEDIUM, GC_PM_SMOOTH); + gui_gc_drawString(nspire_gc, utf16, x, y-1, GC_SM_NORMAL | GC_SM_TOP); // normal mode + return x+dx; +} + +int os_draw_string(int x,int y,int c,int bg,const char * s,int fake){ + get_gc(); + gui_gc_clipRect(nspire_gc,0,nspire_statusarea,SCREEN_WIDTH,SCREEN_HEIGHT-nspire_statusarea,0); + int i=nspire_draw_string(x,y+nspire_statusarea,c,bg,Regular12,s,fake); + gui_gc_clipRect(nspire_gc,0,0,SCREEN_WIDTH,SCREEN_HEIGHT,GC_CRO_RESET); + return i; +} +int os_draw_string_small(int x,int y,int c,int bg,const char * s,int fake){ + get_gc(); + gui_gc_clipRect(nspire_gc,0,nspire_statusarea,SCREEN_WIDTH,SCREEN_HEIGHT-nspire_statusarea,GC_CRO_SET); + int i=nspire_draw_string(x,y+nspire_statusarea,c,bg,Regular9,s,fake); + gui_gc_clipRect(nspire_gc,0,0,SCREEN_WIDTH,SCREEN_HEIGHT,GC_CRO_RESET); + return i; +} + +int os_draw_string_medium(int x,int y,int c,int bg,const char * s,int fake){ + get_gc(); + gui_gc_clipRect(nspire_gc,0,nspire_statusarea,SCREEN_WIDTH,SCREEN_HEIGHT-nspire_statusarea,GC_CRO_SET); + int i=nspire_draw_string(x,y+nspire_statusarea,c,bg,Regular11,s,fake); + gui_gc_clipRect(nspire_gc,0,0,SCREEN_WIDTH,SCREEN_HEIGHT,GC_CRO_RESET); + return i; +} + +void statuslinemsg(const char * msg){ + get_gc(); + int bg=exam_bg(); + gui_gc_setColor(nspire_gc,c_rgb565to888(bg)); + gui_gc_fillRect(nspire_gc,0,0,SCREEN_WIDTH,nspire_statusarea); + nspire_draw_string(0,0,exam_mode?0xffff:0,bg,Regular9,msg,false); + if (nspireemu) + nspire_draw_string(190,0,exam_mode?0xffff:0,bg,Regular9," emu ",false); + else + nspire_draw_string(190,0,exam_mode?0xffff:0,bg,Regular9," CAS ",false); +} + +void display_time(){ + int h,m,s; + get_hms(&h,&m,&s); + char msg[10]; + msg[0]=' '; + msg[1]='0'+(h/10); + msg[2]='0'+(h%10); + msg[3]= 'h'; + msg[4]= ('0'+(m/10)); + msg[5]= ('0'+(m%10)); + msg[6]=0; + //msg[6]= 'm'; + //msg[7] = ('0'+(s/10)); + //msg[8] = ('0'+(s%10)); + //msg[9]=0; + int bg=exam_bg(); + gui_gc_setColor(nspire_gc,c_rgb565to888(bg)); + gui_gc_fillRect(nspire_gc,270,0,SCREEN_WIDTH-270,nspire_statusarea); + nspire_draw_string(270,0,exam_mode?0xffff:0,bg,Regular9,msg,false); +} + +void sync_screen(){ + get_gc(); + //gui_gc_finish(nspire_gc); + gui_gc_blit_to_screen(nspire_gc); + ck_msleep(10); + //nspire_gc=0; + // gui_gc_begin(nspire_gc); +} + +// Nspire peripheral reset : +// https://github.com/nDroidProject/nDroid-bootloader/blob/master/kernel.c +// https://hackspire.org/index.php?title=Memory-mapped_I/O_ports_on_CX#CC000000_-_SHA-256_hash_generator +// hardware ports +// https://hackspire.org/index.php?title=Memory-mapped_I/O_ports_on_CX + +int nspire_shift=0; +int nspire_ctrl=0; +int nspire_select=false; +void statusline(int mode){ + return; //this is broken for our purposes and it honestly doesn't matter enough to fix. + char *msg=0; + if (nspire_ctrl){ + if (nspire_shift) + msg="shift ctrl"; + else + msg=" ctrl"; + } + else { + if (nspire_shift) + msg="shift"; + else + msg=""; + } + int bg=exam_bg(); + gui_gc_setColor(nspire_gc,c_rgb565to888(bg)); + gui_gc_fillRect(nspire_gc,210,0,SCREEN_WIDTH-210,nspire_statusarea); + nspire_draw_string(224,0,exam_mode?0xffff:0,bg,Regular9,msg,false); + if (nspireemu) + nspire_draw_string(190,0,exam_mode?0xffff:0,bg,Regular9," emu ",false); + else + nspire_draw_string(190,0,0xf800,bg,Regular9," CAS ",false); + display_time(); + if (mode==0) + return; + sync_screen(); +} + + +#define SHIFTCTRL(x, y, z) (nspire_ctrl ? (z) : nspire_shift ? (y) : (x)) +#define SHIFT(x, y) SHIFTCTRL(x, y, x) +#define CTRL(x, y) SHIFTCTRL(x, x, y) +#define NORMAL(x) SHIFTCTRL(x, x, x) + +int nspire_scan(int * adaptive_cursor_state){ + if (isKeyPressed(KEY_NSPIRE_CTRL)){ + while (isKeyPressed(KEY_NSPIRE_CTRL)) + ; + nspire_ctrl=!nspire_ctrl; + nspire_shift=0; + statusline(1); + return -2; + } + if (isKeyPressed(KEY_NSPIRE_SHIFT)){ + while (isKeyPressed(KEY_NSPIRE_SHIFT)) + ; + nspire_shift=!nspire_shift; + nspire_ctrl=0; + statusline(1); + return -1; + } + *adaptive_cursor_state = SHIFTCTRL(0, 1, 4); + if (isKeyPressed(KEY_NSPIRE_LEFT)|| isKeyPressed(KEY_NSPIRE_LEFTUP) || isKeyPressed(KEY_NSPIRE_DOWNLEFT)) + return SHIFTCTRL(KEY_CTRL_LEFT,KEY_SHIFT_LEFT,KEY_LEFT_CTRL); + if (isKeyPressed(KEY_NSPIRE_RIGHT)|| isKeyPressed(KEY_NSPIRE_UPRIGHT) || isKeyPressed(KEY_NSPIRE_RIGHTDOWN)) + return SHIFTCTRL(KEY_CTRL_RIGHT,KEY_SHIFT_RIGHT,KEY_RIGHT_CTRL); + if (isKeyPressed(KEY_NSPIRE_UP)) + return SHIFTCTRL(KEY_CTRL_UP,KEY_CTRL_PAGEUP,KEY_UP_CTRL); + if (isKeyPressed(KEY_NSPIRE_DOWN)) + return SHIFTCTRL(KEY_CTRL_DOWN,KEY_CTRL_PAGEDOWN,KEY_DOWN_CTRL); + + if (isKeyPressed(KEY_NSPIRE_ESC)) return KEY_CTRL_EXIT ; + if (isKeyPressed(KEY_NSPIRE_HOME)) return SHIFTCTRL(KEY_CTRL_MENU,KEY_CTRL_MENU,KEY_CTRL_AC) ; + if (isKeyPressed(KEY_NSPIRE_MENU)) return KEY_CTRL_CATALOG ; + + // Characters + if (isKeyPressed(KEY_NSPIRE_A)) return SHIFTCTRL('a','A',KEY_CTRL_A); + if (isKeyPressed(KEY_NSPIRE_B)) return SHIFTCTRL('b','B',KEY_BOOK); + if (isKeyPressed(KEY_NSPIRE_C)) return SHIFTCTRL('c','C',KEY_CTRL_CLIP); + if (isKeyPressed(KEY_NSPIRE_D)) return SHIFTCTRL('d','D',KEY_CTRL_D); + if (isKeyPressed(KEY_NSPIRE_E)) return SHIFTCTRL('e','E',KEY_CTRL_F10); + if (isKeyPressed(KEY_NSPIRE_F)) return SHIFTCTRL('f','F',KEY_CTRL_F11); + if (isKeyPressed(KEY_NSPIRE_G)) return SHIFTCTRL('g','G',KEY_CTRL_F12); + if (isKeyPressed(KEY_NSPIRE_H)) return SHIFTCTRL('h','H',KEY_CTRL_F13); + if (isKeyPressed(KEY_NSPIRE_I)) return SHIFTCTRL('i','I',KEY_CTRL_F14); + if (isKeyPressed(KEY_NSPIRE_J)) return SHIFTCTRL('j','J',KEY_CTRL_F15); + if (isKeyPressed(KEY_NSPIRE_K)) return SHIFTCTRL('k','K',KEY_CTRL_AC); + if (isKeyPressed(KEY_NSPIRE_L)) return SHIFTCTRL('l','L',KEY_CTRL_F14); + if (isKeyPressed(KEY_NSPIRE_M)) return SHIFTCTRL('m','M',KEY_CTRL_CATALOG); + if (isKeyPressed(KEY_NSPIRE_N)) return SHIFTCTRL('n','N',KEY_CTRL_N); + if (isKeyPressed(KEY_NSPIRE_O)) return SHIFTCTRL('o','O',KEY_SHIFT_OPTN); + if (isKeyPressed(KEY_NSPIRE_P)) return SHIFTCTRL('p','P',KEY_CTRL_PRGM); + if (isKeyPressed(KEY_NSPIRE_Q)) return SHIFT('q','Q'); + if (isKeyPressed(KEY_NSPIRE_R)) return SHIFTCTRL('r','R',KEY_CTRL_R); + if (isKeyPressed(KEY_NSPIRE_S)) return SHIFTCTRL('s','S',KEY_CTRL_S); + if (isKeyPressed(KEY_NSPIRE_T)) return SHIFTCTRL('t','T',KEY_CTRL_T); + if (isKeyPressed(KEY_NSPIRE_U)) return SHIFTCTRL('u','U',KEY_CTRL_F13); + if (isKeyPressed(KEY_NSPIRE_V)) return SHIFTCTRL('v','V',KEY_CTRL_PASTE); + if (isKeyPressed(KEY_NSPIRE_W)) return SHIFT('w','W'); + if (isKeyPressed(KEY_NSPIRE_X)) return SHIFTCTRL('x','X',KEY_CTRL_CUT); + if (isKeyPressed(KEY_NSPIRE_Y)) return SHIFT('y','Y'); + if (isKeyPressed(KEY_NSPIRE_Z)) return SHIFTCTRL('z','Z',KEY_CTRL_UNDO); + + // Numbers + if (nspireemu){ // for firebird, redefine ctrl + if (isKeyPressed(KEY_NSPIRE_0)) return SHIFTCTRL('0',KEY_CTRL_F10,')'); + if (isKeyPressed(KEY_NSPIRE_1)) return SHIFTCTRL('1',KEY_CTRL_F1,'!'); + if (isKeyPressed(KEY_NSPIRE_2)) return SHIFTCTRL('2',KEY_CTRL_F2,'@'); + if (isKeyPressed(KEY_NSPIRE_3)) return SHIFTCTRL('3',KEY_CTRL_F3,'#'); + if (isKeyPressed(KEY_NSPIRE_4)) return SHIFTCTRL('4',KEY_CTRL_F4,'$'); + if (isKeyPressed(KEY_NSPIRE_5)) return SHIFTCTRL('5',KEY_CTRL_F5,'%'); + if (isKeyPressed(KEY_NSPIRE_6)) return SHIFTCTRL('6',KEY_CTRL_F6,'^'); + if (isKeyPressed(KEY_NSPIRE_7)) return SHIFTCTRL('7',KEY_CTRL_F7,'&'); + if (isKeyPressed(KEY_NSPIRE_8)) return SHIFTCTRL('8',KEY_CTRL_F8,'*'); + if (isKeyPressed(KEY_NSPIRE_9)) return SHIFTCTRL('9',KEY_CTRL_F9,'('); + } + else { + if (isKeyPressed(KEY_NSPIRE_0)) return SHIFTCTRL('0',KEY_CTRL_F10,KEY_CTRL_F10); + if (isKeyPressed(KEY_NSPIRE_1)) return SHIFTCTRL('1',KEY_CTRL_F1,KEY_CTRL_F1); + if (isKeyPressed(KEY_NSPIRE_2)) return SHIFTCTRL('2',KEY_CTRL_F2,KEY_CTRL_F2); + if (isKeyPressed(KEY_NSPIRE_3)) return SHIFTCTRL('3',KEY_CTRL_F3,KEY_CTRL_F3); + if (isKeyPressed(KEY_NSPIRE_4)) return SHIFTCTRL('4',KEY_CTRL_F4,KEY_CTRL_F4); + if (isKeyPressed(KEY_NSPIRE_5)) return SHIFTCTRL('5',KEY_CTRL_F5,KEY_CTRL_F5); + if (isKeyPressed(KEY_NSPIRE_6)) return SHIFTCTRL('6',KEY_CTRL_F6,KEY_CTRL_F6); + if (isKeyPressed(KEY_NSPIRE_7)) return SHIFTCTRL('7',KEY_CTRL_F7,KEY_CTRL_F7); + if (isKeyPressed(KEY_NSPIRE_8)) return SHIFTCTRL('8',KEY_CTRL_F8,KEY_CTRL_F8); + if (isKeyPressed(KEY_NSPIRE_9)) return SHIFTCTRL('9',KEY_CTRL_F9,KEY_CTRL_F9); + } + + // Symbols + if (isKeyPressed(KEY_NSPIRE_FRAC)) return SHIFTCTRL(KEY_EQW_TEMPLATE,KEY_AFFECT,KEY_AFFECT); + if (isKeyPressed(KEY_NSPIRE_SQU)) return CTRL(KEY_CHAR_SQUARE,KEY_CHAR_ROOT); + if (isKeyPressed(KEY_NSPIRE_TENX)) return CTRL(KEY_CHAR_EXPN10,KEY_CHAR_LOG); + if (isKeyPressed(KEY_NSPIRE_eEXP)) return CTRL(KEY_CHAR_EXPN,KEY_CHAR_LN); + if (isKeyPressed(KEY_NSPIRE_COMMA)) return SHIFTCTRL(',',';',':'); + if (isKeyPressed(KEY_NSPIRE_PERIOD)) return SHIFTCTRL('.',KEY_CTRL_F11,':'); + if (isKeyPressed(KEY_NSPIRE_COLON)) return NORMAL(':'); + if (isKeyPressed(KEY_NSPIRE_LP)) return SHIFTCTRL('(',KEY_CTRL_F13,KEY_CHAR_CROCHETS); + if (isKeyPressed(KEY_NSPIRE_RP)) return SHIFTCTRL(')',KEY_CTRL_F14,KEY_CHAR_ACCOLADES); + if (isKeyPressed(KEY_NSPIRE_SPACE)) return SHIFTCTRL(' ','_','_'); + if (isKeyPressed(KEY_NSPIRE_DIVIDE)) + return SHIFTCTRL('/','%','\\'); + if (isKeyPressed(KEY_NSPIRE_MULTIPLY)) return SHIFTCTRL('*','\'','\"'); + if (isKeyPressed(KEY_NSPIRE_MINUS)) return SHIFTCTRL('-','_', '<'); + if (isKeyPressed(KEY_NSPIRE_NEGATIVE)) return SHIFTCTRL('-',KEY_CTRL_F12,KEY_CHAR_ANS); + if (isKeyPressed(KEY_NSPIRE_PLUS)) return SHIFTCTRL('+', KEY_CHAR_NORMAL,'>'); + if (isKeyPressed(KEY_NSPIRE_EQU)) return SHIFTCTRL('=', '|',KEY_CHAR_STORE); + if (isKeyPressed(KEY_NSPIRE_LTHAN)) return NORMAL('<'); + if (isKeyPressed(KEY_NSPIRE_GTHAN)) return NORMAL('>'); + if (isKeyPressed(KEY_NSPIRE_QUOTE)) return NORMAL('\"'); + if (isKeyPressed(KEY_NSPIRE_APOSTROPHE)) return NORMAL('\''); + if (isKeyPressed(KEY_NSPIRE_QUES)) return SHIFTCTRL('?','|','!'); + if (isKeyPressed(KEY_NSPIRE_QUESEXCL)) return SHIFTCTRL('?','|','!'); + if (isKeyPressed(KEY_NSPIRE_BAR)) return NORMAL('|'); + if (isKeyPressed(KEY_NSPIRE_EXP)) return SHIFT('^',KEY_CHAR_RECIP); + if (isKeyPressed(KEY_NSPIRE_EE)) return SHIFTCTRL('%','&', '@'); + if (isKeyPressed(KEY_NSPIRE_PI)) return KEY_CHAR_PI; + if (isKeyPressed(KEY_NSPIRE_FLAG)) return SHIFTCTRL(';',':',KEY_CHAR_IMGNRY); + if (isKeyPressed(KEY_NSPIRE_ENTER)) return SHIFTCTRL(KEY_CTRL_OK,'~',KEY_CTRL_EXE); + if (isKeyPressed(KEY_NSPIRE_TRIG)) return SHIFTCTRL(KEY_CHAR_SIN,KEY_CHAR_COS,KEY_CHAR_TAN); + + // Special chars + if (isKeyPressed(KEY_NSPIRE_SCRATCHPAD)) return SHIFTCTRL(KEY_CTRL_SETUP,KEY_LOAD,KEY_SAVE); + if (isKeyPressed(KEY_NSPIRE_VAR)) return SHIFTCTRL(KEY_CTRL_VARS,KEY_CHAR_FACTOR,KEY_CHAR_STORE); + if (isKeyPressed(KEY_NSPIRE_DOC)) return SHIFT(KEY_CTRL_MENU,KEY_CTRL_SD); + if (isKeyPressed(KEY_NSPIRE_CAT)) return KEY_BOOK; + if (isKeyPressed(KEY_NSPIRE_DEL)) return SHIFTCTRL(KEY_CTRL_DEL,KEY_CTRL_DEL,KEY_CTRL_AC); + if (isKeyPressed(KEY_NSPIRE_RET)) return KEY_CTRL_EXE; + if (isKeyPressed(KEY_NSPIRE_TAB)) return '\t'; + + return 0; +} + +int ascii_get(int* adaptive_cursor_state){ + int res=nspire_scan(adaptive_cursor_state);; + return res; +} + +int handle_f5(){return 0;} +int iskeydown(int key){ + t_key t=KEY_NSPIRE_SPACE; + switch (key){ + case 0: + t=KEY_NSPIRE_LEFT; + break; + case 1: + t=KEY_NSPIRE_UP; + break; + case 2: + t=KEY_NSPIRE_DOWN; + break; + case 3: + t=KEY_NSPIRE_RIGHT; + break; + case 4: + t=KEY_NSPIRE_ENTER; + break; + case 5: + t=KEY_NSPIRE_ESC; + break; + case 6: + t=KEY_NSPIRE_HOME; + break; + case 7: + t=KEY_NSPIRE_MENU; + break; + case 12: + t=KEY_NSPIRE_SHIFT; + break; + case 13: + t=KEY_NSPIRE_CTRL; + break; + case 14: + t=KEY_NSPIRE_SCRATCHPAD; + break; + case 15: + t=KEY_NSPIRE_VAR; + break; + case 16: + t=KEY_NSPIRE_DOC; + break; + case 17: + t=KEY_NSPIRE_DEL; + break; + case 18: + t=KEY_NSPIRE_eEXP; + break; + case 19: + t=KEY_NSPIRE_EQU; + break; + case 20: + t=KEY_NSPIRE_TENX; + break; + case 21: + t=KEY_NSPIRE_I; + break; + case 22: + t=KEY_NSPIRE_COMMA; + break; + case 23: + t=KEY_NSPIRE_EXP; + break; + case 24: + t=KEY_NSPIRE_TRIG; + break; + case 25: + t=KEY_NSPIRE_C; + break; + case 26: + t=KEY_NSPIRE_T; + break; + case 27: + t=KEY_NSPIRE_PI; + break; + case 28: + t=KEY_NSPIRE_S; + break; + case 29: + t=KEY_NSPIRE_SQU; + break; + case 30: + t=KEY_NSPIRE_7; + break; + case 31: + t=KEY_NSPIRE_8; + break; + case 32: + t=KEY_NSPIRE_9; + break; + case 33: + t=KEY_NSPIRE_LP; + break; + case 34: + t=KEY_NSPIRE_RP; + break; + case 36: + t=KEY_NSPIRE_4; + break; + case 37: + t=KEY_NSPIRE_5; + break; + case 38: + t=KEY_NSPIRE_6; + break; + case 39: + t=KEY_NSPIRE_MULTIPLY; + break; + case 40: + t=KEY_NSPIRE_DIVIDE; + break; + case 42: + t=KEY_NSPIRE_1; + break; + case 43: + t=KEY_NSPIRE_2; + break; + case 44: + t=KEY_NSPIRE_3; + break; + case 45: + t=KEY_NSPIRE_PLUS; + break; + case 46: + t=KEY_NSPIRE_MINUS; + break; + case 48: + t=KEY_NSPIRE_0; + break; + case 49: + t=KEY_NSPIRE_PERIOD; + break; + case 50: + t=KEY_NSPIRE_EE; + break; + case 51: + t=KEY_NSPIRE_NEGATIVE; + break; + case 52: + t=KEY_NSPIRE_RET; + break; + } + return isKeyPressed(t); +} + + +// ? see also ndless-sdk/thirdparty/nspire-io/arch-nspire/nspire.c nio_ascii_get +int getkey(int allow_suspend){ + sync_screen(); + if (shutdown_state) + return KEY_SHUTDOWN; + int lastkey=-1; + unsigned NSPIRE_RTC_ADDR=0x90090000; + static unsigned lastt=0; + for (;;){ + unsigned t1= * (volatile unsigned *) NSPIRE_RTC_ADDR; + if (lastt==0) + lastt=t1; + if (t1-lastt>10){ + display_time(); + sync_screen(); + } + int autosuspend=(t1-lastt>=100); + if (nspire_exam_mode!=2 && + is_cx2 && nspire_ctrl && on_key_pressed()){ + os_fill_rect(50,90,200,40,0x1234); + nspire_draw_string(60,120,0,0xffff,Regular12,"Quit KhiCAS to shutdown",false); + nspire_ctrl=false; + statusline(1); + continue; + } + if ( (nspire_exam_mode==2 || !is_cx2) && + allow_suspend && (autosuspend || (nspire_ctrl && on_key_pressed()))){ + nspire_ctrl=nspire_shift=false; + while (!autosuspend && on_key_pressed()) + loopsleep(10); + // somewhat OFF by setting LCD to 0 + unsigned NSPIRE_CONTRAST_ADDR=is_cx2?0x90130014:0x900f0020; + unsigned oldval=*(volatile unsigned *)NSPIRE_CONTRAST_ADDR,oldval2; + if (is_cx2){ + oldval2=*(volatile unsigned *) (NSPIRE_CONTRAST_ADDR+4); + *(volatile unsigned *) (NSPIRE_CONTRAST_ADDR+4)=0xffff; + } + *(volatile unsigned *)NSPIRE_CONTRAST_ADDR=is_cx2?0xffff:0x100; + static volatile uint32_t *lcd_controller = (volatile uint32_t*) 0xC0000000; + lcd_controller[6] &= ~(0b1 << 11); + loopsleep(20); + lcd_controller[6] &= ~ 0b1; + unsigned offtime=* (volatile unsigned *) NSPIRE_RTC_ADDR; + for (int n=0;!on_key_pressed();++n){ + loopsleep(100); + idle(); + if (!exam_mode && nspire_exam_mode!=2 && shutdown + // && n&0xff==0 + ){ + unsigned curtime=* (volatile unsigned *) NSPIRE_RTC_ADDR; + if (curtime-offtime>7200){ + shutdown_state=1; + // after 2 hours, leave KhiCAS + // that way the OS will really shutdown the calc + lcd_controller[6] |= 0b1; + loopsleep(20); + lcd_controller[6]|= 0b1 << 11; + if (is_cx2) + *(volatile unsigned *)(NSPIRE_CONTRAST_ADDR+4)=oldval2; + *(volatile unsigned *)NSPIRE_CONTRAST_ADDR=oldval; + statuslinemsg("Press ON to disable KhiCAS auto shutdown"); + //os_fill_rect(0,0,320,222,0xffff); + sync_screen(); + int m=0,mmax=150; + for (;m=KEY_CTRL_LEFT && i<=KEY_CTRL_RIGHT) || + (i>=KEY_UP_CTRL && i<=KEY_RIGHT_CTRL) || + (i>=KEY_SELECT_LEFT && i<=KEY_SELECT_RIGHT) || + i==KEY_CTRL_DEL){ + int delay=(lastkey==i)?5:60,j; + for (j=0;j=0 && h>=0) + os_fill_rect(x,y,w,h,c); + } + int os_get_pixel(int x,int y); + /* returns new x position */ +#ifdef __cplusplus + int os_draw_string(int x,int y,int c,int bg,const char * s,int fake=0); +#else + int os_draw_string(int x,int y,int c,int bg,const char * s,int fake); +#endif + inline int os_draw_string_(int x,int y,const char * s){ return os_draw_string(x,y,SDK_BLACK,SDK_WHITE,s,0);} +#ifdef __cplusplus + int os_draw_string_small(int x,int y,int c,int bg,const char * s,int fake=0); +#else + int os_draw_string_small(int x,int y,int c,int bg,const char * s,int fake); +#endif + inline int os_draw_string_small_(int x,int y,const char * s){ return os_draw_string_small(x,y,SDK_BLACK,SDK_WHITE,s,0);} + +#ifdef __cplusplus +#ifdef NUMWORKS + inline int os_draw_string_medium(int x,int y,int c,int bg,const char * s,int fake=0){ return os_draw_string_small(x,y,c,bg,s,fake);} +#else + int os_draw_string_medium(int x,int y,int c,int bg,const char * s,int fake=0); +#endif +#else +#ifdef NUMWORKS + inline int os_draw_string_medium(int x,int y,int c,int bg,const char * s,int fake){ return os_draw_string_small(x,y,c,bg,s,fake);} +#else + int os_draw_string_medium(int x,int y,int c,int bg,const char * s,int fake); +#endif +#endif + inline int os_draw_string_medium_(int x,int y,const char * s){ return os_draw_string_medium(x,y,SDK_BLACK,SDK_WHITE,s,0);} + + inline void Printmini(int x,int y,const char * s,int i){ + // dbg_printf("Printmini %i %i %s %i\n",x,y,s,i); +#if defined FX || defined FXCG + os_draw_string_small(x,y,i?0xffff:0,i?0:0xffff,s,false); +#else + os_draw_string_small(x,y,SDK_BLACK,i?COLOR_SELECTED:SDK_WHITE,s,false); +#endif + } + + inline void Printxy(int x,int y,const char * s,int i){ os_draw_string_medium(x,y,0,i?COLOR_SELECTED:0xffff,s,false);} + inline void PrintXY(int x,int y,const char * s,int i){ Printxy(3*x,3*y,s,i);} + + void GetKey(int * key); + int getkey(int allow_suspend); // transformed + inline void ck_getkey(int *key){ GetKey(key);} + void enable_back_interrupt(); + inline void set_abort(){ enable_back_interrupt(); } + void disable_back_interrupt(); + inline void clear_abort(){ disable_back_interrupt(); } + int isalphaactive(); + int alphawasactive(int * key); + void lock_alpha(); + void reset_kbd(); + int handle_f5(); + void statuslinemsg(const char * msg); +#ifdef __cplusplus + void statusline(int mode=0); +#else + void statusline(int mode); +#endif + void statusflags(void); +#ifdef NUMWORKS + inline int iskeydown(int key){ return getkey(key | 0x80000000); } +#else + int iskeydown(int key); +#endif + +#if defined NSPIRE || defined NSPIRE_NEWLIB + extern int nspire_shift,nspire_ctrl; + double loopsleep(int ms); +#include + Gc * get_gc(); + int c_rgb565to888(int c); + int nspire_scan(int * adaptive_cursor_state); + +#define max_heap_size 60 + extern int nspireemu; + extern char nspire_filebuf[NSPIRE_FILEBUFFER]; + extern int on_key_enabled; + void get_hms(int *h,int *m,int *s); + void reset_gc(); +#endif + + extern int (*shutdown)(); // function called after 2 hours of idle + extern short int shutdown_state; + inline void Bdisp_PutDisp_DD(void){ sync_screen(); } + inline void sprint_int(char * c,int i){ sprintf(c,"%d",i);} + inline void sprint_double(char * c,double d){ sprintf(c,"%.4g",d);} + int GetSetupSetting(int k); + inline int Setup_GetEntry(int k){ return GetSetupSetting(k); } + void SetQuitHandler( void (*f)(void)); +#define RTC_GetTicks millis +#ifndef TICE + inline void clear_screen(void){os_fill_rect(0,0,LCD_WIDTH_PX,LCD_HEIGHT_PX,SDK_WHITE);} +#endif + inline void Bdisp_AllClr_VRAM(void){ clear_screen(); } + +#ifdef __cplusplus +} +#endif +#endif // K_CSDK_H diff --git a/ion/src/simulator/nspire/k_defs.h b/ion/src/simulator/nspire/k_defs.h new file mode 100755 index 00000000000..ce395b192c3 --- /dev/null +++ b/ion/src/simulator/nspire/k_defs.h @@ -0,0 +1,211 @@ +#ifndef K_DEFS_H +#define K_DEFS_H +#define NSPIRE_FILEBUFFER 512*1024 + // Character codes +#define KEY_CHAR_0 0x30 +#define KEY_CHAR_1 0x31 +#define KEY_CHAR_2 0x32 +#define KEY_CHAR_3 0x33 +#define KEY_CHAR_4 0x34 +#define KEY_CHAR_5 0x35 +#define KEY_CHAR_6 0x36 +#define KEY_CHAR_7 0x37 +#define KEY_CHAR_8 0x38 +#define KEY_CHAR_9 0x39 +#define KEY_CHAR_DP 0x2e +#define KEY_CHAR_EXP 0x0f +#define KEY_CHAR_PMINUS 30200 +#define KEY_CHAR_PLUS 43 +#define KEY_CHAR_MINUS 45 +#define KEY_CHAR_MULT 42 +#define KEY_CHAR_DIV 47 +#define KEY_CHAR_FRAC 0xbb +#define KEY_CHAR_LPAR 0x28 +#define KEY_CHAR_RPAR 0x29 +#define KEY_CHAR_COMMA 0x2c +#define KEY_CHAR_STORE 0x0e +#define KEY_CHAR_LOG 0x95 +#define KEY_CHAR_LN 0x85 +#define KEY_CHAR_SIN 0x81 +#define KEY_CHAR_COS 0x82 +#define KEY_CHAR_TAN 0x83 +#define KEY_CHAR_SQUARE 0x8b +#define KEY_CHAR_POW 0xa8 +#define KEY_CHAR_IMGNRY 0x7f50 +#define KEY_CHAR_LIST 0x7f51 +#define KEY_CHAR_MAT 0x7f40 +#define KEY_CHAR_EQUAL 0x3d +#define KEY_CHAR_PI 0xd0 +#define KEY_CHAR_ANS 0xc0 +#define KEY_SHIFT_ANS 0xc1 +#define KEY_CHAR_LBRCKT 0x5b +#define KEY_CHAR_RBRCKT 0x5d +#define KEY_CHAR_LBRACE 0x7b +#define KEY_CHAR_RBRACE 0x7d +#define KEY_CHAR_CR 0x0d +#define KEY_CHAR_CUBEROOT 0x96 +#define KEY_CHAR_RECIP 0x9b +#define KEY_CHAR_ANGLE 0x7f54 +#define KEY_CHAR_EXPN10 0xb5 +#define KEY_CHAR_EXPN 0xa5 +#define KEY_CHAR_ASIN 0x91 +#define KEY_CHAR_ACOS 0x92 +#define KEY_CHAR_ATAN 0x93 +#define KEY_CHAR_ROOT 0x86 +#define KEY_CHAR_POWROOT 0xb8 +#define KEY_CHAR_SPACE 0x20 +#define KEY_CHAR_DQUATE 0x22 +#define KEY_CHAR_VALR 0xcd +#define KEY_CHAR_THETA 0xce +#define KEY_CHAR_FACTOR 0xda +#define KEY_CHAR_NORMAL 0xdb +#define KEY_CHAR_SHIFTMINUS 0xdc +#define KEY_CHAR_CROCHETS 0xdd +#define KEY_CHAR_ACCOLADES 0xde +#define KEY_CHAR_A 0x41 +#define KEY_CHAR_B 0x42 +#define KEY_CHAR_C 0x43 +#define KEY_CHAR_D 0x44 +#define KEY_CHAR_E 0x45 +#define KEY_CHAR_F 0x46 +#define KEY_CHAR_G 0x47 +#define KEY_CHAR_H 0x48 +#define KEY_CHAR_I 0x49 +#define KEY_CHAR_J 0x4a +#define KEY_CHAR_K 0x4b +#define KEY_CHAR_L 0x4c +#define KEY_CHAR_M 0x4d +#define KEY_CHAR_N 0x4e +#define KEY_CHAR_O 0x4f +#define KEY_CHAR_P 0x50 +#define KEY_CHAR_Q 0x51 +#define KEY_CHAR_R 0x52 +#define KEY_CHAR_S 0x53 +#define KEY_CHAR_T 0x54 +#define KEY_CHAR_U 0x55 +#define KEY_CHAR_V 0x56 +#define KEY_CHAR_W 0x57 +#define KEY_CHAR_X 0x58 +#define KEY_CHAR_Y 0x59 +#define KEY_CHAR_Z 0x5a + + + // Control codes +#define KEY_CTRL_FORMAT 30203 +#define KEY_CTRL_NOP 30202 +#define KEY_CTRL_EXE 30201 +#define KEY_CTRL_DEL 30025 +#define KEY_CTRL_AC 30070 +#define KEY_CTRL_FD 30046 +#define KEY_CTRL_UNDO 30045 +#define KEY_CTRL_XTT 30001 +#define KEY_CTRL_EXIT 5 +#define KEY_CTRL_OK 4 +#define KEY_CTRL_SHIFT 30006 +#define KEY_CTRL_ALPHA 30007 +#define KEY_CTRL_OPTN 30008 +#define KEY_CTRL_VARS 30030 +#define KEY_CTRL_UP 1 +#define KEY_CTRL_DOWN 2 +#define KEY_CTRL_LEFT 0 +#define KEY_CTRL_RIGHT 3 +#define KEY_CTRL_F1 30009 +#define KEY_CTRL_F2 30010 +#define KEY_CTRL_F3 30011 +#define KEY_CTRL_F4 30012 +#define KEY_CTRL_F5 30013 +#define KEY_CTRL_F6 30014 +#define KEY_CTRL_F7 30915 +#define KEY_CTRL_F8 30916 +#define KEY_CTRL_F9 30917 +#define KEY_CTRL_F10 30918 +#define KEY_CTRL_F11 30919 +#define KEY_CTRL_F12 30920 +#define KEY_CTRL_F13 30921 +#define KEY_CTRL_F14 30922 +#define KEY_CTRL_F15 30923 +#define KEY_CTRL_F16 30924 +#define KEY_CTRL_F17 30925 +#define KEY_CTRL_F18 30926 +#define KEY_CTRL_F19 30927 +#define KEY_CTRL_F20 30928 +#define KEY_CTRL_CATALOG 30100 +#define KEY_CTRL_CAPTURE 30055 +#define KEY_CTRL_CLIP 30050 +#define KEY_CTRL_CUT 30250 +#define KEY_CTRL_PASTE 30036 +#define KEY_CTRL_INS 30033 +#define KEY_CTRL_MIXEDFRAC 30054 +#define KEY_CTRL_FRACCNVRT 30026 +#define KEY_CTRL_QUIT 30029 +#define KEY_CTRL_PRGM 30028 +#define KEY_CTRL_SETUP 30037 +#define KEY_CTRL_PAGEUP 30052 +#define KEY_CTRL_PAGEDOWN 30053 +#define KEY_CTRL_MENU 30003 +#define KEY_SHIFT_OPTN 30059 +#define KEY_CTRL_RESERVE1 30060 +#define KEY_CTRL_RESERVE2 30061 +#define KEY_SHIFT_LEFT 30062 +#define KEY_SHIFT_RIGHT 30063 +#define KEY_UP_CTRL 31060 +#define KEY_DOWN_CTRL 31061 +#define KEY_LEFT_CTRL 31062 +#define KEY_RIGHT_CTRL 31063 +#define KEY_CALCULATOR 31064 +#define KEY_SAVE 31065 +#define KEY_LOAD 31066 +#define KEY_CTRL_A 31001 +#define KEY_CTRL_D 31004 +#define KEY_CTRL_E 31005 +#define KEY_CTRL_H 31008 // help? +#define KEY_CTRL_M 31011 // doc menu +#define KEY_CTRL_N 31012 +#define KEY_CTRL_R 31018 +#define KEY_CTRL_S 31019 +#define KEY_CTRL_T 31020 +#define KEY_EQW_TEMPLATE 31100 +#define KEY_AFFECT 31101 +#define KEY_FLAG 31102 +#define KEY_BOOK 31103 +//#define KEY_CTRL_APPS 31104 +#define KEY_CTRL_SYMB 31105 +//#define KEY_CTRL_NUM 31106 +//#define KEY_CTRL_PLOT 31107 +#define KEY_SELECT_LEFT 31200 +#define KEY_SELECT_UP 31201 +#define KEY_SELECT_DOWN 31202 +#define KEY_SELECT_RIGHT 31203 +#define KEY_SHUTDOWN 32109 + +#define KEY_PRGM_ACON 10 +#define KEY_PRGM_DOWN 37 +#define KEY_PRGM_EXIT 47 +#define KEY_PRGM_F1 79 +#define KEY_PRGM_F2 69 +#define KEY_PRGM_F3 59 +#define KEY_PRGM_F4 49 +#define KEY_PRGM_F5 39 +#define KEY_PRGM_F6 29 +#define KEY_PRGM_LEFT 38 +#define KEY_PRGM_NONE 0 +#define KEY_PRGM_RETURN 31 +#define KEY_PRGM_RIGHT 27 +#define KEY_PRGM_UP 28 +#define KEY_PRGM_1 72 +#define KEY_PRGM_2 62 +#define KEY_PRGM_3 52 +#define KEY_PRGM_4 73 +#define KEY_PRGM_5 63 +#define KEY_PRGM_6 53 +#define KEY_PRGM_7 74 +#define KEY_PRGM_8 64 +#define KEY_PRGM_9 54 +#define KEY_PRGM_A 76 +#define KEY_PRGM_F 26 +#define KEY_PRGM_ALPHA 77 +#define KEY_PRGM_SHIFT 78 +#define KEY_PRGM_MENU 48 +#define KEY_CTRL_SD 39990 + +#endif diff --git a/ion/src/simulator/nspire/keyboard.cpp b/ion/src/simulator/nspire/keyboard.cpp new file mode 100644 index 00000000000..15e993f0d02 --- /dev/null +++ b/ion/src/simulator/nspire/keyboard.cpp @@ -0,0 +1,279 @@ +#include +#include + + +#include +#include +#include + +#include "layout_keyboard.h" +#include "main.h" +#include "k_csdk.h" + + +using namespace Ion::Keyboard; + +class KeyPair { +public: + constexpr KeyPair(Key key, bool numworksShift, bool numworksAlpha, int gintKey, bool gintShift, bool gintAlpha, bool ignoreShiftAlpha = false) : + m_key(key), + m_numworksShift(numworksShift), + m_numworksAlpha(numworksAlpha), + m_gintKey(gintKey), + m_gintShift(gintShift), + m_gintAlpha(gintAlpha), + m_ignoreShiftAlpha(ignoreShiftAlpha) + {} + Key key() const { return m_key; } + bool numworksShift() const { return m_numworksShift; } + bool numworksAlpha() const { return m_numworksAlpha; } + int gintKey() const { return m_gintKey; } + bool gintShift() const { return m_gintShift; } + bool gintAlpha() const { return m_gintAlpha; } + bool ignoreShiftAlpha() const { return m_ignoreShiftAlpha; } +private: + Key m_key; + bool m_numworksShift; + bool m_numworksAlpha; + int m_gintKey; + bool m_gintShift; + bool m_gintAlpha; + bool m_ignoreShiftAlpha; +}; + +constexpr static KeyPair sKeyPairs[] = { + KeyPair(Key::Down, false, false, KEY_DOWN, false, false, true), + KeyPair(Key::Left, false, false, KEY_LEFT, false, false, true), + KeyPair(Key::Right, false, false, KEY_RIGHT, false, false, true), + KeyPair(Key::Up, false, false, KEY_UP, false, false, true), + KeyPair(Key::Back, false, false, KEY_EXIT, false, false), + KeyPair(Key::Home, false, false, KEY_MENU, false, false), + KeyPair(Key::Shift, false, false, KEY_SHIFT, false, false, true), + KeyPair(Key::Alpha, false, false, KEY_ALPHA, false, false, true), + KeyPair(Key::XNT, false, false, KEY_XOT, false, false), + KeyPair(Key::Var, false, false, KEY_VARS, false, false), + KeyPair(Key::Toolbox, false, false, KEY_CTRL_CATALOG, false, false), + KeyPair(Key::Backspace, false, false, KEY_DEL, false, false), + KeyPair(Key::Exp, false, false, KEY_CHAR_EXPN, false, false), + KeyPair(Key::Ln, false, false, KEY_LN, false, false), + KeyPair(Key::Log, false, false, KEY_LOG, false, false), + KeyPair(Key::Imaginary, false, false, KEY_CHAR_IMGNRY, false, false), + KeyPair(Key::Comma, false, false, KEY_COMMA, false, false), + KeyPair(Key::Power, false, false, '^', false, false), + KeyPair(Key::Sine, false, false, KEY_SIN, false, false), + KeyPair(Key::Cosine, false, false, KEY_COS, false, false), + KeyPair(Key::Tangent, false, false, KEY_TAN, false, false), + KeyPair(Key::Pi, false, false, KEY_CHAR_PI, false, false), + KeyPair(Key::Sqrt, false, false, KEY_CHAR_ROOT, false, false), + KeyPair(Key::Square, false, false, KEY_SQUARE, false, false), + KeyPair(Key::Seven, false, false, KEY_7, false, false), + KeyPair(Key::Eight, false, false, KEY_8, false, false), + KeyPair(Key::Nine, false, false, KEY_9, false, false), + KeyPair(Key::LeftParenthesis, false, false, KEY_LEFTP, false, false), + KeyPair(Key::RightParenthesis, false, false, KEY_RIGHTP, false, false), + KeyPair(Key::Four, false, false, KEY_4, false, false), + KeyPair(Key::Five, false, false, KEY_5, false, false), + KeyPair(Key::Six, false, false, KEY_6, false, false), + KeyPair(Key::Multiplication, false, false, KEY_MUL, false, false), + KeyPair(Key::Division, false, false, KEY_DIV, false, false), + KeyPair(Key::Division, false, false, KEY_FRAC, false, false), + KeyPair(Key::One, false, false, KEY_1, false, false), + KeyPair(Key::Two, false, false, KEY_2, false, false), + KeyPair(Key::Three, false, false, KEY_3, false, false), + KeyPair(Key::Plus, false, false, KEY_ADD, false, false), + KeyPair(Key::Minus, false, false, KEY_SUB, false, false), + KeyPair(Key::Zero, false, false, KEY_0, false, false), + KeyPair(Key::Dot, false, false, KEY_DOT, false, false), + KeyPair(Key::EE, false, false, KEY_CHAR_EXP, false, false), + KeyPair(Key::Ans, false, false, KEY_CHAR_ANS, true, false), + KeyPair(Key::EXE, false, false, KEY_EXE, false, false, true), + KeyPair(Key::OnOff, false, false, KEY_ACON, false, false), + + // Cut + KeyPair(Key::XNT, true, false, KEY_CTRL_CUT, false, false), + // Copy + KeyPair(Key::Var, true, false, KEY_CTRL_CLIP, false, false), + // Paste + KeyPair(Key::Toolbox, true, false, KEY_CTRL_PASTE, false, false), + // Clear + KeyPair(Key::Backspace, true, false, KEY_ACON, false, false), + // [ + KeyPair(Key::Exp, true, false, KEY_CHAR_CROCHETS, false, false), + // ] + KeyPair(Key::Ln, true, false, KEY_CTRL_F13, false, false), + // { + KeyPair(Key::Log, true, false, KEY_CHAR_ACCOLADES, false, false), + // } + KeyPair(Key::Imaginary, true, false, KEY_CTRL_F14, false, false), + // _ + KeyPair(Key::Comma, true, false, KEY_NEG, false, false), + // -> + KeyPair(Key::Power, true, false, KEY_STORE, false, true), + // asin + KeyPair(Key::Sine, true, false, KEY_CHAR_ASIN, true, false), + // acos + KeyPair(Key::Cosine, true, false, KEY_CHAR_ACOS, true, false), + // atan + KeyPair(Key::Tangent, true, false, KEY_CHAR_ATAN, true, false), + // = + KeyPair(Key::Pi, true, false, KEY_CHAR_EQUAL, false, false), + // < + KeyPair(Key::Sqrt, true, false, '<', false, false), + // > + KeyPair(Key::Square, true, false, '>', false, false), + + // : + KeyPair(Key::XNT, false, true, ':', false, false), + // ; + KeyPair(Key::Var, false, true, ';', false, false), + // " + KeyPair(Key::Toolbox, false, true, KEY_CHAR_DQUATE, false, true), + // % + KeyPair(Key::Backspace, false, true, '%', false, false), + // A + KeyPair(Key::Exp, false, true, 'a', false, false), + // B + KeyPair(Key::Ln, false, true, 'b', false, false), + // C + KeyPair(Key::Log, false, true, 'c', false, false), + // D + KeyPair(Key::Imaginary, false, true, 'd', false, false), + // E + KeyPair(Key::Comma, false, true, 'e', false, false), + // F + KeyPair(Key::Power, false, true, 'f', false, false), + // G + KeyPair(Key::Sine, false, true, 'g', false, false), + // H + KeyPair(Key::Cosine, false, true, 'h', false, false), + // I + KeyPair(Key::Tangent, false, true, 'i', false, false), + // J + KeyPair(Key::Pi, false, true, 'j', false, false), + // K + KeyPair(Key::Sqrt, false, true,'k', false, false), + // L + KeyPair(Key::Square, false, true, 'l', false, false), + // M + KeyPair(Key::Seven, false, true, 'm', false, false), + // N + KeyPair(Key::Eight, false, true, 'n', false, false), + // O + KeyPair(Key::Nine, false, true, 'o', false, false), + // P + KeyPair(Key::LeftParenthesis, false, true, 'p', false, false), + // Q + KeyPair(Key::RightParenthesis, false, true, 'q', false, false), + // R + KeyPair(Key::Four, false, true, 'r', false, false), + // S + KeyPair(Key::Five, false, true, 's', false, false), + // T + KeyPair(Key::Six, false, true, 't', false, false), + // U + KeyPair(Key::Multiplication, false, true, 'u', false, false), + // V + KeyPair(Key::Division, false, true, 'v', false, false), + // W + KeyPair(Key::One, false, true, 'w', false, false), + // X + KeyPair(Key::Two, false, true, 'x', false, false), + // Y + KeyPair(Key::Three, false, true, 'y', false, false), + // Z + KeyPair(Key::Plus, false, true, 'z', false, false), + // Space + KeyPair(Key::Minus, false, true, KEY_CHAR_SPACE, false, false), + // ? + KeyPair(Key::Zero, false, true, KEY_F6, true, false), + // ! + KeyPair(Key::Dot, false, true, KEY_F6, false, false), + + // Brightness control shortcut in Upsilon + KeyPair(Key::Plus, true, false, KEY_ADD, false, true), + KeyPair(Key::Minus, true, false, KEY_SUB, false, true), + + +}; + +constexpr int sNumberOfKeyPairs = sizeof(sKeyPairs)/sizeof(KeyPair); + +namespace Ion { +namespace Keyboard { + +int menuHeldFor = 0; + +State scan() { + State state = 0; + + // Grab this opportunity to refresh the display if needed + Simulator::Main::refresh(); + if (!any_key_pressed()) + return state; + if (isKeyPressed(KEY_NSPIRE_HOME)){ + state.setKey(Key::Home); return state; + } + if (isKeyPressed(KEY_NSPIRE_ENTER)){ + state.setKey(Key::EXE); + return state; + } + if (isKeyPressed(KEY_NSPIRE_LEFT)){ + state.setKey(Key::Left); + return state; + } + if (isKeyPressed(KEY_NSPIRE_RIGHT)){ + state.setKey(Key::Right); + return state; + } + if (isKeyPressed(KEY_NSPIRE_UP)){ + state.setKey(Key::Up); + return state; + } + if (isKeyPressed(KEY_NSPIRE_DOWN)){ + state.setKey(Key::Down); + return state; + } + int shiftstate; + int scancode=nspire_scan(&shiftstate); + if (scancode==0) { + return state; + } + switch (scancode){ + case -2: case -1: + return state; + case KEY_CTRL_EXIT: + state.setKey(Key::Back); + return state; + case KEY_CTRL_EXE: + state.setKey(Key::EXE); + return state; + case KEY_CTRL_MENU: + state.setKey(Key::Home); + return state; + } + nspire_ctrl=nspire_shift=0; + for (int i = 0; i < sNumberOfKeyPairs; i++) { + const KeyPair & keyPair = sKeyPairs[i]; + if (scancode==keyPair.gintKey()) { + state.setSimulatedShift(keyPair.numworksShift() ? ModSimState::ForceOn : ModSimState::ForceOff); + state.setSimulatedAlpha(keyPair.numworksAlpha() ? ModSimState::ForceOn : ModSimState::ForceOff); + state.setKey(keyPair.key()); + return state; + } + } + if (isKeyPressed(KEY_NSPIRE_TENX)){ + exit(0); + } + return state; +} + +} +} + +namespace Ion { +namespace Simulator { +namespace Keyboard { + +} +} +} diff --git a/ion/src/simulator/nspire/keyboard.h b/ion/src/simulator/nspire/keyboard.h new file mode 100644 index 00000000000..0e379642fb8 --- /dev/null +++ b/ion/src/simulator/nspire/keyboard.h @@ -0,0 +1,15 @@ +#ifndef ION_SIMULATOR_KEYBOARD_H +#define ION_SIMULATOR_KEYBOARD_H + +#include +// #include + +namespace Ion { +namespace Simulator { +namespace Keyboard { + +} +} +} + +#endif \ No newline at end of file diff --git a/ion/src/simulator/nspire/main.cpp b/ion/src/simulator/nspire/main.cpp new file mode 100644 index 00000000000..cfde0d41253 --- /dev/null +++ b/ion/src/simulator/nspire/main.cpp @@ -0,0 +1,68 @@ +#include "main.h" +#include "display.h" +#include "platform.h" + + +#include +#include +#include +#include +#include + +#include +#include +#include "k_csdk.h" + +static const char * storage_name="nwstore.nws"; + +int save_state(const char * fname); // apps/home/controller.cpp + +extern "C" { + extern const int prizm_heap_size; + const int prizm_heap_size=1024*1024; + __attribute__((aligned(4))) char prizm_heap[prizm_heap_size]; + + int calculator=4; // -1 means OS not checked, 0 unknown, 1 cg50 or 90, 2 emu 50 or 90, 3 other + + int main() { + sdk_init(); //this calls the lcd init functions from behind the scenes + Ion::Simulator::Main::init(); + ion_main(0, NULL); + Ion::Simulator::Main::quit(); + sdk_end(); + return 0; + } +} + +namespace Ion { +namespace Simulator { +namespace Main { + +static bool sNeedsRefresh = false; + +void init() { + Ion::Simulator::Display::init(); + setNeedsRefresh(); +} + +void setNeedsRefresh() { + sNeedsRefresh = true; +} + +void refresh() { + if (!sNeedsRefresh) { + return; + } + + Display::draw(); + + sNeedsRefresh = false; +} + +void quit() { + Ion::Simulator::Display::quit(); +} + +} +} +} diff --git a/ion/src/simulator/nspire/main.h b/ion/src/simulator/nspire/main.h new file mode 100644 index 00000000000..7a9e232f0d4 --- /dev/null +++ b/ion/src/simulator/nspire/main.h @@ -0,0 +1,18 @@ +#ifndef ION_SIMULATOR_MAIN_H +#define ION_SIMULATOR_MAIN_H + +namespace Ion { +namespace Simulator { +namespace Main { + +void init(); +void quit(); + +void setNeedsRefresh(); +void refresh(); + +} +} +} + +#endif \ No newline at end of file diff --git a/ion/src/simulator/nspire/platform.h b/ion/src/simulator/nspire/platform.h new file mode 100644 index 00000000000..af5ddfcb60a --- /dev/null +++ b/ion/src/simulator/nspire/platform.h @@ -0,0 +1,23 @@ +#ifndef ION_SIMULATOR_PLATFORM_H +#define ION_SIMULATOR_PLATFORM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Those functions should be implemented per-platform. + * They are defined as C function for easier interop. */ + +const char * IonSimulatorGetLanguageCode(); + +void IonSimulatorKeyboardKeyDown(int keyNumber); +void IonSimulatorKeyboardKeyUp(int keyNumber); +void IonSimulatorEventsPushEvent(int eventNumber); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/ion/src/simulator/nspire/power.cpp b/ion/src/simulator/nspire/power.cpp new file mode 100644 index 00000000000..63560c5734f --- /dev/null +++ b/ion/src/simulator/nspire/power.cpp @@ -0,0 +1,27 @@ +#include +#include +#include + +#include "main.h" + +void powerOff(void){ +} + + +namespace Ion { +namespace Power { + +//NOTE: These should probably be reimplemented at some point +//the prior version was a janky mess that flickered a ton on wake +//and was generally awfulness. + +void suspend(bool checkIfOnOffKeyReleased) { + // Simulator::Main::runPowerOffSafe(powerOff, true); +} + +void standby() { + // Simulator::Main::runPowerOffSafe(powerOff, true); +} + +} +} diff --git a/ion/src/simulator/nspire/telemetry_init.cpp b/ion/src/simulator/nspire/telemetry_init.cpp new file mode 100644 index 00000000000..7a69b2d8c83 --- /dev/null +++ b/ion/src/simulator/nspire/telemetry_init.cpp @@ -0,0 +1,15 @@ +#include "platform.h" + +namespace Ion { +namespace Simulator { +namespace Telemetry { + +void init() { +} + +void shutdown() { +} + +} +} +} \ No newline at end of file diff --git a/ion/src/simulator/nspire/timing.cpp b/ion/src/simulator/nspire/timing.cpp new file mode 100644 index 00000000000..9378fe1c0f5 --- /dev/null +++ b/ion/src/simulator/nspire/timing.cpp @@ -0,0 +1,21 @@ +#include +#include +#include "k_csdk.h" + +namespace Ion { +namespace Timing { + +uint64_t millis() { + return ::millis(); +} + +void usleep(uint32_t us) { + os_wait_1ms(us/1000); // sleep_us(us); +} + +void msleep(uint32_t ms) { + os_wait_1ms(ms); // sleep_us(ms * 1000); +} + +} +} diff --git a/ion/src/simulator/shared/apple/helpers.mak b/ion/src/simulator/shared/apple/helpers.mak index bf765b0d530..6298239ee32 100644 --- a/ion/src/simulator/shared/apple/helpers.mak +++ b/ion/src/simulator/shared/apple/helpers.mak @@ -1,4 +1,4 @@ -# This file contains all the recipies shared between iOS and macOS. +# This file contains all the recipes shared between iOS and macOS. # The only things that have to be customized per platform are the icons and the # Info.plist. diff --git a/ion/src/simulator/shared/apple/targets.mak b/ion/src/simulator/shared/apple/targets.mak index 027602f2ef7..8c790886b48 100644 --- a/ion/src/simulator/shared/apple/targets.mak +++ b/ion/src/simulator/shared/apple/targets.mak @@ -1,7 +1,7 @@ .PRECIOUS: $(simulator_app_deps) # CAUTION: The empty line in this rule is important! -# Indeed, rule without receipe serve a special purpose +# Indeed, rule without recipe serve a special purpose # https://www.gnu.org/software/make/manual/html_node/Canceling-Rules.html .PRECIOUS: $(BUILD_DIR)/%.app $(BUILD_DIR)/%.app: $(simulator_app_deps) diff --git a/ion/src/simulator/shared/events_platform.cpp b/ion/src/simulator/shared/events_platform.cpp index 8eb5b894a01..ad60ce0ce71 100644 --- a/ion/src/simulator/shared/events_platform.cpp +++ b/ion/src/simulator/shared/events_platform.cpp @@ -163,7 +163,7 @@ Event getPlatformEvent() { SDL_GetMouseState(&p.x, &p.y); Simulator::Layout::highlightKeyAt(&p); } - /* On smarphones, don't forget to unhighlight the key when the finger is up. + /* On smartphones, don't forget to unhighlight the key when the finger is up. * (finger up doesn't imply a mouse motion!) */ if (event.type == SDL_FINGERUP) { Simulator::Layout::unhighlightKey(); diff --git a/ion/src/simulator/shared/platform_info.cpp b/ion/src/simulator/shared/platform_info.cpp index cdf676a0c65..848285a871c 100644 --- a/ion/src/simulator/shared/platform_info.cpp +++ b/ion/src/simulator/shared/platform_info.cpp @@ -13,6 +13,10 @@ #error This file expects OMEGA_VERSION to be defined #endif +#ifndef UPSILON_VERSION +#error This file expects UPSILON_VERSION to be defined +#endif + #ifndef HEADER_SECTION #define HEADER_SECTION #endif @@ -31,30 +35,45 @@ class PlatformInfo { m_storageAddress(storageAddress), m_storageSize(Ion::Storage::k_storageSize), m_footer(Magic), - m_ohm_header(OmegaMagic), + m_omegaMagicHeader(OmegaMagic), m_omegaVersion{OMEGA_VERSION}, #ifdef OMEGA_USERNAME m_username{OMEGA_USERNAME}, #else m_username{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, #endif - m_ohm_footer(OmegaMagic) { } + m_omegaMagicFooter(OmegaMagic), + m_upsilonMagicHeader(UpsilonMagic), + m_upsilonVersion{UPSILON_VERSION}, + m_osType(OSType), + m_upsilonMagicFooter(UpsilonMagic) { } const char * version() const { assert(m_storageAddress != nullptr); assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_version; } + const char * upsilonVersion() const { + assert(m_storageAddress != nullptr); + assert(m_storageSize != 0); + assert(m_header == Magic); + assert(m_footer == Magic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); + assert(m_upsilonMagicHeader == UpsilonMagic); + assert(m_upsilonMagicFooter == UpsilonMagic); + return m_upsilonVersion; + } const char * omegaVersion() const { assert(m_storageAddress != nullptr); assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_omegaVersion; } const volatile char * username() const volatile { @@ -62,8 +81,8 @@ class PlatformInfo { assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_username; } const char * patchLevel() const { @@ -71,23 +90,33 @@ class PlatformInfo { assert(m_storageSize != 0); assert(m_header == Magic); assert(m_footer == Magic); - assert(m_ohm_header == OmegaMagic); - assert(m_ohm_footer == OmegaMagic); + assert(m_omegaMagicHeader == OmegaMagic); + assert(m_omegaMagicFooter == OmegaMagic); return m_patchLevel; } + const void * storage_address() const { + return storageAddress; + } private: constexpr static uint32_t Magic = 0xDEC00DF0; constexpr static uint32_t OmegaMagic = 0xEFBEADDE; + constexpr static uint32_t UpsilonMagic = 0x55707369; + constexpr static uint32_t OSType = 0x79827178; uint32_t m_header; const char m_version[8]; const char m_patchLevel[8]; void * m_storageAddress; size_t m_storageSize; uint32_t m_footer; - uint32_t m_ohm_header; + uint32_t m_omegaMagicHeader; const char m_omegaVersion[16]; const volatile char m_username[16]; - uint32_t m_ohm_footer; + uint32_t m_omegaMagicFooter; + uint32_t m_upsilonMagicHeader; + const char m_upsilonVersion[16]; + uint32_t m_osType; + uint32_t m_upsilonMagicFooter; + }; const PlatformInfo HEADER_SECTION platform_infos; @@ -96,6 +125,10 @@ const char * Ion::softwareVersion() { return platform_infos.version(); } +const char * Ion::upsilonVersion() { + return platform_infos.upsilonVersion(); +} + const char * Ion::omegaVersion() { return platform_infos.omegaVersion(); } @@ -107,3 +140,11 @@ const volatile char * Ion::username() { const char * Ion::patchLevel() { return platform_infos.patchLevel(); } + +void * storage_address(){ + return storageAddress; +} + +const void * Ion::storageAddress() { + return platform_infos.storage_address(); +} \ No newline at end of file diff --git a/ion/src/simulator/shared/store_script.cpp b/ion/src/simulator/shared/store_script.cpp index 8a5cea839cd..80187ef5dae 100644 --- a/ion/src/simulator/shared/store_script.cpp +++ b/ion/src/simulator/shared/store_script.cpp @@ -16,7 +16,7 @@ static char* pref_path = nullptr; static char* file_buffer = nullptr; void loadPython(Args * arguments) { - pref_path = SDL_GetPrefPath("io.github.omega", "omega-simulator"); + pref_path = SDL_GetPrefPath("io.github.upsilon", "upsilon-simulator"); std::string path(pref_path); printf("Loading from %s\n", (path + "python.dat").c_str()); diff --git a/ion/src/simulator/shared/window.cpp b/ion/src/simulator/shared/window.cpp index e9b04bdafca..0c3156160a6 100644 --- a/ion/src/simulator/shared/window.cpp +++ b/ion/src/simulator/shared/window.cpp @@ -36,7 +36,7 @@ void init(bool screen_only, bool fullscreen, bool unresizable) { if (screen_only) { sScreenOnly = true; sWindow = SDL_CreateWindow( - "Omega", + "Upsilon", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Ion::Display::Width, Ion::Display::Height, @@ -48,7 +48,7 @@ void init(bool screen_only, bool fullscreen, bool unresizable) { ); } else { sWindow = SDL_CreateWindow( - "Omega", + "Upsilon", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 458, 888, diff --git a/ion/src/simulator/web/clipboard_helper.cpp b/ion/src/simulator/web/clipboard_helper.cpp index c7423cb31f5..1ad83287e73 100644 --- a/ion/src/simulator/web/clipboard_helper.cpp +++ b/ion/src/simulator/web/clipboard_helper.cpp @@ -49,8 +49,8 @@ EM_JS(void, get_clipboard_text, (char * buffer, uint32_t bufferSize, AsyncStatus try { navigator.clipboard.readText().then( function(text) { - var lenghtBytes = Math.min(lengthBytesUTF8(text) + 1, bufferSize); - stringToUTF8(text, buffer, lenghtBytes); + var lengthBytes = Math.min(lengthBytesUTF8(text) + 1, bufferSize); + stringToUTF8(text, buffer, lengthBytes); HEAP32[status>>2] = success; }, function(text) { HEAP32[status>>2] = failure; } diff --git a/ion/src/simulator/web/exports.cpp b/ion/src/simulator/web/exports.cpp index e4f32ce2b33..6827cb28775 100644 --- a/ion/src/simulator/web/exports.cpp +++ b/ion/src/simulator/web/exports.cpp @@ -35,3 +35,6 @@ void IonSimulatorEventsPushEvent(int eventNumber) { Ion::Events::replayFrom(j); } } + +int SDL_LockMutex(SDL_mutex *) { return 0; } +int SDL_UnlockMutex(SDL_mutex *) { return 0; } diff --git a/ion/src/simulator/web/simulator.html.inc b/ion/src/simulator/web/simulator.html.inc index dbfd95c89c6..1c52b8ff5e4 100644 --- a/ion/src/simulator/web/simulator.html.inc +++ b/ion/src/simulator/web/simulator.html.inc @@ -4,7 +4,7 @@ - Omega + Upsilon