mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-18 16:27:34 +01:00
[Update] Epsilon 15.3.1
This commit is contained in:
64
.github/workflows/ci-workflow.yml
vendored
64
.github/workflows/ci-workflow.yml
vendored
@@ -1,5 +1,17 @@
|
||||
name: Continuous integration
|
||||
on: [pull_request, push]
|
||||
#on: [pull_request, push]
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
triggerIos:
|
||||
description: 'Run iOS tests'
|
||||
required: true
|
||||
default: 'no'
|
||||
triggerMacos:
|
||||
description: 'Run macOS tests'
|
||||
required: true
|
||||
default: 'no'
|
||||
|
||||
jobs:
|
||||
# nintendo_3ds:
|
||||
@@ -11,7 +23,7 @@ jobs:
|
||||
# - 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
|
||||
@@ -94,6 +106,23 @@ jobs:
|
||||
with:
|
||||
name: epsilon-binpack-n0110.tgz
|
||||
path: output/release/device/n0110/binpack-n0110.tgz
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: msys2/setup-msys2@v2
|
||||
- uses: actions/checkout@v2
|
||||
- run: pacman -S --noconfirm mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config make mingw-w64-x86_64-python3 mingw-w64-x86_64-libjpeg-turbo mingw-w64-x86_64-libpng
|
||||
- run: make -j2 PLATFORM=simulator
|
||||
- run: make -j2 PLATFORM=simulator epsilon.official.exe
|
||||
- run: make -j2 PLATFORM=simulator test.headless.exe
|
||||
- run: output/release/simulator/windows/test.headless.exe
|
||||
- uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: epsilon-windows.exe
|
||||
path: output/release/simulator/windows/epsilon.exe
|
||||
web:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -121,3 +150,34 @@ jobs:
|
||||
name: epsilon-linux.bin
|
||||
path: output/release/simulator/linux/epsilon.bin
|
||||
- run: make -j2 PLATFORM=simulator test.headless.bin
|
||||
macos:
|
||||
if: github.event.inputs.triggerMacos == 'yes'
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- run: brew install numworks/tap/epsilon-sdk
|
||||
- uses: actions/checkout@v2
|
||||
- run: make -j2 PLATFORM=simulator
|
||||
- run: make -j2 PLATFORM=simulator epsilon.official.app
|
||||
- run: make -j2 PLATFORM=simulator ARCH=x86_64 test.headless.bin
|
||||
- run: output/release/simulator/macos/x86_64/test.headless.bin
|
||||
- uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: epsilon-macos.zip
|
||||
path: output/release/simulator/macos/epsilon.app
|
||||
ios:
|
||||
if: github.event.inputs.triggerIos == 'yes'
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- run: brew install numworks/tap/epsilon-sdk
|
||||
- uses: actions/checkout@v2
|
||||
- run: make -j2 PLATFORM=simulator TARGET=ios EPSILON_TELEMETRY=0
|
||||
- run: make -j2 PLATFORM=simulator TARGET=ios EPSILON_TELEMETRY=0 epsilon.official.ipa
|
||||
- run: make -j2 PLATFORM=simulator TARGET=ios EPSILON_TELEMETRY=0 test.ipa
|
||||
- run: make -j2 PLATFORM=simulator TARGET=ios EPSILON_TELEMETRY=0 APPLE_PLATFORM=ios-simulator
|
||||
- uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: epsilon-ios.ipa
|
||||
path: output/release/simulator/ios/epsilon.ipa
|
||||
|
||||
env:
|
||||
ACCEPT_OFFICIAL_TOS: 1
|
||||
|
||||
@@ -42,7 +42,10 @@ apps_src += $(addprefix apps/,\
|
||||
title_bar_view.cpp \
|
||||
)
|
||||
|
||||
tests_src += apps/exam_mode_configuration_non_official.cpp
|
||||
tests_src += $(addprefix apps/,\
|
||||
exam_mode_configuration_official.cpp \
|
||||
)
|
||||
|
||||
|
||||
snapshots_declaration = $(foreach i,$(apps),$(i)::Snapshot m_snapshot$(subst :,,$(i))Snapshot;)
|
||||
apps_declaration = $(foreach i,$(apps),$(i) m_$(subst :,,$(i));)
|
||||
@@ -56,6 +59,9 @@ $(call object_for,apps/apps_container_storage.cpp apps/apps_container.cpp apps/m
|
||||
|
||||
# I18n file generation
|
||||
|
||||
country_preferences = apps/country_preferences.csv
|
||||
language_preferences = apps/language_preferences.csv
|
||||
|
||||
# The header is refered to as <apps/i18n.h> so make sure it's findable this way
|
||||
SFLAGS += -I$(BUILD_DIR)
|
||||
|
||||
@@ -65,14 +71,14 @@ i18n_files += $(addprefix apps/language_,$(addsuffix _iso6391.universal.i18n, $(
|
||||
endif
|
||||
|
||||
i18n_files += $(call i18n_with_universal_for,shared)
|
||||
i18n_files += $(call i18n_without_universal_for,toolbox)
|
||||
i18n_files += $(call i18n_with_universal_for,toolbox)
|
||||
i18n_files += $(call i18n_without_universal_for,variables)
|
||||
|
||||
$(eval $(call rule_for, \
|
||||
I18N, \
|
||||
apps/i18n.cpp, \
|
||||
$(i18n_files), \
|
||||
$$(PYTHON) apps/i18n.py --codepoints $(code_points) --header $$(subst .cpp,.h,$$@) --implementation $$@ --locales $$(EPSILON_I18N) --files $$^ --generateISO6391locales $$(EPSILON_GETOPT), \
|
||||
$$(PYTHON) apps/i18n.py --codepoints $(code_points) --countrypreferences $(country_preferences) --languagepreferences $(language_preferences) --header $$(subst .cpp,.h,$$@) --implementation $$@ --locales $$(EPSILON_I18N) --countries $$(EPSILON_COUNTRIES) --files $$^ --generateISO6391locales $$(EPSILON_GETOPT), \
|
||||
global \
|
||||
))
|
||||
|
||||
@@ -87,7 +93,7 @@ $(eval $(call depends_on_image,apps/title_bar_view.cpp,apps/exam_icon.png))
|
||||
$(call object_for,$(apps_src) $(tests_src)): $(BUILD_DIR)/apps/i18n.h
|
||||
$(call object_for,$(apps_src) $(tests_src)): $(BUILD_DIR)/python/port/genhdr/qstrdefs.generated.h
|
||||
|
||||
apps_tests_src = $(app_calculation_test_src) $(app_code_test_src) $(app_probability_test_src) $(app_regression_test_src) $(app_sequence_test_src) $(app_shared_test_src) $(app_statistics_test_src) $(app_settings_test_src) $(app_solver_test_src)
|
||||
apps_tests_src = $(app_calculation_test_src) $(app_code_test_src) $(app_graph_test_src) $(app_probability_test_src) $(app_regression_test_src) $(app_sequence_test_src) $(app_shared_test_src) $(app_statistics_test_src) $(app_settings_test_src) $(app_solver_test_src)
|
||||
|
||||
apps_tests_src += $(addprefix apps/,\
|
||||
alternate_empty_nested_menu_controller.cpp \
|
||||
|
||||
@@ -59,8 +59,23 @@ AppsContainer::AppsContainer() :
|
||||
}
|
||||
|
||||
bool AppsContainer::poincareCircuitBreaker() {
|
||||
constexpr uint64_t minimalPressDuration = 20;
|
||||
static uint64_t beginningOfInterruption = 0;
|
||||
Ion::Keyboard::State state = Ion::Keyboard::scan();
|
||||
return state.keyDown(Ion::Keyboard::Key::Back);
|
||||
bool interrupt = state.keyDown(Ion::Keyboard::Key::Back) || state.keyDown(Ion::Keyboard::Key::Home) || state.keyDown(Ion::Keyboard::Key::OnOff);
|
||||
if (!interrupt) {
|
||||
beginningOfInterruption = 0;
|
||||
return false;
|
||||
}
|
||||
if (beginningOfInterruption == 0) {
|
||||
beginningOfInterruption = Ion::Timing::millis();
|
||||
return false;
|
||||
}
|
||||
if (Ion::Timing::millis() - beginningOfInterruption > minimalPressDuration) {
|
||||
beginningOfInterruption = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
App::Snapshot * AppsContainer::hardwareTestAppSnapshot() {
|
||||
@@ -181,7 +196,7 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) {
|
||||
}
|
||||
if (changedZoom) {
|
||||
KDIonContext::sharedContext()->updatePostProcessingEffects();
|
||||
redrawWindow(true);
|
||||
redrawWindow();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -341,7 +356,7 @@ void AppsContainer::shutdownDueToLowBattery() {
|
||||
* case. */
|
||||
return;
|
||||
}
|
||||
while (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) {
|
||||
while (Ion::Battery::level() == Ion::Battery::Charge::EMPTY && !Ion::USB::isPlugged()) {
|
||||
Ion::Backlight::setBrightness(0);
|
||||
if (!GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) {
|
||||
/* Unless the LED is lit up for the exam mode, switch off the LED. IF the
|
||||
@@ -365,15 +380,15 @@ bool AppsContainer::updateAlphaLock() {
|
||||
return m_window.updateAlphaLock();
|
||||
}
|
||||
|
||||
OnBoarding::PopUpController * AppsContainer::promptController() {
|
||||
OnBoarding::PromptController * AppsContainer::promptController() {
|
||||
if (k_promptNumberOfMessages == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
return &m_promptController;
|
||||
}
|
||||
|
||||
void AppsContainer::redrawWindow(bool force) {
|
||||
m_window.redraw(force);
|
||||
void AppsContainer::redrawWindow() {
|
||||
m_window.redraw();
|
||||
}
|
||||
|
||||
void AppsContainer::activateExamMode(GlobalPreferences::ExamMode examMode) {
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
#include "global_preferences.h"
|
||||
#include "backlight_dimming_timer.h"
|
||||
#include "shared/global_context.h"
|
||||
#include "on_boarding/pop_up_controller.h"
|
||||
#include "clock_timer.h"
|
||||
#include "on_boarding/prompt_controller.h"
|
||||
|
||||
#include <ion/events.h>
|
||||
|
||||
@@ -47,8 +47,8 @@ public:
|
||||
void displayExamModePopUp(GlobalPreferences::ExamMode mode);
|
||||
void shutdownDueToLowBattery();
|
||||
void setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus newStatus);
|
||||
OnBoarding::PopUpController * promptController();
|
||||
void redrawWindow(bool force = false);
|
||||
OnBoarding::PromptController * promptController();
|
||||
void redrawWindow();
|
||||
void activateExamMode(GlobalPreferences::ExamMode examMode);
|
||||
// Exam pop-up controller delegate
|
||||
void examDeactivatingPopUpIsDismissed() override;
|
||||
@@ -74,7 +74,7 @@ private:
|
||||
MathToolbox m_mathToolbox;
|
||||
MathVariableBoxController m_variableBoxController;
|
||||
ExamPopUpController m_examPopUpController;
|
||||
OnBoarding::PopUpController m_promptController;
|
||||
OnBoarding::PromptController m_promptController;
|
||||
BatteryTimer m_batteryTimer;
|
||||
SuspendTimer m_suspendTimer;
|
||||
BacklightDimmingTimer m_backlightDimmingTimer;
|
||||
|
||||
@@ -9,7 +9,7 @@ BatteryTimer::BatteryTimer() :
|
||||
bool BatteryTimer::fire() {
|
||||
AppsContainer * container = AppsContainer::sharedAppsContainer();
|
||||
bool needRedrawing = container->updateBatteryState();
|
||||
if (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) {
|
||||
if (Ion::Battery::level() == Ion::Battery::Charge::EMPTY && !Ion::USB::isPlugged()) {
|
||||
container->shutdownDueToLowBattery();
|
||||
}
|
||||
return needRedrawing;
|
||||
|
||||
@@ -17,6 +17,7 @@ app_calculation_src = $(addprefix apps/calculation/,\
|
||||
additional_outputs/integer_list_controller.cpp \
|
||||
additional_outputs/scrollable_three_expressions_cell.cpp \
|
||||
additional_outputs/list_controller.cpp \
|
||||
additional_outputs/matrix_list_controller.cpp \
|
||||
additional_outputs/rational_list_controller.cpp \
|
||||
additional_outputs/trigonometry_graph_cell.cpp \
|
||||
additional_outputs/trigonometry_list_controller.cpp \
|
||||
|
||||
@@ -11,7 +11,7 @@ ExpressionsListController::ExpressionsListController(EditExpressionController *
|
||||
ListController(editExpressionController),
|
||||
m_cells{}
|
||||
{
|
||||
for (int i = 0; i < k_maxNumberOfCells; i++) {
|
||||
for (int i = 0; i < k_maxNumberOfRows; i++) {
|
||||
m_cells[i].setParentResponder(m_listController.selectableTableView());
|
||||
}
|
||||
}
|
||||
@@ -21,15 +21,20 @@ void ExpressionsListController::didEnterResponderChain(Responder * previousFirst
|
||||
}
|
||||
|
||||
int ExpressionsListController::reusableCellCount(int type) {
|
||||
return k_maxNumberOfCells;
|
||||
return k_maxNumberOfRows;
|
||||
}
|
||||
|
||||
void ExpressionsListController::viewDidDisappear() {
|
||||
ListController::viewDidDisappear();
|
||||
// Reset cell memoization to avoid taking extra space in the pool
|
||||
for (int i = 0; i < k_maxNumberOfCells; i++) {
|
||||
// Reset layout and cell memoization to avoid taking extra space in the pool
|
||||
for (int i = 0; i < k_maxNumberOfRows; i++) {
|
||||
m_cells[i].setLayout(Layout());
|
||||
/* By reseting m_layouts, numberOfRow will go down to 0, and the highlighted
|
||||
* cells won't be unselected. Therefore we unselect them here. */
|
||||
m_cells[i].setHighlighted(false);
|
||||
m_layouts[i] = Layout();
|
||||
}
|
||||
m_expression = Expression();
|
||||
}
|
||||
|
||||
HighlightCell * ExpressionsListController::reusableCell(int index, int type) {
|
||||
@@ -43,24 +48,34 @@ KDCoordinate ExpressionsListController::rowHeight(int j) {
|
||||
}
|
||||
|
||||
void ExpressionsListController::willDisplayCellForIndex(HighlightCell * cell, int index) {
|
||||
/* Note : To further optimize memoization space in the pool, layout
|
||||
* serialization could be memoized instead, and layout would be recomputed
|
||||
* here, when setting cell's layout. */
|
||||
ExpressionTableCellWithPointer * myCell = static_cast<ExpressionTableCellWithPointer *>(cell);
|
||||
myCell->setLayout(layoutAtIndex(index));
|
||||
myCell->setAccessoryMessage(messageAtIndex(index));
|
||||
myCell->reloadScroll();
|
||||
}
|
||||
|
||||
int ExpressionsListController::numberOfRows() const {
|
||||
int nbOfRows = 0;
|
||||
for (size_t i = 0; i < k_maxNumberOfRows; i++) {
|
||||
if (!m_layouts[i].isUninitialized()) {
|
||||
nbOfRows++;
|
||||
}
|
||||
}
|
||||
return nbOfRows;
|
||||
}
|
||||
|
||||
void ExpressionsListController::setExpression(Poincare::Expression e) {
|
||||
// Reinitialize memoization
|
||||
for (int i = 0; i < k_maxNumberOfCells; i++) {
|
||||
for (int i = 0; i < k_maxNumberOfRows; i++) {
|
||||
m_layouts[i] = Layout();
|
||||
}
|
||||
m_expression = e;
|
||||
}
|
||||
|
||||
Poincare::Layout ExpressionsListController::layoutAtIndex(int index) {
|
||||
if (m_layouts[index].isUninitialized()) {
|
||||
computeLayoutAtIndex(index);
|
||||
}
|
||||
assert(!m_layouts[index].isUninitialized());
|
||||
return m_layouts[index];
|
||||
}
|
||||
|
||||
@@ -22,22 +22,22 @@ public:
|
||||
KDCoordinate rowHeight(int j) override;
|
||||
int typeAtLocation(int i, int j) override { return 0; }
|
||||
void willDisplayCellForIndex(HighlightCell * cell, int index) override;
|
||||
int numberOfRows() const override;
|
||||
|
||||
// IllustratedListController
|
||||
void setExpression(Poincare::Expression e) override;
|
||||
|
||||
protected:
|
||||
constexpr static int k_maxNumberOfCells = 4;
|
||||
constexpr static int k_maxNumberOfRows = 5;
|
||||
int textAtIndex(char * buffer, size_t bufferSize, int index) override;
|
||||
Poincare::Expression m_expression;
|
||||
// Memoization of layouts
|
||||
mutable Poincare::Layout m_layouts[k_maxNumberOfCells];
|
||||
mutable Poincare::Layout m_layouts[k_maxNumberOfRows];
|
||||
private:
|
||||
Poincare::Layout layoutAtIndex(int index);
|
||||
virtual void computeLayoutAtIndex(int index) = 0;
|
||||
virtual I18n::Message messageAtIndex(int index) = 0;
|
||||
// Cells
|
||||
ExpressionTableCellWithPointer m_cells[k_maxNumberOfCells];
|
||||
ExpressionTableCellWithPointer m_cells[k_maxNumberOfRows];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Calculation {
|
||||
|
||||
IllustratedListController::IllustratedListController(EditExpressionController * editExpressionController) :
|
||||
ListController(editExpressionController, this),
|
||||
m_calculationStore(m_calculationStoreBuffer, k_calculationStoreBufferSize),
|
||||
m_additionalCalculationCells{}
|
||||
{
|
||||
for (int i = 0; i < k_maxNumberOfAdditionalCalculations; i++) {
|
||||
|
||||
@@ -39,7 +39,10 @@ protected:
|
||||
private:
|
||||
int textAtIndex(char * buffer, size_t bufferSize, int index) override;
|
||||
virtual CodePoint expressionSymbol() const = 0;
|
||||
// Set the size of the buffer needed to store the additional calculation
|
||||
constexpr static int k_maxNumberOfAdditionalCalculations = 4;
|
||||
constexpr static int k_calculationStoreBufferSize = k_maxNumberOfAdditionalCalculations * (sizeof(Calculation) + Calculation::k_numberOfExpressions * Constant::MaxSerializedExpressionSize + sizeof(Calculation *));
|
||||
char m_calculationStoreBuffer[k_calculationStoreBufferSize];
|
||||
// Cells
|
||||
virtual HighlightCell * illustrationCell() = 0;
|
||||
ScrollableThreeExpressionsCell m_additionalCalculationCells[k_maxNumberOfAdditionalCalculations];
|
||||
|
||||
@@ -11,10 +11,6 @@ using namespace Shared;
|
||||
|
||||
namespace Calculation {
|
||||
|
||||
int IntegerListController::numberOfRows() const {
|
||||
return 3 + factorExpressionIsComputable();
|
||||
}
|
||||
|
||||
Integer::Base baseAtIndex(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
@@ -27,12 +23,20 @@ Integer::Base baseAtIndex(int index) {
|
||||
}
|
||||
}
|
||||
|
||||
void IntegerListController::computeLayoutAtIndex(int index) {
|
||||
assert(m_expression.type() == ExpressionNode::Type::BasedInteger);
|
||||
// For index = k_indexOfFactorExpression, the layout is assumed to be alreday memoized because it is needed to compute the numberOfRows
|
||||
assert(index < k_indexOfFactorExpression);
|
||||
Integer i = static_cast<BasedInteger &>(m_expression).integer();
|
||||
m_layouts[index] = i.createLayout(baseAtIndex(index));
|
||||
void IntegerListController::setExpression(Poincare::Expression e) {
|
||||
ExpressionsListController::setExpression(e);
|
||||
static_assert(k_maxNumberOfRows >= k_indexOfFactorExpression + 1, "k_maxNumberOfRows must be greater than k_indexOfFactorExpression");
|
||||
assert(!m_expression.isUninitialized() && m_expression.type() == ExpressionNode::Type::BasedInteger);
|
||||
Integer integer = static_cast<BasedInteger &>(m_expression).integer();
|
||||
for (int index = 0; index < k_indexOfFactorExpression; ++index) {
|
||||
m_layouts[index] = integer.createLayout(baseAtIndex(index));
|
||||
}
|
||||
// Computing factorExpression
|
||||
Expression factor = Factor::Builder(m_expression.clone());
|
||||
PoincareHelpers::Simplify(&factor, App::app()->localContext(), ExpressionNode::ReductionTarget::User);
|
||||
if (!factor.isUndefined()) {
|
||||
m_layouts[k_indexOfFactorExpression] = PoincareHelpers::CreateLayout(factor);
|
||||
}
|
||||
}
|
||||
|
||||
I18n::Message IntegerListController::messageAtIndex(int index) {
|
||||
@@ -48,20 +52,4 @@ I18n::Message IntegerListController::messageAtIndex(int index) {
|
||||
}
|
||||
}
|
||||
|
||||
bool IntegerListController::factorExpressionIsComputable() const {
|
||||
if (!m_layouts[k_indexOfFactorExpression].isUninitialized()) {
|
||||
// The factor expression is already memoized
|
||||
return !m_layouts[k_indexOfFactorExpression].isEmpty();
|
||||
}
|
||||
Poincare::Context * context = App::app()->localContext();
|
||||
Expression factor = Factor::Builder(m_expression.clone());
|
||||
PoincareHelpers::Simplify(&factor, context, ExpressionNode::ReductionTarget::User);
|
||||
if (!factor.isUndefined()) {
|
||||
m_layouts[k_indexOfFactorExpression] = PoincareHelpers::CreateLayout(factor);
|
||||
return true;
|
||||
}
|
||||
m_layouts[k_indexOfFactorExpression] = EmptyLayout::Builder();
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,13 +10,11 @@ public:
|
||||
IntegerListController(EditExpressionController * editExpressionController) :
|
||||
ExpressionsListController(editExpressionController) {}
|
||||
|
||||
//ListViewDataSource
|
||||
int numberOfRows() const override;
|
||||
void setExpression(Poincare::Expression e) override;
|
||||
|
||||
private:
|
||||
static constexpr int k_indexOfFactorExpression = 3;
|
||||
void computeLayoutAtIndex(int index) override;
|
||||
I18n::Message messageAtIndex(int index) override;
|
||||
bool factorExpressionIsComputable() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
106
apps/calculation/additional_outputs/matrix_list_controller.cpp
Normal file
106
apps/calculation/additional_outputs/matrix_list_controller.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
#include "matrix_list_controller.h"
|
||||
#include "../app.h"
|
||||
#include "../../shared/poincare_helpers.h"
|
||||
#include <apps/global_preferences.h>
|
||||
#include <poincare_nodes.h>
|
||||
#include <poincare/matrix.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace Poincare;
|
||||
using namespace Shared;
|
||||
|
||||
namespace Calculation {
|
||||
|
||||
void MatrixListController::setExpression(Poincare::Expression e) {
|
||||
ExpressionsListController::setExpression(e);
|
||||
assert(!m_expression.isUninitialized());
|
||||
static_assert(k_maxNumberOfRows >= k_maxNumberOfOutputRows, "k_maxNumberOfRows must be greater than k_maxNumberOfOutputRows");
|
||||
|
||||
Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences();
|
||||
Poincare::Preferences::ComplexFormat currentComplexFormat = preferences->complexFormat();
|
||||
if (currentComplexFormat == Poincare::Preferences::ComplexFormat::Real) {
|
||||
/* Temporary change complex format to avoid all additional expressions to be
|
||||
* "unreal" (with [i] for instance). As additional results are computed from
|
||||
* the output, which is built taking ComplexFormat into account, there are
|
||||
* no risks of displaying additional results on an unreal output. */
|
||||
preferences->setComplexFormat(Poincare::Preferences::ComplexFormat::Cartesian);
|
||||
}
|
||||
|
||||
Context * context = App::app()->localContext();
|
||||
ExpressionNode::ReductionContext reductionContext(
|
||||
context,
|
||||
preferences->complexFormat(),
|
||||
preferences->angleUnit(),
|
||||
GlobalPreferences::sharedGlobalPreferences()->unitFormat(),
|
||||
ExpressionNode::ReductionTarget::SystemForApproximation,
|
||||
ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined);
|
||||
|
||||
// The expression must be reduced to call methods such as determinant or trace
|
||||
assert(m_expression.type() == ExpressionNode::Type::Matrix);
|
||||
|
||||
bool mIsSquared = (static_cast<Matrix &>(m_expression).numberOfRows() == static_cast<Matrix &>(m_expression).numberOfColumns());
|
||||
size_t index = 0;
|
||||
size_t messageIndex = 0;
|
||||
// 1. Matrix determinant if square matrix
|
||||
if (mIsSquared) {
|
||||
/* Determinant is reduced so that a null determinant can be detected.
|
||||
* However, some exceptions remain such as cos(x)^2+sin(x)^2-1 which will
|
||||
* not be reduced to a rational, but will be null in theory. */
|
||||
Expression determinant = Determinant::Builder(m_expression.clone()).reduce(reductionContext);
|
||||
m_indexMessageMap[index] = messageIndex++;
|
||||
m_layouts[index++] = getLayoutFromExpression(determinant, context, preferences);
|
||||
// 2. Matrix inverse if invertible matrix
|
||||
// A squared matrix is invertible if and only if determinant is non null
|
||||
if (!determinant.isUndefined() && determinant.nullStatus(context) != ExpressionNode::NullStatus::Null) {
|
||||
// TODO: Handle ExpressionNode::NullStatus::Unknown
|
||||
m_indexMessageMap[index] = messageIndex++;
|
||||
m_layouts[index++] = getLayoutFromExpression(MatrixInverse::Builder(m_expression.clone()), context, preferences);
|
||||
}
|
||||
}
|
||||
// 3. Matrix row echelon form
|
||||
messageIndex = 2;
|
||||
Expression rowEchelonForm = MatrixRowEchelonForm::Builder(m_expression.clone());
|
||||
m_indexMessageMap[index] = messageIndex++;
|
||||
m_layouts[index++] = getLayoutFromExpression(rowEchelonForm, context, preferences);
|
||||
/* 4. Matrix reduced row echelon form
|
||||
* it can be computed from row echelon form to save computation time.*/
|
||||
m_indexMessageMap[index] = messageIndex++;
|
||||
m_layouts[index++] = getLayoutFromExpression(MatrixReducedRowEchelonForm::Builder(rowEchelonForm), context, preferences);
|
||||
// 5. Matrix trace if square matrix
|
||||
if (mIsSquared) {
|
||||
m_indexMessageMap[index] = messageIndex++;
|
||||
m_layouts[index++] = getLayoutFromExpression(MatrixTrace::Builder(m_expression.clone()), context, preferences);
|
||||
}
|
||||
// Reset complex format as before
|
||||
preferences->setComplexFormat(currentComplexFormat);
|
||||
}
|
||||
|
||||
Poincare::Layout MatrixListController::getLayoutFromExpression(Expression e, Context * context, Poincare::Preferences * preferences) {
|
||||
assert(!e.isUninitialized());
|
||||
// Simplify or approximate expression
|
||||
Expression approximateExpression;
|
||||
Expression simplifiedExpression;
|
||||
e.simplifyAndApproximate(&simplifiedExpression, &approximateExpression, context,
|
||||
preferences->complexFormat(), preferences->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat(),
|
||||
ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined);
|
||||
// simplify might have been interrupted, in which case we use approximate
|
||||
if (simplifiedExpression.isUninitialized()) {
|
||||
assert(!approximateExpression.isUninitialized());
|
||||
return Shared::PoincareHelpers::CreateLayout(approximateExpression);
|
||||
}
|
||||
return Shared::PoincareHelpers::CreateLayout(simplifiedExpression);
|
||||
}
|
||||
|
||||
I18n::Message MatrixListController::messageAtIndex(int index) {
|
||||
// Message index is mapped in setExpression because it depends on the Matrix.
|
||||
assert(index < k_maxNumberOfOutputRows && index >=0);
|
||||
I18n::Message messages[k_maxNumberOfOutputRows] = {
|
||||
I18n::Message::AdditionalDeterminant,
|
||||
I18n::Message::AdditionalInverse,
|
||||
I18n::Message::AdditionalRowEchelonForm,
|
||||
I18n::Message::AdditionalReducedRowEchelonForm,
|
||||
I18n::Message::AdditionalTrace};
|
||||
return messages[m_indexMessageMap[index]];
|
||||
}
|
||||
|
||||
}
|
||||
27
apps/calculation/additional_outputs/matrix_list_controller.h
Normal file
27
apps/calculation/additional_outputs/matrix_list_controller.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef CALCULATION_ADDITIONAL_OUTPUTS_MATRIX_LIST_CONTROLLER_H
|
||||
#define CALCULATION_ADDITIONAL_OUTPUTS_MATRIX_LIST_CONTROLLER_H
|
||||
|
||||
#include "expressions_list_controller.h"
|
||||
|
||||
namespace Calculation {
|
||||
|
||||
class MatrixListController : public ExpressionsListController {
|
||||
public:
|
||||
MatrixListController(EditExpressionController * editExpressionController) :
|
||||
ExpressionsListController(editExpressionController) {}
|
||||
|
||||
void setExpression(Poincare::Expression e) override;
|
||||
|
||||
private:
|
||||
I18n::Message messageAtIndex(int index) override;
|
||||
Poincare::Layout getLayoutFromExpression(Poincare::Expression e, Poincare::Context * context, Poincare::Preferences * preferences);
|
||||
// Map from cell index to message index
|
||||
constexpr static int k_maxNumberOfOutputRows = 5;
|
||||
int m_indexMessageMap[k_maxNumberOfOutputRows];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -9,34 +9,31 @@ using namespace Shared;
|
||||
|
||||
namespace Calculation {
|
||||
|
||||
int RationalListController::numberOfRows() const {
|
||||
return 2;
|
||||
}
|
||||
|
||||
Integer extractInteger(const Expression e) {
|
||||
assert(e.type() == ExpressionNode::Type::BasedInteger);
|
||||
return static_cast<const BasedInteger &>(e).integer();
|
||||
}
|
||||
|
||||
void RationalListController::computeLayoutAtIndex(int index) {
|
||||
void RationalListController::setExpression(Poincare::Expression e) {
|
||||
ExpressionsListController::setExpression(e);
|
||||
assert(!m_expression.isUninitialized());
|
||||
static_assert(k_maxNumberOfRows >= 2, "k_maxNumberOfRows must be greater than 2");
|
||||
|
||||
bool negative = false;
|
||||
Expression div = m_expression;
|
||||
if (m_expression.type() == ExpressionNode::Type::Opposite) {
|
||||
negative = true;
|
||||
div = m_expression.childAtIndex(0);
|
||||
}
|
||||
|
||||
assert(div.type() == ExpressionNode::Type::Division);
|
||||
Integer numerator = extractInteger(div.childAtIndex(0));
|
||||
numerator.setNegative(negative);
|
||||
Integer denominator = extractInteger(div.childAtIndex(1));
|
||||
Expression e;
|
||||
if (index == 0) {
|
||||
e = Integer::CreateMixedFraction(numerator, denominator);
|
||||
} else {
|
||||
assert(index == 1);
|
||||
e = Integer::CreateEuclideanDivision(numerator, denominator);
|
||||
}
|
||||
m_layouts[index] = PoincareHelpers::CreateLayout(e);
|
||||
|
||||
int index = 0;
|
||||
m_layouts[index++] = PoincareHelpers::CreateLayout(Integer::CreateMixedFraction(numerator, denominator));
|
||||
m_layouts[index++] = PoincareHelpers::CreateLayout(Integer::CreateEuclideanDivision(numerator, denominator));
|
||||
}
|
||||
|
||||
I18n::Message RationalListController::messageAtIndex(int index) {
|
||||
|
||||
@@ -10,10 +10,9 @@ public:
|
||||
RationalListController(EditExpressionController * editExpressionController) :
|
||||
ExpressionsListController(editExpressionController) {}
|
||||
|
||||
//ListViewDataSource
|
||||
int numberOfRows() const override;
|
||||
void setExpression(Poincare::Expression e) override;
|
||||
|
||||
private:
|
||||
void computeLayoutAtIndex(int index) override;
|
||||
I18n::Message messageAtIndex(int index) override;
|
||||
int textAtIndex(char * buffer, size_t bufferSize, int index) override;
|
||||
};
|
||||
|
||||
@@ -15,112 +15,62 @@ namespace Calculation {
|
||||
void UnitListController::setExpression(Poincare::Expression e) {
|
||||
ExpressionsListController::setExpression(e);
|
||||
assert(!m_expression.isUninitialized());
|
||||
// Reinitialize m_memoizedExpressions
|
||||
for (size_t i = 0; i < k_maxNumberOfCells; i++) {
|
||||
m_memoizedExpressions[i] = Expression();
|
||||
static_assert(k_maxNumberOfRows >= 3, "k_maxNumberOfRows must be greater than 3");
|
||||
|
||||
Poincare::Expression expressions[k_maxNumberOfRows];
|
||||
// Initialize expressions
|
||||
for (size_t i = 0; i < k_maxNumberOfRows; i++) {
|
||||
expressions[i] = Expression();
|
||||
}
|
||||
|
||||
size_t numberOfMemoizedExpressions = 0;
|
||||
// 1. First rows: miscellaneous classic units for some dimensions
|
||||
/* 1. First rows: miscellaneous classic units for some dimensions, in both
|
||||
* metric and imperial units. */
|
||||
Expression copy = m_expression.clone();
|
||||
Expression units;
|
||||
// Reduce to be able to recognize units
|
||||
PoincareHelpers::Reduce(©, App::app()->localContext(), ExpressionNode::ReductionTarget::User);
|
||||
copy = copy.removeUnit(&units);
|
||||
bool requireSimplification = false;
|
||||
bool canChangeUnitPrefix = false;
|
||||
PoincareHelpers::ReduceAndRemoveUnit(©, App::app()->localContext(), ExpressionNode::ReductionTarget::User, &units);
|
||||
double value = Shared::PoincareHelpers::ApproximateToScalar<double>(copy, App::app()->localContext());
|
||||
ExpressionNode::ReductionContext reductionContext(
|
||||
App::app()->localContext(),
|
||||
Preferences::sharedPreferences()->complexFormat(),
|
||||
Preferences::sharedPreferences()->angleUnit(),
|
||||
GlobalPreferences::sharedGlobalPreferences()->unitFormat(),
|
||||
ExpressionNode::ReductionTarget::User,
|
||||
ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined);
|
||||
int numberOfExpressions = Unit::SetAdditionalExpressions(units, value, expressions, k_maxNumberOfRows, reductionContext);
|
||||
|
||||
if (Unit::IsSISpeed(units)) {
|
||||
// 1.a. Turn speed into km/h
|
||||
m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder(
|
||||
m_expression.clone(),
|
||||
Multiplication::Builder(
|
||||
Unit::Kilometer(),
|
||||
Power::Builder(
|
||||
Unit::Hour(),
|
||||
Rational::Builder(-1)
|
||||
)
|
||||
)
|
||||
);
|
||||
requireSimplification = true; // Simplify the conversion
|
||||
} else if (Unit::IsSIVolume(units)) {
|
||||
// 1.b. Turn volume into L
|
||||
m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder(
|
||||
m_expression.clone(),
|
||||
Unit::Liter()
|
||||
);
|
||||
requireSimplification = true; // Simplify the conversion
|
||||
canChangeUnitPrefix = true; // Pick best prefix (mL)
|
||||
} else if (Unit::IsSIEnergy(units)) {
|
||||
// 1.c. Turn energy into Wh
|
||||
m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder(
|
||||
m_expression.clone(),
|
||||
Multiplication::Builder(
|
||||
Unit::Watt(),
|
||||
Unit::Hour()
|
||||
)
|
||||
);
|
||||
m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder(
|
||||
m_expression.clone(),
|
||||
Unit::ElectronVolt()
|
||||
);
|
||||
requireSimplification = true; // Simplify the conversion
|
||||
canChangeUnitPrefix = true; // Pick best prefix (kWh)
|
||||
} else if (Unit::IsSITime(units)) {
|
||||
// Turn time into ? year + ? month + ? day + ? h + ? min + ? s
|
||||
double value = Shared::PoincareHelpers::ApproximateToScalar<double>(copy, App::app()->localContext());
|
||||
m_memoizedExpressions[numberOfMemoizedExpressions++] = Unit::BuildTimeSplit(value, App::app()->localContext(), Preferences::sharedPreferences()->complexFormat(), Preferences::sharedPreferences()->angleUnit());
|
||||
}
|
||||
// 1.d. Simplify and tune prefix of all computed expressions
|
||||
size_t currentExpressionIndex = 0;
|
||||
while (currentExpressionIndex < numberOfMemoizedExpressions) {
|
||||
assert(!m_memoizedExpressions[currentExpressionIndex].isUninitialized());
|
||||
if (requireSimplification) {
|
||||
Shared::PoincareHelpers::Simplify(&m_memoizedExpressions[currentExpressionIndex], App::app()->localContext(), ExpressionNode::ReductionTarget::User);
|
||||
}
|
||||
if (canChangeUnitPrefix) {
|
||||
Expression newUnits;
|
||||
// Reduce to be able to removeUnit
|
||||
PoincareHelpers::Reduce(&m_memoizedExpressions[currentExpressionIndex], App::app()->localContext(), ExpressionNode::ReductionTarget::User);
|
||||
m_memoizedExpressions[currentExpressionIndex] = m_memoizedExpressions[currentExpressionIndex].removeUnit(&newUnits);
|
||||
double value = Shared::PoincareHelpers::ApproximateToScalar<double>(m_memoizedExpressions[currentExpressionIndex], App::app()->localContext());
|
||||
ExpressionNode::ReductionContext reductionContext(
|
||||
App::app()->localContext(),
|
||||
Preferences::sharedPreferences()->complexFormat(),
|
||||
Preferences::sharedPreferences()->angleUnit(),
|
||||
ExpressionNode::ReductionTarget::User,
|
||||
ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined);
|
||||
Unit::ChooseBestPrefixForValue(&newUnits, &value, reductionContext);
|
||||
m_memoizedExpressions[currentExpressionIndex] = Multiplication::Builder(Number::FloatNumber(value), newUnits);
|
||||
}
|
||||
currentExpressionIndex++;
|
||||
}
|
||||
// 2. SI units only
|
||||
assert(numberOfExpressions < k_maxNumberOfRows - 1);
|
||||
expressions[numberOfExpressions] = m_expression.clone();
|
||||
Shared::PoincareHelpers::Simplify(&expressions[numberOfExpressions], App::app()->localContext(), ExpressionNode::ReductionTarget::User, Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::InternationalSystem);
|
||||
numberOfExpressions++;
|
||||
|
||||
// 2. IS units only
|
||||
assert(numberOfMemoizedExpressions < k_maxNumberOfCells - 1);
|
||||
m_memoizedExpressions[numberOfMemoizedExpressions] = m_expression.clone();
|
||||
Shared::PoincareHelpers::Simplify(&m_memoizedExpressions[numberOfMemoizedExpressions], App::app()->localContext(), ExpressionNode::ReductionTarget::User, Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::InternationalSystem);
|
||||
numberOfMemoizedExpressions++;
|
||||
|
||||
// 3. Get rid of duplicates
|
||||
/* 3. Get rid of duplicates
|
||||
* We find duplicates by comparing the serializations, to eliminate
|
||||
* expressions that only differ by the types of their number nodes. */
|
||||
Expression reduceExpression = m_expression.clone();
|
||||
// Make m_expression compareable to m_memoizedExpressions (turn BasedInteger into Rational for instance)
|
||||
// Make m_expression comparable to expressions (turn BasedInteger into Rational for instance)
|
||||
Shared::PoincareHelpers::Simplify(&reduceExpression, App::app()->localContext(), ExpressionNode::ReductionTarget::User, Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::None);
|
||||
currentExpressionIndex = 1;
|
||||
while (currentExpressionIndex < numberOfMemoizedExpressions) {
|
||||
int currentExpressionIndex = 0;
|
||||
while (currentExpressionIndex < numberOfExpressions) {
|
||||
bool duplicateFound = false;
|
||||
for (size_t i = 0; i < currentExpressionIndex + 1; i++) {
|
||||
// Compare the currentExpression to all previous memoized expressions and to m_expression
|
||||
Expression comparedExpression = i == currentExpressionIndex ? reduceExpression : m_memoizedExpressions[i];
|
||||
constexpr int buffersSize = Constant::MaxSerializedExpressionSize;
|
||||
char buffer1[buffersSize];
|
||||
int size1 = PoincareHelpers::Serialize(expressions[currentExpressionIndex], buffer1, buffersSize);
|
||||
for (int i = 0; i < currentExpressionIndex + 1; i++) {
|
||||
// Compare the currentExpression to all previous expressions and to m_expression
|
||||
Expression comparedExpression = i == currentExpressionIndex ? reduceExpression : expressions[i];
|
||||
assert(!comparedExpression.isUninitialized());
|
||||
if (comparedExpression.isIdenticalTo(m_memoizedExpressions[currentExpressionIndex])) {
|
||||
numberOfMemoizedExpressions--;
|
||||
char buffer2[buffersSize];
|
||||
int size2 = PoincareHelpers::Serialize(comparedExpression, buffer2, buffersSize);
|
||||
if (size1 == size2 && strcmp(buffer1, buffer2) == 0) {
|
||||
numberOfExpressions--;
|
||||
// Shift next expressions
|
||||
for (size_t j = currentExpressionIndex; j < numberOfMemoizedExpressions; j++) {
|
||||
m_memoizedExpressions[j] = m_memoizedExpressions[j+1];
|
||||
for (int j = currentExpressionIndex; j < numberOfExpressions; j++) {
|
||||
expressions[j] = expressions[j+1];
|
||||
}
|
||||
// Remove last expression
|
||||
m_memoizedExpressions[numberOfMemoizedExpressions] = Expression();
|
||||
expressions[numberOfExpressions] = Expression();
|
||||
// The current expression has been discarded, no need to increment the current index
|
||||
duplicateFound = true;
|
||||
break;
|
||||
@@ -131,21 +81,12 @@ void UnitListController::setExpression(Poincare::Expression e) {
|
||||
currentExpressionIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int UnitListController::numberOfRows() const {
|
||||
int nbOfRows = 0;
|
||||
for (size_t i = 0; i < k_maxNumberOfCells; i++) {
|
||||
if (!m_memoizedExpressions[i].isUninitialized()) {
|
||||
nbOfRows++;
|
||||
// Memoize layouts
|
||||
for (size_t i = 0; i < k_maxNumberOfRows; i++) {
|
||||
if (!expressions[i].isUninitialized()) {
|
||||
m_layouts[i] = Shared::PoincareHelpers::CreateLayout(expressions[i]);
|
||||
}
|
||||
}
|
||||
return nbOfRows;
|
||||
}
|
||||
|
||||
void UnitListController::computeLayoutAtIndex(int index) {
|
||||
assert(!m_memoizedExpressions[index].isUninitialized());
|
||||
m_layouts[index] = Shared::PoincareHelpers::CreateLayout(m_memoizedExpressions[index]);
|
||||
}
|
||||
|
||||
I18n::Message UnitListController::messageAtIndex(int index) {
|
||||
|
||||
@@ -12,13 +12,8 @@ public:
|
||||
|
||||
void setExpression(Poincare::Expression e) override;
|
||||
|
||||
//ListViewDataSource
|
||||
int numberOfRows() const override;
|
||||
private:
|
||||
void computeLayoutAtIndex(int index) override;
|
||||
I18n::Message messageAtIndex(int index) override;
|
||||
// Memoization of expressions
|
||||
mutable Poincare::Expression m_memoizedExpressions[k_maxNumberOfCells];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ App * App::Snapshot::unpack(Container * container) {
|
||||
|
||||
void App::Snapshot::reset() {
|
||||
m_calculationStore.deleteAll();
|
||||
m_cacheBuffer[0] = 0;
|
||||
m_cacheBufferInformation = 0;
|
||||
}
|
||||
|
||||
App::Descriptor * App::Snapshot::descriptor() {
|
||||
@@ -38,14 +40,14 @@ App::Descriptor * App::Snapshot::descriptor() {
|
||||
return &descriptor;
|
||||
}
|
||||
|
||||
void App::Snapshot::tidy() {
|
||||
m_calculationStore.tidy();
|
||||
App::Snapshot::Snapshot() : m_calculationStore(m_calculationBuffer, k_calculationBufferSize)
|
||||
{
|
||||
}
|
||||
|
||||
App::App(Snapshot * snapshot) :
|
||||
ExpressionFieldDelegateApp(snapshot, &m_editExpressionController),
|
||||
m_historyController(&m_editExpressionController, snapshot->calculationStore()),
|
||||
m_editExpressionController(&m_modalViewController, this, &m_historyController, snapshot->calculationStore())
|
||||
m_editExpressionController(&m_modalViewController, this, snapshot->cacheBuffer(), snapshot->cacheBufferInformationAddress(), &m_historyController, snapshot->calculationStore())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -70,7 +72,17 @@ bool App::isAcceptableExpression(const Poincare::Expression expression) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !expression.isUninitialized();
|
||||
return !(expression.isUninitialized() || expression.type() == ExpressionNode::Type::Equal);
|
||||
}
|
||||
|
||||
void App::didBecomeActive(Window * window) {
|
||||
m_editExpressionController.restoreInput();
|
||||
Shared::ExpressionFieldDelegateApp::didBecomeActive(window);
|
||||
}
|
||||
|
||||
void App::willBecomeInactive() {
|
||||
m_editExpressionController.memoizeInput();
|
||||
Shared::ExpressionFieldDelegateApp::willBecomeInactive();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "history_controller.h"
|
||||
#include "../shared/text_field_delegate_app.h"
|
||||
#include <escher.h>
|
||||
#include "../shared/shared_app.h"
|
||||
|
||||
namespace Calculation {
|
||||
|
||||
@@ -18,15 +19,22 @@ public:
|
||||
App::Descriptor::ExaminationLevel examinationLevel() override;
|
||||
const Image * icon() override;
|
||||
};
|
||||
class Snapshot : public ::App::Snapshot {
|
||||
class Snapshot : public ::SharedApp::Snapshot {
|
||||
public:
|
||||
Snapshot();
|
||||
App * unpack(Container * container) override;
|
||||
void reset() override;
|
||||
Descriptor * descriptor() override;
|
||||
CalculationStore * calculationStore() { return &m_calculationStore; }
|
||||
char * cacheBuffer() { return m_cacheBuffer; }
|
||||
size_t * cacheBufferInformationAddress() { return &m_cacheBufferInformation; }
|
||||
private:
|
||||
void tidy() override;
|
||||
CalculationStore m_calculationStore;
|
||||
// Set the size of the buffer needed to store the calculations
|
||||
static constexpr int k_calculationBufferSize = 10 * (sizeof(Calculation) + Calculation::k_numberOfExpressions * Constant::MaxSerializedExpressionSize + sizeof(Calculation *));
|
||||
char m_calculationBuffer[k_calculationBufferSize];
|
||||
char m_cacheBuffer[EditExpressionController::k_cacheBufferSize];
|
||||
size_t m_cacheBufferInformation;
|
||||
};
|
||||
static App * app() {
|
||||
return static_cast<App *>(Container::activeApp());
|
||||
@@ -36,9 +44,12 @@ public:
|
||||
bool layoutFieldDidReceiveEvent(::LayoutField * layoutField, Ion::Events::Event event) override;
|
||||
// TextFieldDelegateApp
|
||||
bool isAcceptableExpression(const Poincare::Expression expression) override;
|
||||
|
||||
private:
|
||||
App(Snapshot * snapshot);
|
||||
HistoryController m_historyController;
|
||||
void didBecomeActive(Window * window) override;
|
||||
void willBecomeInactive() override;
|
||||
EditExpressionController m_editExpressionController;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,3 +7,8 @@ BinaryBase = "Binär"
|
||||
PrimeFactors = "Primfaktoren"
|
||||
MixedFraction = "Gemischte Zahl"
|
||||
EuclideanDivision = "Division mit Rest"
|
||||
AdditionalDeterminant = "Determinante"
|
||||
AdditionalInverse = "Inverse"
|
||||
AdditionalRowEchelonForm = "Stufenform"
|
||||
AdditionalReducedRowEchelonForm = "Reduzierte Stufenform"
|
||||
AdditionalTrace = "Spur"
|
||||
@@ -7,3 +7,8 @@ BinaryBase = "Binary"
|
||||
PrimeFactors = "Prime factors"
|
||||
MixedFraction = "Mixed fraction"
|
||||
EuclideanDivision = "Euclidean division"
|
||||
AdditionalDeterminant = "Determinant"
|
||||
AdditionalInverse = "Inverse"
|
||||
AdditionalRowEchelonForm = "Row echelon form"
|
||||
AdditionalReducedRowEchelonForm = "Reduced row echelon form"
|
||||
AdditionalTrace = "Trace"
|
||||
@@ -7,3 +7,8 @@ BinaryBase = "Binario"
|
||||
PrimeFactors = "Factores primos"
|
||||
MixedFraction = "Fracción mixta"
|
||||
EuclideanDivision = "División euclidiana"
|
||||
AdditionalDeterminant = "Determinante"
|
||||
AdditionalInverse = "Inversa"
|
||||
AdditionalRowEchelonForm = "Matriz escalonada"
|
||||
AdditionalReducedRowEchelonForm = "Matriz escalonada reducida"
|
||||
AdditionalTrace = "Traza"
|
||||
@@ -7,3 +7,8 @@ BinaryBase = "Binaire"
|
||||
PrimeFactors = "Facteurs premiers"
|
||||
MixedFraction = "Fraction mixte"
|
||||
EuclideanDivision = "Division euclidienne"
|
||||
AdditionalDeterminant = "Déterminant"
|
||||
AdditionalInverse = "Inverse"
|
||||
AdditionalRowEchelonForm = "Forme échelonnée"
|
||||
AdditionalReducedRowEchelonForm = "Forme échelonnée réduite"
|
||||
AdditionalTrace = "Trace"
|
||||
@@ -4,6 +4,11 @@ AdditionalResults = "Risultati complementari"
|
||||
DecimalBase = "Decimale"
|
||||
HexadecimalBase = "Esadecimale"
|
||||
BinaryBase = "Binario"
|
||||
PrimeFactors = "Fattori primi"
|
||||
PrimeFactors = "Fattorizzazione"
|
||||
MixedFraction = "Frazione mista"
|
||||
EuclideanDivision = "Divisione euclidea"
|
||||
AdditionalDeterminant = "Determinante"
|
||||
AdditionalInverse = "Inversa"
|
||||
AdditionalRowEchelonForm = "Matrice a scalini"
|
||||
AdditionalReducedRowEchelonForm = "Matrice ridotta a scalini"
|
||||
AdditionalTrace = "Traccia"
|
||||
@@ -1,9 +1,14 @@
|
||||
CalculApp = "Calculatie"
|
||||
CalculAppCapital = "CALCULATIE"
|
||||
AdditionalResults = "Bijkomende resultaten"
|
||||
CalculApp = "Rekenen"
|
||||
CalculAppCapital = "REKENEN"
|
||||
AdditionalResults = "Aanvullende resultaten"
|
||||
DecimalBase = "Decimaal"
|
||||
HexadecimalBase = "Hexadecimaal"
|
||||
BinaryBase = "Binaire"
|
||||
PrimeFactors = "Priemfactoren"
|
||||
BinaryBase = "Binair"
|
||||
PrimeFactors = "Ontbinding"
|
||||
MixedFraction = "Gemengde breuk"
|
||||
EuclideanDivision = "Geheeltallige deling"
|
||||
AdditionalDeterminant = "Determinant"
|
||||
AdditionalInverse = "Inverse"
|
||||
AdditionalRowEchelonForm = "Echelonvorm"
|
||||
AdditionalReducedRowEchelonForm = "Gereduceerde echelonvorm"
|
||||
AdditionalTrace = "Spoor"
|
||||
@@ -4,6 +4,11 @@ AdditionalResults = "Resultados adicionais"
|
||||
DecimalBase = "Decimal"
|
||||
HexadecimalBase = "Hexadecimal"
|
||||
BinaryBase = "Binário"
|
||||
PrimeFactors = "Fatores primos"
|
||||
PrimeFactors = "Fatorização"
|
||||
MixedFraction = "Fração mista"
|
||||
EuclideanDivision = "Divisão euclidiana"
|
||||
AdditionalDeterminant = "Determinante"
|
||||
AdditionalInverse = "Matriz inversa"
|
||||
AdditionalRowEchelonForm = "Matriz escalonada"
|
||||
AdditionalReducedRowEchelonForm = "Matriz escalonada reduzida"
|
||||
AdditionalTrace = "Traço"
|
||||
@@ -169,7 +169,8 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) {
|
||||
ExpressionNode::Type::Sum,
|
||||
ExpressionNode::Type::Derivative,
|
||||
ExpressionNode::Type::ConfidenceInterval,
|
||||
ExpressionNode::Type::PredictionInterval
|
||||
ExpressionNode::Type::PredictionInterval,
|
||||
ExpressionNode::Type::Sequence
|
||||
};
|
||||
return e.isOfType(approximateOnlyTypes, sizeof(approximateOnlyTypes)/sizeof(ExpressionNode::Type));
|
||||
}, context)
|
||||
@@ -218,7 +219,7 @@ Calculation::EqualSign Calculation::exactAndApproximateDisplayedOutputsAreEqual(
|
||||
Preferences * preferences = Preferences::sharedPreferences();
|
||||
// TODO: complex format should not be needed here (as it is not used to create layouts)
|
||||
Preferences::ComplexFormat complexFormat = Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), m_inputText);
|
||||
m_equalSign = Expression::ParsedExpressionsAreEqual(exactOutputText(), approximateOutputText(NumberOfSignificantDigits::UserDefined), context, complexFormat, preferences->angleUnit()) ? EqualSign::Equal : EqualSign::Approximation;
|
||||
m_equalSign = Expression::ParsedExpressionsAreEqual(exactOutputText(), approximateOutputText(NumberOfSignificantDigits::UserDefined), context, complexFormat, preferences->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat()) ? EqualSign::Equal : EqualSign::Approximation;
|
||||
return m_equalSign;
|
||||
} else {
|
||||
/* Do not override m_equalSign in case there is enough room in the pool
|
||||
@@ -254,32 +255,9 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co
|
||||
}
|
||||
if (o.hasUnit()) {
|
||||
Expression unit;
|
||||
PoincareHelpers::Reduce(&o,
|
||||
App::app()->localContext(),
|
||||
ExpressionNode::ReductionTarget::User,
|
||||
ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined,
|
||||
ExpressionNode::UnitConversion::None);
|
||||
o = o.removeUnit(&unit);
|
||||
// There might be no unit in the end, if the reduction was interrupted.
|
||||
if (!unit.isUninitialized()) {
|
||||
if (Unit::IsSI(unit)) {
|
||||
if (Unit::IsSISpeed(unit) || Unit::IsSIVolume(unit) || Unit::IsSIEnergy(unit)) {
|
||||
/* All these units will provide misc. classic representatives in
|
||||
* addition to the SI unit in additional information. */
|
||||
return AdditionalInformationType::Unit;
|
||||
}
|
||||
if (Unit::IsSITime(unit)) {
|
||||
/* If the number of seconds is above 60s, we can write it in the form
|
||||
* of an addition: 23_min + 12_s for instance. */
|
||||
double value = Shared::PoincareHelpers::ApproximateToScalar<double>(o, App::app()->localContext());
|
||||
if (value > Unit::SecondsPerMinute) {
|
||||
return AdditionalInformationType::Unit;
|
||||
}
|
||||
}
|
||||
return AdditionalInformationType::None;
|
||||
}
|
||||
return AdditionalInformationType::Unit;
|
||||
}
|
||||
PoincareHelpers::ReduceAndRemoveUnit(&o, App::app()->localContext(), ExpressionNode::ReductionTarget::User, &unit, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined, ExpressionNode::UnitConversion::None);
|
||||
double value = PoincareHelpers::ApproximateToScalar<double>(o, App::app()->localContext());
|
||||
return (Unit::ShouldDisplayAdditionalOutputs(value, unit, GlobalPreferences::sharedGlobalPreferences()->unitFormat())) ? AdditionalInformationType::Unit : AdditionalInformationType::None;
|
||||
}
|
||||
if (o.isBasedIntegerCappedBy(k_maximalIntegerWithAdditionalInformation)) {
|
||||
return AdditionalInformationType::Integer;
|
||||
@@ -291,6 +269,9 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co
|
||||
if (o.hasDefinedComplexApproximation(context, complexFormat, preferences->angleUnit())) {
|
||||
return AdditionalInformationType::Complex;
|
||||
}
|
||||
if (o.type() == ExpressionNode::Type::Matrix) {
|
||||
return AdditionalInformationType::Matrix;
|
||||
}
|
||||
return AdditionalInformationType::None;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ class CalculationStore;
|
||||
class Calculation {
|
||||
friend CalculationStore;
|
||||
public:
|
||||
static constexpr int k_numberOfExpressions = 4;
|
||||
enum class EqualSign : uint8_t {
|
||||
Unknown,
|
||||
Approximation,
|
||||
@@ -40,6 +41,7 @@ public:
|
||||
Rational,
|
||||
Trigonometry,
|
||||
Unit,
|
||||
Matrix,
|
||||
Complex
|
||||
};
|
||||
static bool DisplaysExact(DisplayOutput d) { return d != DisplayOutput::ApproximateOnly; }
|
||||
@@ -48,7 +50,7 @@ public:
|
||||
* calculations instead of clearing less space, then fail to serialize, clear
|
||||
* more space, fail to serialize, clear more space, etc., until reaching
|
||||
* sufficient free space. */
|
||||
static int MinimalSize() { return sizeof(uint8_t) + 2*sizeof(KDCoordinate) + sizeof(uint8_t) + 3*Constant::MaxSerializedExpressionSize; }
|
||||
static int MinimalSize() { return sizeof(uint8_t) + 2*sizeof(KDCoordinate) + sizeof(uint8_t) + 3*Constant::MaxSerializedExpressionSize + sizeof(Calculation *); }
|
||||
|
||||
Calculation() :
|
||||
m_displayOutput(DisplayOutput::Unknown),
|
||||
@@ -93,8 +95,6 @@ public:
|
||||
// Additional Information
|
||||
AdditionalInformationType additionalInformationType(Poincare::Context * context);
|
||||
private:
|
||||
static constexpr int maxWidth = 314;
|
||||
static constexpr int k_numberOfExpressions = 4;
|
||||
static constexpr KDCoordinate k_heightComputationFailureHeight = 50;
|
||||
static constexpr const char * k_maximalIntegerWithAdditionalInformation = "10000000000000000";
|
||||
|
||||
|
||||
@@ -12,58 +12,47 @@ using namespace Shared;
|
||||
|
||||
namespace Calculation {
|
||||
|
||||
CalculationStore::CalculationStore() :
|
||||
m_bufferEnd(m_buffer),
|
||||
m_numberOfCalculations(0),
|
||||
m_slidedBuffer(false),
|
||||
m_indexOfFirstMemoizedCalculationPointer(0)
|
||||
CalculationStore::CalculationStore(char * buffer, int size) :
|
||||
m_buffer(buffer),
|
||||
m_bufferSize(size),
|
||||
m_calculationAreaEnd(m_buffer),
|
||||
m_numberOfCalculations(0)
|
||||
{
|
||||
resetMemoizedModelsAfterCalculationIndex(-1);
|
||||
assert(m_buffer != nullptr);
|
||||
assert(m_bufferSize > 0);
|
||||
}
|
||||
|
||||
// Returns an expiring pointer to the calculation of index i
|
||||
ExpiringPointer<Calculation> CalculationStore::calculationAtIndex(int i) {
|
||||
assert(!m_slidedBuffer);
|
||||
assert(i >= 0 && i < m_numberOfCalculations);
|
||||
assert(m_indexOfFirstMemoizedCalculationPointer >= 0);
|
||||
if (i >= m_indexOfFirstMemoizedCalculationPointer && i < m_indexOfFirstMemoizedCalculationPointer + k_numberOfMemoizedCalculationPointers) {
|
||||
// The calculation is within the range of memoized calculations
|
||||
Calculation * c = m_memoizedCalculationPointers[i-m_indexOfFirstMemoizedCalculationPointer];
|
||||
if (c != nullptr) {
|
||||
// The pointer was memoized
|
||||
return ExpiringPointer<Calculation>(c);
|
||||
}
|
||||
c = bufferCalculationAtIndex(i);
|
||||
m_memoizedCalculationPointers[i-m_indexOfFirstMemoizedCalculationPointer] = c;
|
||||
return c;
|
||||
// m_buffer is the adress 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
|
||||
c = *reinterpret_cast<Calculation**>(addressOfPointerToCalculationOfIndex(i+1));
|
||||
}
|
||||
// Slide the memoization buffer
|
||||
if (i >= m_indexOfFirstMemoizedCalculationPointer) {
|
||||
// Slide the memoization buffer to the left
|
||||
memmove(m_memoizedCalculationPointers, m_memoizedCalculationPointers+1, (k_numberOfMemoizedCalculationPointers - 1) * sizeof(Calculation *));
|
||||
m_memoizedCalculationPointers[k_numberOfMemoizedCalculationPointers - 1] = nullptr;
|
||||
m_indexOfFirstMemoizedCalculationPointer++;
|
||||
} else {
|
||||
// Slide the memoization buffer to the right
|
||||
memmove(m_memoizedCalculationPointers+1, m_memoizedCalculationPointers, (k_numberOfMemoizedCalculationPointers - 1) * sizeof(Calculation *));
|
||||
m_memoizedCalculationPointers[0] = nullptr;
|
||||
m_indexOfFirstMemoizedCalculationPointer--;
|
||||
}
|
||||
return calculationAtIndex(i);
|
||||
return ExpiringPointer<Calculation>(c);
|
||||
}
|
||||
|
||||
// Pushes an expression in the store
|
||||
ExpiringPointer<Calculation> CalculationStore::push(const char * text, Context * context, HeightComputer heightComputer) {
|
||||
/* Compute ans now, before the buffer is slided and before the calculation
|
||||
/* 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
|
||||
int minSize = Calculation::MinimalSize();
|
||||
assert(k_bufferSize > minSize);
|
||||
while (remainingBufferSize() < minSize || m_numberOfCalculations > k_maxNumberOfCalculations) {
|
||||
deleteLastCalculation();
|
||||
/* 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 */
|
||||
int minSize = Calculation::MinimalSize() + sizeof(Calculation *);
|
||||
assert(m_bufferSize > minSize);
|
||||
while (remainingBufferSize() < minSize) {
|
||||
// If there is no more space to store a calculation, we delete the oldest one
|
||||
deleteOldestCalculation();
|
||||
}
|
||||
char * newCalculationsLocation = slideCalculationsToEndOfBuffer();
|
||||
char * nextSerializationLocation = m_buffer;
|
||||
|
||||
// Getting the adresses of the limits of the free space
|
||||
char * beginingOfFreeSpace = (char *)m_calculationAreaEnd;
|
||||
char * endOfFreeSpace = beginingOfMemoizationArea();
|
||||
char * previousCalc = beginingOfFreeSpace;
|
||||
|
||||
// Add the beginning of the calculation
|
||||
{
|
||||
@@ -71,23 +60,23 @@ ExpiringPointer<Calculation> CalculationStore::push(const char * text, Context *
|
||||
* available, so this memmove will not overide anything. */
|
||||
Calculation newCalc = Calculation();
|
||||
size_t calcSize = sizeof(newCalc);
|
||||
memmove(nextSerializationLocation, &newCalc, calcSize);
|
||||
nextSerializationLocation += calcSize;
|
||||
memcpy(beginingOfFreeSpace, &newCalc, calcSize);
|
||||
beginingOfFreeSpace += calcSize;
|
||||
}
|
||||
|
||||
/* Add the input expression.
|
||||
* We do not store directly the text entered by the user because we do not
|
||||
* want to keep Ans symbol in the calculation store. */
|
||||
const char * inputSerialization = nextSerializationLocation;
|
||||
const char * inputSerialization = beginingOfFreeSpace;
|
||||
{
|
||||
Expression input = Expression::Parse(text, context).replaceSymbolWithExpression(Symbol::Ans(), ans);
|
||||
if (!pushSerializeExpression(input, nextSerializationLocation, &newCalculationsLocation)) {
|
||||
if (!pushSerializeExpression(input, beginingOfFreeSpace, &endOfFreeSpace)) {
|
||||
/* If the input does not fit in the store (event if the current
|
||||
* calculation is the only calculation), just replace the calculation with
|
||||
* undef. */
|
||||
return emptyStoreAndPushUndef(context, heightComputer);
|
||||
}
|
||||
nextSerializationLocation += strlen(nextSerializationLocation) + 1;
|
||||
beginingOfFreeSpace += strlen(beginingOfFreeSpace) + 1;
|
||||
}
|
||||
|
||||
// Compute and serialize the outputs
|
||||
@@ -110,30 +99,27 @@ ExpiringPointer<Calculation> CalculationStore::push(const char * text, Context *
|
||||
if (i == numberOfOutputs - 1) {
|
||||
numberOfSignificantDigits = Poincare::Preferences::sharedPreferences()->numberOfSignificantDigits();
|
||||
}
|
||||
if (!pushSerializeExpression(outputs[i], nextSerializationLocation, &newCalculationsLocation, numberOfSignificantDigits)) {
|
||||
if (!pushSerializeExpression(outputs[i], beginingOfFreeSpace, &endOfFreeSpace, numberOfSignificantDigits)) {
|
||||
/* If the exat/approximate output does not fit in the store (event if the
|
||||
* current calculation is the only calculation), replace the output with
|
||||
* undef if it fits, else replace the whole calcualtion with undef. */
|
||||
Expression undef = Undefined::Builder();
|
||||
if (!pushSerializeExpression(undef, nextSerializationLocation, &newCalculationsLocation)) {
|
||||
if (!pushSerializeExpression(undef, beginingOfFreeSpace, &endOfFreeSpace)) {
|
||||
return emptyStoreAndPushUndef(context, heightComputer);
|
||||
}
|
||||
}
|
||||
nextSerializationLocation += strlen(nextSerializationLocation) + 1;
|
||||
beginingOfFreeSpace += strlen(beginingOfFreeSpace) + 1;
|
||||
}
|
||||
}
|
||||
// Storing the pointer of the end of the new calculation
|
||||
memcpy(endOfFreeSpace-sizeof(Calculation*),&beginingOfFreeSpace,sizeof(beginingOfFreeSpace));
|
||||
|
||||
// Restore the other calculations
|
||||
size_t slideSize = m_buffer + k_bufferSize - newCalculationsLocation;
|
||||
memcpy(nextSerializationLocation, newCalculationsLocation, slideSize);
|
||||
m_slidedBuffer = false;
|
||||
// The new calculation is now stored
|
||||
m_numberOfCalculations++;
|
||||
m_bufferEnd+= nextSerializationLocation - m_buffer;
|
||||
|
||||
// Clean the memoization
|
||||
resetMemoizedModelsAfterCalculationIndex(-1);
|
||||
|
||||
ExpiringPointer<Calculation> calculation = ExpiringPointer<Calculation>(reinterpret_cast<Calculation *>(m_buffer));
|
||||
// The end of the calculation storage area is updated
|
||||
m_calculationAreaEnd += beginingOfFreeSpace - previousCalc;
|
||||
ExpiringPointer<Calculation> calculation = ExpiringPointer<Calculation>(reinterpret_cast<Calculation *>(previousCalc));
|
||||
/* Heights are computed now to make sure that the display output is decided
|
||||
* accordingly to the remaining size in the Poincare pool. Once it is, it
|
||||
* can't change anymore: the calculation heights are fixed which ensures that
|
||||
@@ -144,36 +130,42 @@ ExpiringPointer<Calculation> CalculationStore::push(const char * text, Context *
|
||||
return calculation;
|
||||
}
|
||||
|
||||
// Delete the calculation of index i
|
||||
void CalculationStore::deleteCalculationAtIndex(int i) {
|
||||
assert(i >= 0 && i < m_numberOfCalculations);
|
||||
assert(!m_slidedBuffer);
|
||||
ExpiringPointer<Calculation> calcI = calculationAtIndex(i);
|
||||
char * nextCalc = reinterpret_cast<char *>(calcI->next());
|
||||
assert(m_bufferEnd >= nextCalc);
|
||||
size_t slidingSize = m_bufferEnd - nextCalc;
|
||||
memmove((char *)(calcI.pointer()), nextCalc, slidingSize);
|
||||
m_bufferEnd -= (nextCalc - (char *)(calcI.pointer()));
|
||||
m_numberOfCalculations--;
|
||||
resetMemoizedModelsAfterCalculationIndex(i);
|
||||
}
|
||||
|
||||
void CalculationStore::deleteAll() {
|
||||
/* We might call deleteAll because the app closed due to a pool allocation
|
||||
* failure, so we cannot assert that m_slidedBuffer is false. */
|
||||
m_slidedBuffer = false;
|
||||
m_bufferEnd = m_buffer;
|
||||
m_numberOfCalculations = 0;
|
||||
resetMemoizedModelsAfterCalculationIndex(-1);
|
||||
}
|
||||
|
||||
void CalculationStore::tidy() {
|
||||
if (m_slidedBuffer) {
|
||||
deleteAll();
|
||||
if (i == 0) {
|
||||
ExpiringPointer<Calculation> lastCalculationPointer = calculationAtIndex(0);
|
||||
m_calculationAreaEnd = (char *)(lastCalculationPointer.pointer());
|
||||
m_numberOfCalculations--;
|
||||
return;
|
||||
}
|
||||
resetMemoizedModelsAfterCalculationIndex(-1);
|
||||
char * calcI = (char *)calculationAtIndex(i).pointer();
|
||||
char * nextCalc = (char *) calculationAtIndex(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
|
||||
memmove(calcI, nextCalc, slidingSize);
|
||||
m_calculationAreaEnd -= nextCalc - calcI;
|
||||
// Recompute pointer to calculations after the i'th
|
||||
recomputeMemoizedPointersAfterCalculationIndex(i);
|
||||
m_numberOfCalculations--;
|
||||
}
|
||||
|
||||
// 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);
|
||||
char * newBufferEnd = (char *) m_calculationAreaEnd;
|
||||
return oldBufferEnd - newBufferEnd;
|
||||
}
|
||||
|
||||
// Delete all calculations
|
||||
void CalculationStore::deleteAll() {
|
||||
m_calculationAreaEnd = m_buffer;
|
||||
m_numberOfCalculations = 0;
|
||||
}
|
||||
|
||||
// Replace "Ans" by its expression
|
||||
Expression CalculationStore::ansExpression(Context * context) {
|
||||
if (numberOfCalculations() == 0) {
|
||||
return Rational::Builder(0);
|
||||
@@ -192,92 +184,42 @@ Expression CalculationStore::ansExpression(Context * context) {
|
||||
return mostRecentCalculation->exactOutput();
|
||||
}
|
||||
|
||||
Calculation * CalculationStore::bufferCalculationAtIndex(int i) {
|
||||
int currentIndex = 0;
|
||||
for (Calculation * c : *this) {
|
||||
if (currentIndex == i) {
|
||||
return c;
|
||||
}
|
||||
currentIndex++;
|
||||
}
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Push converted expression in the buffer
|
||||
bool CalculationStore::pushSerializeExpression(Expression e, char * location, char * * newCalculationsLocation, int numberOfSignificantDigits) {
|
||||
assert(m_slidedBuffer);
|
||||
assert(*newCalculationsLocation <= m_buffer + k_bufferSize);
|
||||
assert(*newCalculationsLocation <= m_buffer + m_bufferSize);
|
||||
bool expressionIsPushed = false;
|
||||
while (true) {
|
||||
size_t locationSize = *newCalculationsLocation - location;
|
||||
expressionIsPushed = (PoincareHelpers::Serialize(e, location, locationSize, numberOfSignificantDigits) < (int)locationSize-1);
|
||||
if (expressionIsPushed || *newCalculationsLocation >= m_buffer + k_bufferSize) {
|
||||
if (expressionIsPushed || *newCalculationsLocation >= m_buffer + m_bufferSize) {
|
||||
break;
|
||||
}
|
||||
*newCalculationsLocation = *newCalculationsLocation + deleteLastCalculation();
|
||||
assert(*newCalculationsLocation <= m_buffer + k_bufferSize);
|
||||
*newCalculationsLocation = *newCalculationsLocation + deleteOldestCalculation();
|
||||
assert(*newCalculationsLocation <= m_buffer + m_bufferSize);
|
||||
}
|
||||
return expressionIsPushed;
|
||||
}
|
||||
|
||||
char * CalculationStore::slideCalculationsToEndOfBuffer() {
|
||||
int calculationsSize = m_bufferEnd - m_buffer;
|
||||
char * calculationsNewPosition = m_buffer + k_bufferSize - calculationsSize;
|
||||
memmove(calculationsNewPosition, m_buffer, calculationsSize);
|
||||
m_slidedBuffer = true;
|
||||
return calculationsNewPosition;
|
||||
}
|
||||
|
||||
size_t CalculationStore::deleteLastCalculation(const char * calculationsStart) {
|
||||
assert(m_numberOfCalculations > 0);
|
||||
size_t result;
|
||||
if (!m_slidedBuffer) {
|
||||
assert(calculationsStart == nullptr);
|
||||
const char * previousBufferEnd = m_bufferEnd;
|
||||
m_bufferEnd = lastCalculationPosition(m_buffer);
|
||||
assert(previousBufferEnd > m_bufferEnd);
|
||||
result = previousBufferEnd - m_bufferEnd;
|
||||
} else {
|
||||
assert(calculationsStart != nullptr);
|
||||
const char * lastCalc = lastCalculationPosition(calculationsStart);
|
||||
assert(*lastCalc == 0);
|
||||
result = m_buffer + k_bufferSize - lastCalc;
|
||||
memmove(const_cast<char *>(calculationsStart + result), calculationsStart, m_buffer + k_bufferSize - calculationsStart - result);
|
||||
}
|
||||
m_numberOfCalculations--;
|
||||
resetMemoizedModelsAfterCalculationIndex(-1);
|
||||
return result;
|
||||
}
|
||||
|
||||
const char * CalculationStore::lastCalculationPosition(const char * calculationsStart) const {
|
||||
assert(calculationsStart >= m_buffer && calculationsStart < m_buffer + k_bufferSize);
|
||||
Calculation * c = reinterpret_cast<Calculation *>(const_cast<char *>(calculationsStart));
|
||||
int calculationIndex = 0;
|
||||
while (calculationIndex < m_numberOfCalculations - 1) {
|
||||
c = c->next();
|
||||
calculationIndex++;
|
||||
}
|
||||
return reinterpret_cast<const char *>(c);
|
||||
}
|
||||
|
||||
Shared::ExpiringPointer<Calculation> CalculationStore::emptyStoreAndPushUndef(Context * context, HeightComputer heightComputer) {
|
||||
/* We end up here as a result of a failed calculation push. The store
|
||||
* attributes are not necessarily clean, so we need to reset them. */
|
||||
m_slidedBuffer = false;
|
||||
deleteAll();
|
||||
return push(Undefined::Name(), context, heightComputer);
|
||||
}
|
||||
|
||||
void CalculationStore::resetMemoizedModelsAfterCalculationIndex(int index) {
|
||||
if (index < m_indexOfFirstMemoizedCalculationPointer) {
|
||||
memset(&m_memoizedCalculationPointers, 0, k_numberOfMemoizedCalculationPointers * sizeof(Calculation *));
|
||||
return;
|
||||
}
|
||||
if (index >= m_indexOfFirstMemoizedCalculationPointer + k_numberOfMemoizedCalculationPointers) {
|
||||
return;
|
||||
}
|
||||
for (int i = index - m_indexOfFirstMemoizedCalculationPointer; i < k_numberOfMemoizedCalculationPointers; i++) {
|
||||
m_memoizedCalculationPointers[i] = nullptr;
|
||||
// Recompute memoized pointers to the calculations after index i
|
||||
void CalculationStore::recomputeMemoizedPointersAfterCalculationIndex(int index) {
|
||||
assert(index < m_numberOfCalculations);
|
||||
// Clear pointer and recompute new ones
|
||||
Calculation * c = calculationAtIndex(index).pointer();
|
||||
Calculation * nextCalc;
|
||||
while (index != 0) {
|
||||
nextCalc = c->next();
|
||||
memcpy(addressOfPointerToCalculationOfIndex(index), &nextCalc, sizeof(Calculation *));
|
||||
c = nextCalc;
|
||||
index--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,37 +7,45 @@
|
||||
|
||||
namespace Calculation {
|
||||
|
||||
/* To optimize the storage space, we use one big buffer for all calculations.
|
||||
*
|
||||
* The previous solution was to keep 10 calculations, each containing 3 buffers
|
||||
* (for input and outputs). To optimize the storage, we then wanted to put all
|
||||
* outputs in a cache where they could be deleted to add a new entry, and
|
||||
* recomputed on cache miss. However, the computation depends too much on the
|
||||
* state of the memory for this to be possible. For instance:
|
||||
* 6->a
|
||||
* a+1
|
||||
* Perform some big computations that remove a+1 from the cache
|
||||
* Delete a from the variable box.
|
||||
* Scroll up to display a+1 : a does not exist anymore so the outputs won't be
|
||||
* recomputed correctly.
|
||||
*
|
||||
* Now we do not cap the number of calculations and just delete the oldests to
|
||||
* create space for a new calculation. */
|
||||
/*
|
||||
To optimize the storage space, we use one big buffer for all calculations.
|
||||
The calculations are stored one after another while pointers to the end of each
|
||||
calculation are stored at the end of the buffer, in the opposite direction.
|
||||
By doing so, we can memoize every calculation entered while not limiting
|
||||
the number of calculation stored in the buffer.
|
||||
|
||||
If the remaining space is too small for storing a new calculation, we
|
||||
delete the oldest one.
|
||||
|
||||
Memory layout :
|
||||
<- Available space for new calculations ->
|
||||
+--------------------------------------------------------------------------------------------------------------------+
|
||||
| | | | | | | | | |
|
||||
| Calculation 3 | Calculation 2 | Calculation 1 | Calculation O | |p0|p1|p2|p3|
|
||||
| Oldest | | | | | | | | |
|
||||
+--------------------------------------------------------------------------------------------------------------------+
|
||||
^ ^ ^ ^ ^ ^
|
||||
m_buffer p3 p2 p1 p0 a
|
||||
|
||||
m_calculationAreaEnd = p0
|
||||
a = addressOfPointerToCalculation(0)
|
||||
*/
|
||||
|
||||
class CalculationStore {
|
||||
public:
|
||||
CalculationStore();
|
||||
CalculationStore(char * buffer, int size);
|
||||
Shared::ExpiringPointer<Calculation> calculationAtIndex(int i);
|
||||
typedef KDCoordinate (*HeightComputer)(Calculation * c, bool expanded);
|
||||
Shared::ExpiringPointer<Calculation> push(const char * text, Poincare::Context * context, HeightComputer heightComputer);
|
||||
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; }
|
||||
Poincare::Expression ansExpression(Poincare::Context * context);
|
||||
void tidy();
|
||||
int bufferSize() { return m_bufferSize; }
|
||||
|
||||
private:
|
||||
static constexpr int k_maxNumberOfCalculations = 25;
|
||||
static constexpr int k_bufferSize = 10 * Calculation::k_numberOfExpressions * Constant::MaxSerializedExpressionSize;
|
||||
|
||||
class CalculationIterator {
|
||||
public:
|
||||
@@ -53,26 +61,22 @@ private:
|
||||
};
|
||||
|
||||
CalculationIterator begin() const { return CalculationIterator(m_buffer); }
|
||||
CalculationIterator end() const { return CalculationIterator(m_bufferEnd); }
|
||||
CalculationIterator end() const { return CalculationIterator(m_calculationAreaEnd); }
|
||||
|
||||
Calculation * bufferCalculationAtIndex(int i);
|
||||
int remainingBufferSize() const { assert(m_bufferEnd >= m_buffer); return k_bufferSize - (m_bufferEnd - m_buffer); }
|
||||
bool pushSerializeExpression(Poincare::Expression e, char * location, char * * newCalculationsLocation, int numberOfSignificantDigits = Poincare::PrintFloat::k_numberOfStoredSignificantDigits);
|
||||
char * slideCalculationsToEndOfBuffer(); // returns the new position of the calculations
|
||||
size_t deleteLastCalculation(const char * calculationsStart = nullptr);
|
||||
const char * lastCalculationPosition(const char * calculationsStart) const;
|
||||
Shared::ExpiringPointer<Calculation> emptyStoreAndPushUndef(Poincare::Context * context, HeightComputer heightComputer);
|
||||
|
||||
char m_buffer[k_bufferSize];
|
||||
const char * m_bufferEnd;
|
||||
char * m_buffer;
|
||||
int m_bufferSize;
|
||||
const char * m_calculationAreaEnd;
|
||||
int m_numberOfCalculations;
|
||||
bool m_slidedBuffer;
|
||||
|
||||
size_t deleteOldestCalculation();
|
||||
char * addressOfPointerToCalculationOfIndex(int i) {return m_buffer + m_bufferSize - (m_numberOfCalculations - i)*sizeof(Calculation *);}
|
||||
|
||||
// Memoization
|
||||
static constexpr int k_numberOfMemoizedCalculationPointers = 10;
|
||||
void resetMemoizedModelsAfterCalculationIndex(int index);
|
||||
int m_indexOfFirstMemoizedCalculationPointer;
|
||||
mutable Calculation * m_memoizedCalculationPointers[k_numberOfMemoizedCalculationPointers];
|
||||
char * beginingOfMemoizationArea() {return addressOfPointerToCalculationOfIndex(0);};
|
||||
void recomputeMemoizedPointersAfterCalculationIndex(int index);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -38,13 +38,14 @@ void EditExpressionController::ContentView::reload() {
|
||||
markRectAsDirty(bounds());
|
||||
}
|
||||
|
||||
EditExpressionController::EditExpressionController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, HistoryController * historyController, CalculationStore * calculationStore) :
|
||||
EditExpressionController::EditExpressionController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, char * cacheBuffer, size_t * cacheBufferInformation, HistoryController * historyController, CalculationStore * calculationStore) :
|
||||
ViewController(parentResponder),
|
||||
m_cacheBuffer(cacheBuffer),
|
||||
m_cacheBufferInformation(cacheBufferInformation),
|
||||
m_historyController(historyController),
|
||||
m_calculationStore(calculationStore),
|
||||
m_contentView(this, static_cast<CalculationSelectableTableView *>(m_historyController->view()), inputEventHandlerDelegate, this, this)
|
||||
{
|
||||
m_cacheBuffer[0] = 0;
|
||||
}
|
||||
|
||||
void EditExpressionController::insertTextBody(const char * text) {
|
||||
@@ -58,6 +59,15 @@ void EditExpressionController::didBecomeFirstResponder() {
|
||||
Container::activeApp()->setFirstResponder(m_contentView.expressionField());
|
||||
}
|
||||
|
||||
void EditExpressionController::restoreInput() {
|
||||
m_contentView.expressionField()->restoreContent(m_cacheBuffer, *m_cacheBufferInformation);
|
||||
clearCacheBuffer();
|
||||
}
|
||||
|
||||
void EditExpressionController::memoizeInput() {
|
||||
*m_cacheBufferInformation = m_contentView.expressionField()->moveCursorAndDumpContent(m_cacheBuffer, k_cacheBufferSize);
|
||||
}
|
||||
|
||||
void EditExpressionController::viewWillAppear() {
|
||||
m_historyController->viewWillAppear();
|
||||
}
|
||||
@@ -128,7 +138,7 @@ bool EditExpressionController::inputViewDidReceiveEvent(Ion::Events::Event event
|
||||
}
|
||||
if (event == Ion::Events::Up) {
|
||||
if (m_calculationStore->numberOfCalculations() > 0) {
|
||||
m_cacheBuffer[0] = 0;
|
||||
clearCacheBuffer();
|
||||
m_contentView.expressionField()->setEditing(false, false);
|
||||
Container::activeApp()->setFirstResponder(m_historyController);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "../shared/text_field_delegate.h"
|
||||
#include "../shared/layout_field_delegate.h"
|
||||
#include "history_controller.h"
|
||||
#include "calculation_store.h"
|
||||
#include "selectable_table_view.h"
|
||||
|
||||
namespace Calculation {
|
||||
@@ -15,11 +14,23 @@ namespace Calculation {
|
||||
/* TODO: implement a split view */
|
||||
class EditExpressionController : public ViewController, public Shared::TextFieldDelegate, public Shared::LayoutFieldDelegate {
|
||||
public:
|
||||
EditExpressionController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, HistoryController * historyController, CalculationStore * calculationStore);
|
||||
EditExpressionController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, char * cacheBuffer, size_t * cacheBufferInformation, HistoryController * historyController, CalculationStore * calculationStore);
|
||||
|
||||
/* k_layoutBufferMaxSize dictates the size under which the expression being
|
||||
* edited can be remembered when the user leaves Calculation. */
|
||||
static constexpr int k_layoutBufferMaxSize = 1024;
|
||||
/* k_cacheBufferSize is the size of the array to which m_cacheBuffer points.
|
||||
* It is used both as a way to buffer expression when pushing them the
|
||||
* CalculationStore, and as a storage for the current input when leaving the
|
||||
* application. */
|
||||
static constexpr int k_cacheBufferSize = (k_layoutBufferMaxSize < Constant::MaxSerializedExpressionSize) ? Constant::MaxSerializedExpressionSize : k_layoutBufferMaxSize;
|
||||
|
||||
View * view() override { return &m_contentView; }
|
||||
void didBecomeFirstResponder() override;
|
||||
void viewWillAppear() override;
|
||||
void insertTextBody(const char * text);
|
||||
void restoreInput();
|
||||
void memoizeInput();
|
||||
|
||||
/* TextFieldDelegate */
|
||||
bool textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) override;
|
||||
@@ -47,11 +58,12 @@ private:
|
||||
ExpressionField m_expressionField;
|
||||
};
|
||||
void reloadView();
|
||||
void clearCacheBuffer() { m_cacheBuffer[0] = 0; *m_cacheBufferInformation = 0; }
|
||||
bool inputViewDidReceiveEvent(Ion::Events::Event event, bool shouldDuplicateLastCalculation);
|
||||
bool inputViewDidFinishEditing(const char * text, Poincare::Layout layoutR);
|
||||
bool inputViewDidAbortEditing(const char * text);
|
||||
static constexpr int k_cacheBufferSize = Constant::MaxSerializedExpressionSize;
|
||||
char m_cacheBuffer[k_cacheBufferSize];
|
||||
char * m_cacheBuffer;
|
||||
size_t * m_cacheBufferInformation;
|
||||
HistoryController * m_historyController;
|
||||
CalculationStore * m_calculationStore;
|
||||
ContentView m_contentView;
|
||||
|
||||
@@ -17,7 +17,8 @@ HistoryController::HistoryController(EditExpressionController * editExpressionCo
|
||||
m_integerController(editExpressionController),
|
||||
m_rationalController(editExpressionController),
|
||||
m_trigonometryController(editExpressionController),
|
||||
m_unitController(editExpressionController)
|
||||
m_unitController(editExpressionController),
|
||||
m_matrixController(editExpressionController)
|
||||
{
|
||||
for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) {
|
||||
m_calculationHistory[i].setParentResponder(&m_selectableTableView);
|
||||
@@ -110,6 +111,8 @@ bool HistoryController::handleEvent(Ion::Events::Event event) {
|
||||
vc = &m_rationalController;
|
||||
} else if (additionalInfoType == Calculation::AdditionalInformationType::Unit) {
|
||||
vc = &m_unitController;
|
||||
} else if (additionalInfoType == Calculation::AdditionalInformationType::Matrix) {
|
||||
vc = &m_matrixController;
|
||||
}
|
||||
if (vc) {
|
||||
vc->setExpression(e);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "additional_outputs/rational_list_controller.h"
|
||||
#include "additional_outputs/trigonometry_list_controller.h"
|
||||
#include "additional_outputs/unit_list_controller.h"
|
||||
#include "additional_outputs/matrix_list_controller.h"
|
||||
|
||||
namespace Calculation {
|
||||
|
||||
@@ -48,6 +49,7 @@ private:
|
||||
RationalListController m_rationalController;
|
||||
TrigonometryListController m_trigonometryController;
|
||||
UnitListController m_unitController;
|
||||
MatrixListController m_matrixController;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -5,9 +5,17 @@
|
||||
#include <assert.h>
|
||||
#include "../calculation_store.h"
|
||||
|
||||
typedef ::Calculation::Calculation::DisplayOutput DisplayOutput;
|
||||
typedef ::Calculation::Calculation::EqualSign EqualSign ;
|
||||
typedef ::Calculation::Calculation::NumberOfSignificantDigits NumberOfSignificantDigits;
|
||||
|
||||
using namespace Poincare;
|
||||
using namespace Calculation;
|
||||
|
||||
static constexpr int calculationBufferSize = 10 * (sizeof(::Calculation::Calculation) + ::Calculation::Calculation::k_numberOfExpressions * ::Constant::MaxSerializedExpressionSize + sizeof(::Calculation::Calculation *));
|
||||
char calculationBuffer[calculationBufferSize];
|
||||
|
||||
|
||||
void assert_store_is(CalculationStore * store, const char * * result) {
|
||||
for (int i = 0; i < store->numberOfCalculations(); i++) {
|
||||
quiz_assert(strcmp(store->calculationAtIndex(i)->inputText(), result[i]) == 0);
|
||||
@@ -18,7 +26,7 @@ KDCoordinate dummyHeight(::Calculation::Calculation * c, bool expanded) { return
|
||||
|
||||
QUIZ_CASE(calculation_store) {
|
||||
Shared::GlobalContext globalContext;
|
||||
CalculationStore store;
|
||||
CalculationStore store(calculationBuffer,calculationBufferSize);
|
||||
// Store is now {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
|
||||
const char * result[] = {"9", "8", "7", "6", "5", "4", "3", "2", "1", "0"};
|
||||
for (int i = 0; i < 10; i++) {
|
||||
@@ -29,101 +37,117 @@ QUIZ_CASE(calculation_store) {
|
||||
assert_store_is(&store, result);
|
||||
|
||||
for (int i = 9; i > 0; i = i-2) {
|
||||
store.deleteCalculationAtIndex(i);
|
||||
store.deleteCalculationAtIndex(i);
|
||||
}
|
||||
// Store is now {9, 7, 5, 3, 1}
|
||||
const char * result2[] = {"9", "7", "5", "3", "1"};
|
||||
assert_store_is(&store, result2);
|
||||
|
||||
store.deleteAll();
|
||||
|
||||
// Checking if the store handles correctly the delete of the oldest calculation when full
|
||||
static int minSize = ::Calculation::Calculation::MinimalSize();
|
||||
char text[2] = {'0', 0};
|
||||
while (store.remainingBufferSize() > minSize) {
|
||||
store.push(text, &globalContext, dummyHeight);
|
||||
}
|
||||
int numberOfCalculations1 = store.numberOfCalculations();
|
||||
/* The buffer is now to full to push a new calculation.
|
||||
* Trying to push a new one should delete the oldest one*/
|
||||
store.push(text, &globalContext, dummyHeight);
|
||||
int numberOfCalculations2 = store.numberOfCalculations();
|
||||
// The numberOfCalculations should be the same
|
||||
quiz_assert(numberOfCalculations1 == numberOfCalculations2);
|
||||
store.deleteAll();
|
||||
quiz_assert(store.remainingBufferSize() == store.bufferSize());
|
||||
}
|
||||
|
||||
QUIZ_CASE(calculation_ans) {
|
||||
Shared::GlobalContext globalContext;
|
||||
CalculationStore store;
|
||||
CalculationStore store(calculationBuffer,calculationBufferSize);
|
||||
|
||||
store.push("1+3/4", &globalContext, dummyHeight);
|
||||
store.push("ans+2/3", &globalContext, dummyHeight);
|
||||
Shared::ExpiringPointer<::Calculation::Calculation> lastCalculation = store.calculationAtIndex(0);
|
||||
quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximate);
|
||||
quiz_assert(lastCalculation->displayOutput(&globalContext) == DisplayOutput::ExactAndApproximate);
|
||||
quiz_assert(strcmp(lastCalculation->exactOutputText(),"29/12") == 0);
|
||||
|
||||
store.push("ans+0.22", &globalContext, dummyHeight);
|
||||
lastCalculation = store.calculationAtIndex(0);
|
||||
quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximateToggle);
|
||||
quiz_assert(strcmp(lastCalculation->approximateOutputText(::Calculation::Calculation::NumberOfSignificantDigits::Maximal),"2.6366666666667") == 0);
|
||||
quiz_assert(lastCalculation->displayOutput(&globalContext) == DisplayOutput::ExactAndApproximateToggle);
|
||||
quiz_assert(strcmp(lastCalculation->approximateOutputText(NumberOfSignificantDigits::Maximal),"2.6366666666667") == 0);
|
||||
|
||||
store.deleteAll();
|
||||
}
|
||||
|
||||
void assertCalculationIs(const char * input, ::Calculation::Calculation::DisplayOutput display, ::Calculation::Calculation::EqualSign sign, const char * exactOutput, const char * displayedApproximateOutput, const char * storedApproximateOutput, Context * context, CalculationStore * store) {
|
||||
void assertCalculationIs(const char * input, DisplayOutput display, EqualSign sign, const char * exactOutput, const char * displayedApproximateOutput, const char * storedApproximateOutput, Context * context, CalculationStore * store) {
|
||||
store->push(input, context, dummyHeight);
|
||||
Shared::ExpiringPointer<::Calculation::Calculation> lastCalculation = store->calculationAtIndex(0);
|
||||
quiz_assert(lastCalculation->displayOutput(context) == display);
|
||||
if (sign != ::Calculation::Calculation::EqualSign::Unknown) {
|
||||
if (sign != EqualSign::Unknown) {
|
||||
quiz_assert(lastCalculation->exactAndApproximateDisplayedOutputsAreEqual(context) == sign);
|
||||
}
|
||||
if (exactOutput) {
|
||||
quiz_assert_print_if_failure(strcmp(lastCalculation->exactOutputText(), exactOutput) == 0, input);
|
||||
}
|
||||
if (displayedApproximateOutput) {
|
||||
quiz_assert_print_if_failure(strcmp(lastCalculation->approximateOutputText(::Calculation::Calculation::NumberOfSignificantDigits::UserDefined), displayedApproximateOutput) == 0, input);
|
||||
quiz_assert_print_if_failure(strcmp(lastCalculation->approximateOutputText(NumberOfSignificantDigits::UserDefined), displayedApproximateOutput) == 0, input);
|
||||
}
|
||||
if (storedApproximateOutput) {
|
||||
quiz_assert_print_if_failure(strcmp(lastCalculation->approximateOutputText(::Calculation::Calculation::NumberOfSignificantDigits::Maximal), storedApproximateOutput) == 0, input);
|
||||
quiz_assert_print_if_failure(strcmp(lastCalculation->approximateOutputText(NumberOfSignificantDigits::Maximal), storedApproximateOutput) == 0, input);
|
||||
}
|
||||
store->deleteAll();
|
||||
}
|
||||
|
||||
QUIZ_CASE(calculation_significant_digits) {
|
||||
Shared::GlobalContext globalContext;
|
||||
CalculationStore store;
|
||||
CalculationStore store(calculationBuffer,calculationBufferSize);
|
||||
|
||||
assertCalculationIs("123456789", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "123456789", "1.234568ᴇ8", "123456789", &globalContext, &store);
|
||||
assertCalculationIs("1234567", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "1234567", "1234567", "1234567", &globalContext, &store);
|
||||
assertCalculationIs("123456789", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "123456789", "1.234568ᴇ8", "123456789", &globalContext, &store);
|
||||
assertCalculationIs("1234567", DisplayOutput::ApproximateOnly, EqualSign::Equal, "1234567", "1234567", "1234567", &globalContext, &store);
|
||||
|
||||
}
|
||||
|
||||
QUIZ_CASE(calculation_display_exact_approximate) {
|
||||
Shared::GlobalContext globalContext;
|
||||
CalculationStore store;
|
||||
CalculationStore store(calculationBuffer,calculationBufferSize);
|
||||
|
||||
assertCalculationIs("1/2", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Equal, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("1/3", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("1/0", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
|
||||
assertCalculationIs("2x-x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
|
||||
assertCalculationIs("[[1,2,3]]", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("[[1,x,3]]", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "undef", "undef", &globalContext, &store);
|
||||
assertCalculationIs("28^7", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("3+√(2)→a", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(2)+3", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("1/2", DisplayOutput::ExactAndApproximate, EqualSign::Equal, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("1/3", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("1/0", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
|
||||
assertCalculationIs("2x-x", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
|
||||
assertCalculationIs("[[1,2,3]]", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("[[1,x,3]]", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "undef", "undef", &globalContext, &store);
|
||||
assertCalculationIs("28^7", DisplayOutput::ExactAndApproximate, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("3+√(2)→a", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "√(2)+3", nullptr, nullptr, &globalContext, &store);
|
||||
Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy();
|
||||
assertCalculationIs("3+2→a", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "5", "5", "5", &globalContext, &store);
|
||||
assertCalculationIs("3+2→a", DisplayOutput::ApproximateOnly, EqualSign::Equal, "5", "5", "5", &globalContext, &store);
|
||||
Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy();
|
||||
assertCalculationIs("3→a", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", "3", "3", &globalContext, &store);
|
||||
assertCalculationIs("3→a", DisplayOutput::ApproximateOnly, EqualSign::Equal, "3", "3", "3", &globalContext, &store);
|
||||
Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy();
|
||||
assertCalculationIs("3+x→f(x)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "x+3", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("3+x→f(x)", DisplayOutput::ExactOnly, EqualSign::Unknown, "x+3", nullptr, nullptr, &globalContext, &store);
|
||||
Ion::Storage::sharedStorage()->recordNamed("f.func").destroy();
|
||||
assertCalculationIs("1+1+random()", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("1+1+round(1.343,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3.34", "3.34", &globalContext, &store);
|
||||
assertCalculationIs("randint(2,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "5", "5", "5", &globalContext, &store);
|
||||
assertCalculationIs("confidence(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("prediction(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("prediction95(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("1+1+random()", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("1+1+round(1.343,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "3.34", "3.34", &globalContext, &store);
|
||||
assertCalculationIs("randint(2,2)+3", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "5", "5", "5", &globalContext, &store);
|
||||
assertCalculationIs("confidence(0.5,2)+3", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("prediction(0.5,2)+3", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("prediction95(0.5,2)+3", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
|
||||
}
|
||||
|
||||
QUIZ_CASE(calculation_symbolic_computation) {
|
||||
Shared::GlobalContext globalContext;
|
||||
CalculationStore store;
|
||||
CalculationStore store(calculationBuffer,calculationBufferSize);
|
||||
|
||||
assertCalculationIs("x+x+1+3+√(π)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
|
||||
assertCalculationIs("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
|
||||
assertCalculationIs("1+x→f(x)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "x+1", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
|
||||
assertCalculationIs("f(2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", "3", "3", &globalContext, &store);
|
||||
assertCalculationIs("2→x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "2", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("x+x+1+3+√(π)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(π)+8", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("x+x+1+3+√(π)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
|
||||
assertCalculationIs("f(x)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
|
||||
assertCalculationIs("1+x→f(x)", DisplayOutput::ExactOnly, EqualSign::Unknown, "x+1", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("f(x)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
|
||||
assertCalculationIs("f(2)", DisplayOutput::ApproximateOnly, EqualSign::Equal, "3", "3", "3", &globalContext, &store);
|
||||
assertCalculationIs("2→x", DisplayOutput::ApproximateOnly, EqualSign::Equal, "2", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("f(x)", DisplayOutput::ApproximateOnly, EqualSign::Equal, "3", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("x+x+1+3+√(π)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "√(π)+8", nullptr, nullptr, &globalContext, &store);
|
||||
|
||||
Ion::Storage::sharedStorage()->recordNamed("f.func").destroy();
|
||||
Ion::Storage::sharedStorage()->recordNamed("x.exp").destroy();
|
||||
@@ -131,18 +155,18 @@ QUIZ_CASE(calculation_symbolic_computation) {
|
||||
|
||||
QUIZ_CASE(calculation_symbolic_computation_and_parametered_expressions) {
|
||||
Shared::GlobalContext globalContext;
|
||||
CalculationStore store;
|
||||
CalculationStore store(calculationBuffer,calculationBufferSize);
|
||||
|
||||
assertCalculationIs("int((ℯ^(-x))-x^(0.5), x, 0, 3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); // Tests a bug with symbolic computation
|
||||
assertCalculationIs("int(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store);
|
||||
assertCalculationIs("sum(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3", "3", &globalContext, &store);
|
||||
assertCalculationIs("product(x,x,1,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store);
|
||||
assertCalculationIs("diff(x^2,x,3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "6", "6", &globalContext, &store);
|
||||
assertCalculationIs("2→x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("int(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store);
|
||||
assertCalculationIs("sum(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3", "3", &globalContext, &store);
|
||||
assertCalculationIs("product(x,x,1,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store);
|
||||
assertCalculationIs("diff(x^2,x,3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "6", "6", &globalContext, &store);
|
||||
assertCalculationIs("int((ℯ^(-x))-x^(0.5), x, 0, 3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); // Tests a bug with symbolic computation
|
||||
assertCalculationIs("int(x,x,0,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store);
|
||||
assertCalculationIs("sum(x,x,0,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "3", "3", &globalContext, &store);
|
||||
assertCalculationIs("product(x,x,1,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store);
|
||||
assertCalculationIs("diff(x^2,x,3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "6", "6", &globalContext, &store);
|
||||
assertCalculationIs("2→x", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("int(x,x,0,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store);
|
||||
assertCalculationIs("sum(x,x,0,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "3", "3", &globalContext, &store);
|
||||
assertCalculationIs("product(x,x,1,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store);
|
||||
assertCalculationIs("diff(x^2,x,3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "6", "6", &globalContext, &store);
|
||||
|
||||
Ion::Storage::sharedStorage()->recordNamed("x.exp").destroy();
|
||||
}
|
||||
@@ -150,34 +174,34 @@ QUIZ_CASE(calculation_symbolic_computation_and_parametered_expressions) {
|
||||
|
||||
QUIZ_CASE(calculation_complex_format) {
|
||||
Shared::GlobalContext globalContext;
|
||||
CalculationStore store;
|
||||
CalculationStore store(calculationBuffer,calculationBufferSize);
|
||||
|
||||
Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Real);
|
||||
assertCalculationIs("1+𝐢", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "1+𝐢", "1+𝐢", &globalContext, &store);
|
||||
assertCalculationIs("√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "unreal", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("ln(-2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store);
|
||||
assertCalculationIs("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store);
|
||||
assertCalculationIs("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "-2", "-2", &globalContext, &store);
|
||||
assertCalculationIs("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "4", "4", &globalContext, &store);
|
||||
assertCalculationIs("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store);
|
||||
assertCalculationIs("1+𝐢", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "1+𝐢", "1+𝐢", &globalContext, &store);
|
||||
assertCalculationIs("√(-1)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "unreal", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("ln(-2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store);
|
||||
assertCalculationIs("√(-1)×√(-1)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store);
|
||||
assertCalculationIs("(-8)^(1/3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "-2", "-2", &globalContext, &store);
|
||||
assertCalculationIs("(-8)^(2/3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "4", "4", &globalContext, &store);
|
||||
assertCalculationIs("(-2)^(1/4)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store);
|
||||
|
||||
Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Cartesian);
|
||||
assertCalculationIs("1+𝐢", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "1+𝐢", "1+𝐢", &globalContext, &store);
|
||||
assertCalculationIs("√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "𝐢", "𝐢", &globalContext, &store);
|
||||
assertCalculationIs("ln(-2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ln(-2)", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "-1", "-1", &globalContext, &store);
|
||||
assertCalculationIs("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "1+√(3)×𝐢", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "-2+2×√(3)×𝐢", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "root(8,4)/2+root(8,4)/2×𝐢", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("1+𝐢", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "1+𝐢", "1+𝐢", &globalContext, &store);
|
||||
assertCalculationIs("√(-1)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "𝐢", "𝐢", &globalContext, &store);
|
||||
assertCalculationIs("ln(-2)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "ln(-2)", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("√(-1)×√(-1)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "-1", "-1", &globalContext, &store);
|
||||
assertCalculationIs("(-8)^(1/3)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "1+√(3)×𝐢", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("(-8)^(2/3)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "-2+2×√(3)×𝐢", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("(-2)^(1/4)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "root(8,4)/2+root(8,4)/2×𝐢", nullptr, nullptr, &globalContext, &store);
|
||||
|
||||
Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Polar);
|
||||
assertCalculationIs("1+𝐢", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(2)×ℯ^\u0012π/4×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("√(-1)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ℯ^\u0012π/2×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("ln(-2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ln(-2)", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "ℯ^\u00123.141593×𝐢\u0013", "ℯ^\u00123.1415926535898×𝐢\u0013", &globalContext, &store);
|
||||
assertCalculationIs("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "2×ℯ^\u0012π/3×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "4×ℯ^\u0012\u00122×π\u0013/3×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "root(2,4)×ℯ^\u0012π/4×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("1+𝐢", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "√(2)×ℯ^\u0012π/4×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("√(-1)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "ℯ^\u0012π/2×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("ln(-2)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "ln(-2)", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("√(-1)×√(-1)", DisplayOutput::ExactAndApproximate, EqualSign::Unknown, nullptr, "ℯ^\u00123.141593×𝐢\u0013", "ℯ^\u00123.1415926535898×𝐢\u0013", &globalContext, &store);
|
||||
assertCalculationIs("(-8)^(1/3)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "2×ℯ^\u0012π/3×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("(-8)^(2/3)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "4×ℯ^\u0012\u00122×π\u0013/3×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
|
||||
assertCalculationIs("(-2)^(1/4)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "root(2,4)×ℯ^\u0012π/4×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
|
||||
|
||||
Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Cartesian);
|
||||
}
|
||||
|
||||
@@ -89,16 +89,18 @@ App::App(Snapshot * snapshot) :
|
||||
, snapshot->lockOnConsole()
|
||||
#endif
|
||||
),
|
||||
m_listFooter(&m_codeStackViewController, &m_menuController, &m_menuController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGrey, ButtonRowController::Size::Large),
|
||||
m_listFooter(&m_codeStackViewController, &m_menuController, &m_menuController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGray, ButtonRowController::Size::Large),
|
||||
m_menuController(&m_listFooter, this, snapshot->scriptStore(), &m_listFooter),
|
||||
m_codeStackViewController(&m_modalViewController, &m_listFooter),
|
||||
m_variableBoxController(snapshot->scriptStore())
|
||||
{
|
||||
Clipboard::sharedClipboard()->enterPython();
|
||||
}
|
||||
|
||||
App::~App() {
|
||||
assert(!m_consoleController.inputRunLoopActive());
|
||||
deinitPython();
|
||||
Clipboard::sharedClipboard()->exitPython();
|
||||
}
|
||||
|
||||
bool App::handleEvent(Ion::Events::Event event) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "script_store.h"
|
||||
#include "python_toolbox.h"
|
||||
#include "variable_box_controller.h"
|
||||
#include "../shared/shared_app.h"
|
||||
|
||||
namespace Code {
|
||||
|
||||
@@ -21,7 +22,7 @@ public:
|
||||
App::Descriptor::ExaminationLevel examinationLevel() override;
|
||||
const Image * icon() override;
|
||||
};
|
||||
class Snapshot : public ::App::Snapshot {
|
||||
class Snapshot : public SharedApp::Snapshot {
|
||||
public:
|
||||
Snapshot();
|
||||
App * unpack(Container * container) override;
|
||||
|
||||
@@ -30,8 +30,8 @@ PythonColor = "Definiere eine RGB-Farbe"
|
||||
PythonColorBlack = "Black color"
|
||||
PythonColorBlue = "Blue color"
|
||||
PythonColorBrown = "Brown color"
|
||||
PythonColorGray = "Gray color"
|
||||
PythonColorGreen = "Green color"
|
||||
PythonColorGrey = "Grey color"
|
||||
PythonColorOrange = "Orange color"
|
||||
PythonColorPink = "Pink color"
|
||||
PythonColorPurple = "Purple color"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
PythonPound = "Comment"
|
||||
PythonPercent = "Modulo"
|
||||
Python1J = "Imaginary i"
|
||||
PythonLF = "line feed"
|
||||
PythonLF = "Line feed"
|
||||
PythonTab = "Tabulation"
|
||||
PythonAmpersand = "Bitwise and"
|
||||
PythonSymbolExp = "Bitwise exclusive or"
|
||||
@@ -30,8 +30,8 @@ PythonColor = "Define a rgb color"
|
||||
PythonColorBlack = "Black color"
|
||||
PythonColorBlue = "Blue color"
|
||||
PythonColorBrown = "Brown color"
|
||||
PythonColorGray = "Gray color"
|
||||
PythonColorGreen = "Green color"
|
||||
PythonColorGrey = "Grey color"
|
||||
PythonColorOrange = "Orange color"
|
||||
PythonColorPink = "Pink color"
|
||||
PythonColorPurple = "Purple color"
|
||||
|
||||
@@ -30,8 +30,8 @@ PythonColor = "Define a rgb color"
|
||||
PythonColorBlack = "Black color"
|
||||
PythonColorBlue = "Blue color"
|
||||
PythonColorBrown = "Brown color"
|
||||
PythonColorGray = "Gray color"
|
||||
PythonColorGreen = "Green color"
|
||||
PythonColorGrey = "Grey color"
|
||||
PythonColorOrange = "Orange color"
|
||||
PythonColorPink = "Pink color"
|
||||
PythonColorPurple = "Purple color"
|
||||
|
||||
@@ -30,8 +30,8 @@ PythonColor = "Définit une couleur rvb"
|
||||
PythonColorBlack = "Couleur noire"
|
||||
PythonColorBlue = "Couleur bleue"
|
||||
PythonColorBrown = "Couleur marron"
|
||||
PythonColorGray = "Couleur grise"
|
||||
PythonColorGreen = "Couleur verte"
|
||||
PythonColorGrey = "Couleur grise"
|
||||
PythonColorOrange = "Couleur orange"
|
||||
PythonColorPink = "Couleur rose"
|
||||
PythonColorPurple = "Couleur violette"
|
||||
|
||||
@@ -30,8 +30,8 @@ PythonColor = "Definisci un colore rvb"
|
||||
PythonColorBlack = "Colore nero"
|
||||
PythonColorBlue = "Colore blu"
|
||||
PythonColorBrown = "Colore marrone"
|
||||
PythonColorGray = "Colore grigio"
|
||||
PythonColorGreen = "Colore verde"
|
||||
PythonColorGrey = "Colore grigio"
|
||||
PythonColorOrange = "Colore arancione"
|
||||
PythonColorPink = "Colore rosa"
|
||||
PythonColorPurple = "Colore viola"
|
||||
|
||||
@@ -13,14 +13,14 @@ PythonAbs = "Absolute waarde"
|
||||
PythonAcos = "Arccosinus"
|
||||
PythonAcosh = "Arccosinus hyperbolicus"
|
||||
PythonAppend = "Voeg x toe aan het eind van je lijst"
|
||||
PythonArrow = "Arrow from (x,y) to (x+dx,y+dy)"
|
||||
PythonArrow = "Pijl van (x,y) naar (x+dx,y+dy)"
|
||||
PythonAsin = "Arcsinus"
|
||||
PythonAsinh = "Arcsinus hyperbolicus"
|
||||
PythonAtan = "Arctangens"
|
||||
PythonAtan2 = "Geeft atan(y/x)"
|
||||
PythonAtanh = "Arctangens hyperbolicus"
|
||||
PythonAxis = "Set the axes to (xmin,xmax,ymin,ymax)"
|
||||
PythonBar = "Draw a bar plot with x values"
|
||||
PythonAxis = "Stel de assen in (xmin,xmax,ymin,ymax)"
|
||||
PythonBar = "Teken staafdiagram met x-waarden"
|
||||
PythonBin = "Zet integer om in een binair getal"
|
||||
PythonCeil = "Plafond"
|
||||
PythonChoice = "Geeft willek. getal van de lijst"
|
||||
@@ -30,8 +30,8 @@ PythonColor = "Definieer een rgb kleur"
|
||||
PythonColorBlack = "Zwarte kleur"
|
||||
PythonColorBlue = "Blauwe kleur"
|
||||
PythonColorBrown = "Bruine kleur"
|
||||
PythonColorGray = "Grijze kleur"
|
||||
PythonColorGreen = "Groene kleur"
|
||||
PythonColorGrey = "Grijze kleur"
|
||||
PythonColorOrange = "Oranje kleur"
|
||||
PythonColorPink = "Roze kleur"
|
||||
PythonColorPurple = "Paarse kleur"
|
||||
@@ -60,15 +60,15 @@ PythonFrExp = "Mantisse en exponent van x: (m,e)"
|
||||
PythonGamma = "Gammafunctie"
|
||||
PythonGetPixel = "Geef pixel (x,y) kleur (rgb)"
|
||||
PythonGetrandbits = "Integer met k willekeurige bits"
|
||||
PythonGrid = "Toggle the visibility of the grid"
|
||||
PythonGrid = "Verander zichtbaarheid raster"
|
||||
PythonHex = "Zet integer om in hexadecimaal"
|
||||
PythonHist = "Draw the histogram of x"
|
||||
PythonHist = "Teken het histogram van x"
|
||||
PythonImportCmath = "Importeer cmath module"
|
||||
PythonImportIon = "Importeer ion module"
|
||||
PythonImportKandinsky = "Importeer kandinsky module"
|
||||
PythonImportRandom = "Importeer random module"
|
||||
PythonImportMath = "Importeer math module"
|
||||
PythonImportMatplotlibPyplot = "Import matplotlib.pyplot module"
|
||||
PythonImportMatplotlibPyplot = "Importeer matplotlib.pyplot module"
|
||||
PythonImportTime = "Importeer time module"
|
||||
PythonImportOs = "Importeer os module"
|
||||
PythonOsUname = " Krijg systeeminfo"
|
||||
@@ -84,7 +84,7 @@ PythonIonFunction = "ion module voorvoegsel"
|
||||
PythonIsFinite = "Controleer of x eindig is"
|
||||
PythonIsInfinite = "Controleer of x oneindig is"
|
||||
PythonIsKeyDown = "Geef True als k toets omlaag is"
|
||||
PythonIsNaN = "Controleer of x geen nummer is"
|
||||
PythonIsNaN = "Controleer of x geen getal is"
|
||||
PythonKandinskyFunction = "kandinsky module voorvoegsel"
|
||||
PythonKeyLeft = "PIJL NAAR LINKS toets"
|
||||
PythonKeyUp = "PIJL OMHOOG toets"
|
||||
@@ -146,14 +146,14 @@ PythonModf = "Fractionele en gehele delen van x"
|
||||
PythonMonotonic = "Waarde van een monotone klok"
|
||||
PythonOct = "Integer omzetten naar octaal"
|
||||
PythonPhase = "Fase van z in radialen"
|
||||
PythonPlot = "Plot y versus x as lines"
|
||||
PythonPlot = "Plot y versus x als lijnen"
|
||||
PythonPolar = "z in poolcoördinaten"
|
||||
PythonPop = "Verwijder en breng het laatste item terug"
|
||||
PythonPower = "x tot de macht y"
|
||||
PythonPrint = "Print object"
|
||||
PythonRadians = "Zet x om van graden naar radialen"
|
||||
PythonRandint = "Geeft willek. integer in [a,b]"
|
||||
PythonRandom = "Een willekeurig getal in [0,1["
|
||||
PythonRandom = "Een willekeurig getal in [0,1)"
|
||||
PythonRandomFunction = "random module voorvoegsel"
|
||||
PythonRandrange = "Willek. getal in range(start, stop)"
|
||||
PythonRangeStartStop = "Lijst van start tot stop-1"
|
||||
@@ -162,10 +162,10 @@ PythonRect = "z in cartesiaanse coördinaten"
|
||||
PythonRemove = "Verwijder het eerste voorkomen van x"
|
||||
PythonReverse = "Keer de elementen van de lijst om"
|
||||
PythonRound = "Rond af op n cijfers"
|
||||
PythonScatter = "Draw a scatter plot of y versus x"
|
||||
PythonScatter = "Teken scatterplot van y versus x"
|
||||
PythonSeed = "Start willek. getallengenerator"
|
||||
PythonSetPixel = "Kleur pixel (x,y)"
|
||||
PythonShow = "Display the figure"
|
||||
PythonShow = "Figuur weergeven"
|
||||
PythonSin= "Sinus"
|
||||
PythonSinh = "Sinus hyperbolicus"
|
||||
PythonSleep = "Stel executie voor t seconden uit"
|
||||
@@ -174,7 +174,7 @@ PythonSqrt = "Vierkantswortel"
|
||||
PythonSum = "Sommeer de items van een lijst"
|
||||
PythonTan = "Tangens"
|
||||
PythonTanh = "Tangens hyperbolicus"
|
||||
PythonText = "Display a text at (x,y) coordinates"
|
||||
PythonText = "Geef tekst weer op coördinaten (x,y)"
|
||||
PythonTimeFunction = "time module voorvoegsel"
|
||||
PythonTrunc = "x afgeknot tot een integer"
|
||||
PythonTurtleBackward = "Ga achterwaarts met x pixels"
|
||||
@@ -199,7 +199,7 @@ PythonTurtleSetposition = "Plaats de schildpad"
|
||||
PythonTurtleShowturtle = "Laat de schildpad zien"
|
||||
PythonTurtleSpeed = "Tekensnelheid tussen 0 and 10"
|
||||
PythonTurtleWrite = "Display a text"
|
||||
PythonUniform = "Zwevendekommagetal in [a,b]"
|
||||
PythonUniform = "Decimaal getal in [a,b]"
|
||||
PythonImportTime = "Import time module"
|
||||
PythonTimePrefix = "time module function prefix"
|
||||
PythonTimeSleep = "Wait for n second"
|
||||
|
||||
@@ -30,8 +30,8 @@ PythonColor = "Define uma cor rgb"
|
||||
PythonColorBlack = "Cor preta"
|
||||
PythonColorBlue = "Cor azul"
|
||||
PythonColorBrown = "Cor castanha"
|
||||
PythonColorGray = "Cor cinzenta"
|
||||
PythonColorGreen = "Cor verde"
|
||||
PythonColorGrey = "Cor cinzenta"
|
||||
PythonColorOrange = "Cor laranja"
|
||||
PythonColorPink = "Cor rosa"
|
||||
PythonColorPurple = "Cor roxa"
|
||||
|
||||
@@ -32,8 +32,8 @@ PythonCommandColor = "color(r,g,b)"
|
||||
PythonCommandColorBlack = "'black'"
|
||||
PythonCommandColorBlue = "'blue'"
|
||||
PythonCommandColorBrown = "'brown'"
|
||||
PythonCommandColorGray = "'gray'"
|
||||
PythonCommandColorGreen = "'green'"
|
||||
PythonCommandColorGrey = "'grey'"
|
||||
PythonCommandColorOrange = "'orange'"
|
||||
PythonCommandColorPink = "'pink'"
|
||||
PythonCommandColorPurple = "'purple'"
|
||||
|
||||
@@ -23,7 +23,7 @@ void EditorController::setScript(Script script, int scriptIndex) {
|
||||
m_script = script;
|
||||
m_scriptIndex = scriptIndex;
|
||||
|
||||
/* We edit the script direclty in the storage buffer. We thus put all the
|
||||
/* We edit the script directly in the storage buffer. We thus put all the
|
||||
* storage available space at the end of the current edited script and we set
|
||||
* its size.
|
||||
*
|
||||
@@ -36,8 +36,8 @@ void EditorController::setScript(Script script, int scriptIndex) {
|
||||
*
|
||||
* */
|
||||
|
||||
size_t newScriptSize = Ion::Storage::sharedStorage()->putAvailableSpaceAtEndOfRecord(m_script);
|
||||
m_editorView.setText(const_cast<char *>(m_script.content()), newScriptSize - Script::StatusSize());
|
||||
Ion::Storage::sharedStorage()->putAvailableSpaceAtEndOfRecord(m_script);
|
||||
m_editorView.setText(const_cast<char *>(m_script.content()), m_script.contentSize());
|
||||
}
|
||||
|
||||
void EditorController::willExitApp() {
|
||||
|
||||
@@ -67,11 +67,19 @@ void EditorView::GutterView::drawRect(KDContext * ctx, KDRect rect) const {
|
||||
KDCoordinate firstLine = m_offset / glyphSize.height();
|
||||
KDCoordinate firstLinePixelOffset = m_offset - firstLine * glyphSize.height();
|
||||
|
||||
char lineNumber[4];
|
||||
char lineNumber[k_lineNumberCharLength];
|
||||
int numberOfLines = bounds().height() / glyphSize.height() + 1;
|
||||
for (int i=0; i<numberOfLines; i++) {
|
||||
Poincare::Integer line(i + firstLine + 1);
|
||||
line.serialize(lineNumber, 4);
|
||||
// Only the first two digits are displayed
|
||||
int lineNumberValue = (i + firstLine + 1) % 100;
|
||||
Poincare::Integer line(lineNumberValue);
|
||||
if (firstLine < 10 || lineNumberValue >= 10) {
|
||||
line.serialize(lineNumber, k_lineNumberCharLength);
|
||||
} else {
|
||||
// Add a leading "0"
|
||||
lineNumber[0] = '0';
|
||||
line.serialize(lineNumber + 1, k_lineNumberCharLength - 1);
|
||||
}
|
||||
KDCoordinate leftPadding = (2 - strlen(lineNumber)) * glyphSize.width();
|
||||
ctx->drawString(
|
||||
lineNumber,
|
||||
|
||||
@@ -42,6 +42,7 @@ private:
|
||||
KDSize minimalSizeForOptimalDisplay() const override;
|
||||
private:
|
||||
static constexpr KDCoordinate k_margin = 2;
|
||||
static constexpr int k_lineNumberCharLength = 3;
|
||||
const KDFont * m_font;
|
||||
KDCoordinate m_offset;
|
||||
};
|
||||
|
||||
@@ -1,44 +1,20 @@
|
||||
#include "helpers.h"
|
||||
#include <string.h>
|
||||
#include <ion/unicode/code_point.h>
|
||||
#include <ion.h>
|
||||
#include <escher/clipboard.h>
|
||||
|
||||
namespace Code {
|
||||
namespace Helpers {
|
||||
|
||||
class EventTextPair {
|
||||
public:
|
||||
constexpr EventTextPair(Ion::Events::Event event, const char * text) : m_event(event), m_text(text) {}
|
||||
Ion::Events::Event event() const { return m_event; }
|
||||
const char * text() const { return m_text; }
|
||||
private:
|
||||
const Ion::Events::Event m_event;
|
||||
const char * m_text;
|
||||
};
|
||||
|
||||
static_assert('\x11' == UCodePointEmpty, "Unicode error");
|
||||
static constexpr EventTextPair sEventTextMap[] = {
|
||||
EventTextPair(Ion::Events::XNT, "x"),
|
||||
EventTextPair(Ion::Events::Exp, "exp(\x11)"),
|
||||
EventTextPair(Ion::Events::Ln, "log(\x11)"),
|
||||
EventTextPair(Ion::Events::Log, "log10(\x11)"),
|
||||
EventTextPair(Ion::Events::Imaginary, "1j"),
|
||||
EventTextPair(Ion::Events::Power, "**"),
|
||||
EventTextPair(Ion::Events::Pi, "pi"),
|
||||
EventTextPair(Ion::Events::Sqrt, "sqrt(\x11)"),
|
||||
EventTextPair(Ion::Events::Square, "**2"),
|
||||
EventTextPair(Ion::Events::Multiplication, "*"),
|
||||
EventTextPair(Ion::Events::EE, "e"),
|
||||
};
|
||||
|
||||
const char * PythonTextForEvent(Ion::Events::Event event) {
|
||||
for (size_t i=0; i<sizeof(sEventTextMap)/sizeof(sEventTextMap[0]); i++) {
|
||||
if (event == sEventTextMap[i].event()) {
|
||||
return sEventTextMap[i].text();
|
||||
for (size_t i=0; i<NumberOfPythonTextPairs; i++) {
|
||||
UTF8Helper::TextPair pair = PythonTextPairs[i];
|
||||
if (event.text() == pair.firstString()) {
|
||||
return pair.secondString();
|
||||
}
|
||||
if (event == Ion::Events::XNT) {
|
||||
return "x";
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,9 +440,7 @@ void PythonTextArea::addAutocompletion() {
|
||||
const int scriptIndex = m_contentView.pythonDelegate()->menuController()->editedScriptIndex();
|
||||
m_contentView.pythonDelegate()->variableBoxController()->loadFunctionsAndVariables(scriptIndex, autocompletionTokenBeginning, autocompletionLocation - autocompletionTokenBeginning);
|
||||
|
||||
if (addAutocompletionTextAtIndex(0)) {
|
||||
m_contentView.setAutocompleting(true);
|
||||
}
|
||||
addAutocompletionTextAtIndex(0);
|
||||
}
|
||||
|
||||
bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIndexToUpdate) {
|
||||
@@ -467,6 +465,7 @@ bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIn
|
||||
return false;
|
||||
}
|
||||
autocompletionLocation += textToInsertLength;
|
||||
m_contentView.setAutocompleting(true);
|
||||
m_contentView.setAutocompletionEnd(autocompletionLocation);
|
||||
}
|
||||
|
||||
@@ -479,8 +478,9 @@ bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIn
|
||||
if (addParentheses && m_contentView.insertTextAtLocation(parentheses, const_cast<char *>(autocompletionLocation), parenthesesLength)) {
|
||||
m_contentView.setAutocompleting(true);
|
||||
m_contentView.setAutocompletionEnd(autocompletionLocation + parenthesesLength);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return (textToInsertLength > 0);
|
||||
}
|
||||
|
||||
void PythonTextArea::cycleAutocompletion(bool downwards) {
|
||||
|
||||
@@ -131,7 +131,7 @@ const ToolboxMessageTree MatplotlibPyplotModuleChildren[] = {
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPink, I18n::Message::PythonColorPink, false),
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorOrange, I18n::Message::PythonColorOrange, false),
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPurple, I18n::Message::PythonColorPurple, false),
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGrey, I18n::Message::PythonColorGrey, false)
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGray, I18n::Message::PythonColorGray, false)
|
||||
};
|
||||
|
||||
const ToolboxMessageTree TurtleModuleChildren[] = {
|
||||
@@ -168,7 +168,7 @@ const ToolboxMessageTree TurtleModuleChildren[] = {
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPink, I18n::Message::PythonColorPink, false),
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorOrange, I18n::Message::PythonColorOrange, false),
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPurple, I18n::Message::PythonColorPurple, false),
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGrey, I18n::Message::PythonColorGrey, false)
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGray, I18n::Message::PythonColorGray, false)
|
||||
};
|
||||
|
||||
const ToolboxMessageTree RandomModuleChildren[] = {
|
||||
@@ -341,8 +341,8 @@ const ToolboxMessageTree catalogChildren[] = {
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPixel, I18n::Message::PythonGetPixel),
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetrandbits, I18n::Message::PythonGetrandbits),
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandGoto, I18n::Message::PythonTurtleGoto),
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGray, I18n::Message::PythonColorGray, false),
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGreen, I18n::Message::PythonColorGreen, false),
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGrey, I18n::Message::PythonColorGrey, false),
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGrid, I18n::Message::PythonGrid),
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandHeading, I18n::Message::PythonTurtleHeading, false),
|
||||
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandHex, I18n::Message::PythonHex),
|
||||
|
||||
@@ -49,6 +49,7 @@ public:
|
||||
bool autoImportationStatus() const;
|
||||
void toggleAutoimportationStatus();
|
||||
const char * content() const;
|
||||
size_t contentSize() { return value().size - k_statusSize; }
|
||||
|
||||
/* Fetched status */
|
||||
bool fetchedFromConsole() const;
|
||||
|
||||
@@ -13,7 +13,7 @@ void ScriptNodeCell::ScriptNodeView::drawRect(KDContext * ctx, KDRect rect) cons
|
||||
// If it exists, draw the description name.
|
||||
const char * descriptionName = m_scriptNode->description();
|
||||
if (descriptionName != nullptr) {
|
||||
ctx->drawString(descriptionName, KDPoint(0, m_frame.height() - k_bottomMargin - k_font->glyphSize().height()), k_font, Palette::GreyDark, backgroundColor);
|
||||
ctx->drawString(descriptionName, KDPoint(0, m_frame.height() - k_bottomMargin - k_font->glyphSize().height()), k_font, Palette::GrayDark, backgroundColor);
|
||||
}
|
||||
|
||||
// Draw the node name
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Functions = "Funzioni"
|
||||
Catalog = "Catalogo"
|
||||
Modules = "Moduli"
|
||||
LoopsAndTests = "Loops e test"
|
||||
LoopsAndTests = "Cicli e test"
|
||||
Files = "Files"
|
||||
Exceptions = "Exceptions"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Functions = "Functies"
|
||||
Catalog = "Catalogus"
|
||||
Modules = "Modules"
|
||||
LoopsAndTests = "Loops and tests"
|
||||
LoopsAndTests = "Herhalingen en testen"
|
||||
Files = "Files"
|
||||
Exceptions = "Exceptions"
|
||||
|
||||
@@ -45,7 +45,7 @@ VariableBoxController::VariableBoxController(ScriptStore * scriptStore) :
|
||||
{
|
||||
for (int i = 0; i < k_scriptOriginsCount; i++) {
|
||||
m_subtitleCells[i].setBackgroundColor(Palette::WallScreen);
|
||||
m_subtitleCells[i].setTextColor(Palette::BlueishGrey);
|
||||
m_subtitleCells[i].setTextColor(Palette::SecondaryText);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
11
apps/country_preferences.csv
Normal file
11
apps/country_preferences.csv
Normal file
@@ -0,0 +1,11 @@
|
||||
CountryCode,CountryPreferences::AvailableExamModes,CountryPreferences::MethodForQuartiles,Poincare::Preferences::UnitFormat,CountryPreferences::HomeAppsLayout
|
||||
WW,StandardOnly,MedianOfSublist,Metric,Default
|
||||
CA,StandardOnly,MedianOfSublist,Metric,Default
|
||||
DE,StandardOnly,MedianOfSublist,Metric,Default
|
||||
ES,StandardOnly,MedianOfSublist,Metric,Default
|
||||
FR,StandardOnly,CumulatedFrequency,Metric,Default
|
||||
GB,StandardOnly,MedianOfSublist,Metric,Default
|
||||
IT,StandardOnly,CumulatedFrequency,Metric,Default
|
||||
NL,All,MedianOfSublist,Metric,Default
|
||||
PT,StandardOnly,MedianOfSublist,Metric,Default
|
||||
US,StandardOnly,MedianOfSublist,Imperial,HidePython
|
||||
|
42
apps/country_preferences.h
Normal file
42
apps/country_preferences.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef COUNTRY_PREFERENCES_H
|
||||
#define COUNTRY_PREFERENCES_H
|
||||
|
||||
#include <poincare/preferences.h>
|
||||
|
||||
class CountryPreferences {
|
||||
public:
|
||||
enum class AvailableExamModes : uint8_t {
|
||||
StandardOnly,
|
||||
All
|
||||
};
|
||||
|
||||
enum class MethodForQuartiles : uint8_t {
|
||||
MedianOfSublist,
|
||||
CumulatedFrequency
|
||||
};
|
||||
|
||||
enum class HomeAppsLayout : uint8_t {
|
||||
Default,
|
||||
HidePython,
|
||||
};
|
||||
|
||||
constexpr CountryPreferences(AvailableExamModes availableExamModes, MethodForQuartiles methodForQuartiles, Poincare::Preferences::UnitFormat unitFormat, HomeAppsLayout homeAppsLayout) :
|
||||
m_availableExamModes(availableExamModes),
|
||||
m_methodForQuartiles(methodForQuartiles),
|
||||
m_unitFormat(unitFormat),
|
||||
m_homeAppsLayout(homeAppsLayout)
|
||||
{}
|
||||
|
||||
constexpr AvailableExamModes availableExamModes() const { return m_availableExamModes; }
|
||||
constexpr MethodForQuartiles methodForQuartiles() const { return m_methodForQuartiles; }
|
||||
constexpr Poincare::Preferences::UnitFormat unitFormat() const { return m_unitFormat; }
|
||||
constexpr HomeAppsLayout homeAppsLayout() const { return m_homeAppsLayout; }
|
||||
|
||||
private:
|
||||
const AvailableExamModes m_availableExamModes;
|
||||
const MethodForQuartiles m_methodForQuartiles;
|
||||
const Poincare::Preferences::UnitFormat m_unitFormat;
|
||||
const HomeAppsLayout m_homeAppsLayout;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -18,7 +18,7 @@ I18n::Message examModeActivationWarningMessage(GlobalPreferences::ExamMode mode,
|
||||
|
||||
// Exam mode behaviour
|
||||
KDColor examModeColor(GlobalPreferences::ExamMode mode);
|
||||
bool appIsForbiddenInExamMode(App::Descriptor::ExaminationLevel appExaminationLevel, GlobalPreferences::ExamMode mode);
|
||||
bool appIsForbiddenInExamMode(I18n::Message appName, GlobalPreferences::ExamMode mode);
|
||||
bool exactExpressionsAreForbidden(GlobalPreferences::ExamMode mode);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
#include "exam_mode_configuration.h"
|
||||
|
||||
using namespace Poincare;
|
||||
|
||||
constexpr Shared::SettingsMessageTree s_examModeMode[] = {Shared::SettingsMessageTree(I18n::Message::ExamModeModeStandard), Shared::SettingsMessageTree(I18n::Message::ExamModeModeNoSym), Shared::SettingsMessageTree(I18n::Message::ExamModeModeNoSymNoText)};
|
||||
constexpr Shared::SettingsMessageTree ExamModeConfiguration::s_modelExamChildren[] = {Shared::SettingsMessageTree(I18n::Message::ExamModeMode, s_examModeMode), Shared::SettingsMessageTree(I18n::Message::ActivateExamMode)};
|
||||
constexpr Shared::SettingsMessageTree ExamModeConfiguration::s_modelExamChildren[] = {Shared::SettingsMessageTree(I18n::Message::ActivateExamMode), Shared::SettingsMessageTree(I18n::Message::Default)};
|
||||
|
||||
int ExamModeConfiguration::numberOfAvailableExamMode() {
|
||||
return 2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
GlobalPreferences::ExamMode ExamModeConfiguration::examModeAtIndex(int index) {
|
||||
return (s_modelExamChildren[index].label() == I18n::Message::ExamModeModeStandard) ? GlobalPreferences::ExamMode::Standard : GlobalPreferences::ExamMode::NoSym;
|
||||
return GlobalPreferences::ExamMode::Standard;
|
||||
}
|
||||
|
||||
I18n::Message ExamModeConfiguration::examModeActivationMessage(int index) {
|
||||
@@ -22,23 +19,20 @@ I18n::Message ExamModeConfiguration::examModeActivationWarningMessage(GlobalPref
|
||||
I18n::Message warnings[] = {I18n::Message::ExitExamMode1, I18n::Message::ExitExamMode2, I18n::Message::Default};
|
||||
return warnings[line];
|
||||
}
|
||||
assert(mode == GlobalPreferences::ExamMode::Standard || mode == GlobalPreferences::ExamMode::NoSym || mode == GlobalPreferences::ExamMode::NoSymNoText);
|
||||
assert(mode == GlobalPreferences::ExamMode::Standard);
|
||||
I18n::Message warnings[] = {I18n::Message::ActiveExamModeMessage1, I18n::Message::ActiveExamModeMessage2, I18n::Message::ActiveExamModeMessage3};
|
||||
return warnings[line];
|
||||
}
|
||||
|
||||
KDColor ExamModeConfiguration::examModeColor(GlobalPreferences::ExamMode mode) {
|
||||
assert(mode == GlobalPreferences::ExamMode::Standard || mode == GlobalPreferences::ExamMode::NoSym || mode == GlobalPreferences::ExamMode::NoSymNoText);
|
||||
assert(mode == GlobalPreferences::ExamMode::Standard);
|
||||
return KDColorRed;
|
||||
}
|
||||
|
||||
bool ExamModeConfiguration::appIsForbiddenInExamMode(App::Descriptor::ExaminationLevel appExaminationLevel, GlobalPreferences::ExamMode mode) {
|
||||
if (mode == GlobalPreferences::ExamMode::NoSymNoText) {
|
||||
return appExaminationLevel == App::Descriptor::ExaminationLevel::Basic;
|
||||
}
|
||||
bool ExamModeConfiguration::appIsForbiddenInExamMode(I18n::Message appName, GlobalPreferences::ExamMode mode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ExamModeConfiguration::exactExpressionsAreForbidden(GlobalPreferences::ExamMode mode) {
|
||||
return mode == GlobalPreferences::ExamMode::NoSymNoText ? true : false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
56
apps/exam_mode_configuration_official.cpp
Normal file
56
apps/exam_mode_configuration_official.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
// Caution: Dutch exam mode is subject to a compliance certification by a government agency. Distribution of a non-certified Dutch exam mode is illegal.
|
||||
|
||||
#include "exam_mode_configuration.h"
|
||||
|
||||
constexpr Shared::SettingsMessageTree ExamModeConfiguration::s_modelExamChildren[2] = {Shared::SettingsMessageTree(I18n::Message::ActivateExamMode), Shared::SettingsMessageTree(I18n::Message::ActivateDutchExamMode)};
|
||||
|
||||
int ExamModeConfiguration::numberOfAvailableExamMode() {
|
||||
if (GlobalPreferences::sharedGlobalPreferences()->availableExamModes() == CountryPreferences::AvailableExamModes::StandardOnly
|
||||
|| GlobalPreferences::sharedGlobalPreferences()->isInExamMode())
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
assert(GlobalPreferences::sharedGlobalPreferences()->availableExamModes() == CountryPreferences::AvailableExamModes::All);
|
||||
return 2;
|
||||
}
|
||||
|
||||
GlobalPreferences::ExamMode ExamModeConfiguration::examModeAtIndex(int index) {
|
||||
return index == 0 ? GlobalPreferences::ExamMode::Standard : GlobalPreferences::ExamMode::Dutch;
|
||||
}
|
||||
|
||||
I18n::Message ExamModeConfiguration::examModeActivationMessage(int index) {
|
||||
return index == 0 ? I18n::Message::ActivateExamMode : I18n::Message::ActivateDutchExamMode;
|
||||
}
|
||||
|
||||
I18n::Message ExamModeConfiguration::examModeActivationWarningMessage(GlobalPreferences::ExamMode mode, int line) {
|
||||
if (mode == GlobalPreferences::ExamMode::Off) {
|
||||
I18n::Message warnings[] = {I18n::Message::ExitExamMode1, I18n::Message::ExitExamMode2, I18n::Message::Default};
|
||||
return warnings[line];
|
||||
} else if (mode == GlobalPreferences::ExamMode::Standard) {
|
||||
I18n::Message warnings[] = {I18n::Message::ActiveExamModeMessage1, I18n::Message::ActiveExamModeMessage2, I18n::Message::ActiveExamModeMessage3};
|
||||
return warnings[line];
|
||||
}
|
||||
assert(mode == GlobalPreferences::ExamMode::Dutch);
|
||||
I18n::Message warnings[] = {I18n::Message::ActiveDutchExamModeMessage1, I18n::Message::ActiveDutchExamModeMessage2, I18n::Message::ActiveDutchExamModeMessage3};
|
||||
return warnings[line];
|
||||
}
|
||||
|
||||
KDColor ExamModeConfiguration::examModeColor(GlobalPreferences::ExamMode mode) {
|
||||
/* The Dutch exam mode LED is supposed to be orange but we can only make
|
||||
* blink "pure" colors: with RGB leds on or off (as the PWM is used for
|
||||
* blinking). The closest "pure" color is Yellow. Moreover, Orange LED is
|
||||
* already used when the battery is charging. Using yellow, we can assert
|
||||
* that the yellow LED only means that Dutch exam mode is on and avoid
|
||||
* confusing states when the battery is charging and states when the Dutch
|
||||
* exam mode is on. */
|
||||
return mode == GlobalPreferences::ExamMode::Dutch ? KDColorYellow : KDColorRed;
|
||||
}
|
||||
|
||||
bool ExamModeConfiguration::appIsForbiddenInExamMode(I18n::Message appName, GlobalPreferences::ExamMode mode) {
|
||||
return appName == I18n::Message::CodeApp && mode == GlobalPreferences::ExamMode::Dutch;
|
||||
}
|
||||
|
||||
bool ExamModeConfiguration::exactExpressionsAreForbidden(GlobalPreferences::ExamMode mode) {
|
||||
return mode == GlobalPreferences::ExamMode::Dutch;
|
||||
}
|
||||
@@ -1,16 +1,32 @@
|
||||
#include "exam_pop_up_controller.h"
|
||||
#include "apps_container.h"
|
||||
#include "exam_mode_configuration.h"
|
||||
#include <apps/i18n.h>
|
||||
#include "global_preferences.h"
|
||||
#include <assert.h>
|
||||
#include <poincare/preferences.h>
|
||||
|
||||
using namespace Poincare;
|
||||
|
||||
ExamPopUpController::ExamPopUpController(ExamPopUpControllerDelegate * delegate) :
|
||||
ViewController(nullptr),
|
||||
m_contentView(this),
|
||||
PopUpController(
|
||||
k_numberOfLines,
|
||||
Invocation(
|
||||
[](void * context, void * sender) {
|
||||
ExamPopUpController * controller = (ExamPopUpController *)context;
|
||||
GlobalPreferences::ExamMode mode = controller->targetExamMode();
|
||||
assert(mode != GlobalPreferences::ExamMode::Unknown);
|
||||
GlobalPreferences::sharedGlobalPreferences()->setExamMode(mode);
|
||||
AppsContainer * container = AppsContainer::sharedAppsContainer();
|
||||
if (mode == GlobalPreferences::ExamMode::Off) {
|
||||
Ion::LED::setColor(KDColorBlack);
|
||||
Ion::LED::updateColorWithPlugAndCharge();
|
||||
} else {
|
||||
container->activateExamMode(mode);
|
||||
}
|
||||
container->refreshPreferences();
|
||||
Container::activeApp()->dismissModalViewController();
|
||||
return true;
|
||||
}, this)
|
||||
),
|
||||
m_targetExamMode(GlobalPreferences::ExamMode::Unknown),
|
||||
m_delegate(delegate)
|
||||
{
|
||||
@@ -18,11 +34,9 @@ ExamPopUpController::ExamPopUpController(ExamPopUpControllerDelegate * delegate)
|
||||
|
||||
void ExamPopUpController::setTargetExamMode(GlobalPreferences::ExamMode mode) {
|
||||
m_targetExamMode = mode;
|
||||
m_contentView.setMessagesForExamMode(mode);
|
||||
}
|
||||
|
||||
View * ExamPopUpController::view() {
|
||||
return &m_contentView;
|
||||
for (int i = 0; i < k_numberOfLines; i++) {
|
||||
m_contentView.setMessage(i, ExamModeConfiguration::examModeActivationWarningMessage(mode, i));
|
||||
}
|
||||
}
|
||||
|
||||
void ExamPopUpController::viewDidDisappear() {
|
||||
@@ -30,103 +44,3 @@ void ExamPopUpController::viewDidDisappear() {
|
||||
m_delegate->examDeactivatingPopUpIsDismissed();
|
||||
}
|
||||
}
|
||||
|
||||
void ExamPopUpController::didBecomeFirstResponder() {
|
||||
m_contentView.setSelectedButton(0);
|
||||
}
|
||||
|
||||
bool ExamPopUpController::handleEvent(Ion::Events::Event event) {
|
||||
if (event == Ion::Events::Left && m_contentView.selectedButton() == 1) {
|
||||
m_contentView.setSelectedButton(0);
|
||||
return true;
|
||||
}
|
||||
if (event == Ion::Events::Right && m_contentView.selectedButton() == 0) {
|
||||
m_contentView.setSelectedButton(1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ExamPopUpController::ContentView::ContentView(Responder * parentResponder) :
|
||||
m_cancelButton(parentResponder, I18n::Message::Cancel, Invocation([](void * context, void * sender) {
|
||||
Container::activeApp()->dismissModalViewController();
|
||||
return true;
|
||||
}, parentResponder), KDFont::SmallFont),
|
||||
m_okButton(parentResponder, I18n::Message::Ok, Invocation([](void * context, void * sender) {
|
||||
ExamPopUpController * controller = (ExamPopUpController *)context;
|
||||
GlobalPreferences::ExamMode mode = controller->targetExamMode();
|
||||
assert(mode != GlobalPreferences::ExamMode::Unknown);
|
||||
GlobalPreferences::sharedGlobalPreferences()->setExamMode(mode);
|
||||
AppsContainer * container = AppsContainer::sharedAppsContainer();
|
||||
if (mode == GlobalPreferences::ExamMode::Off) {
|
||||
Ion::LED::setColor(KDColorBlack);
|
||||
Ion::LED::updateColorWithPlugAndCharge();
|
||||
} else {
|
||||
container->activateExamMode(mode);
|
||||
}
|
||||
container->refreshPreferences();
|
||||
Container::activeApp()->dismissModalViewController();
|
||||
return true;
|
||||
}, parentResponder), KDFont::SmallFont),
|
||||
m_warningTextView(KDFont::SmallFont, I18n::Message::Warning, 0.5, 0.5, KDColorWhite, KDColorBlack),
|
||||
m_messageTextViews{}
|
||||
{
|
||||
for (int i = 0; i < k_maxNumberOfLines; i++) {
|
||||
m_messageTextViews[i].setFont(KDFont::SmallFont);
|
||||
m_messageTextViews[i].setAlignment(0.5f, 0.5f);
|
||||
m_messageTextViews[i].setBackgroundColor(KDColorBlack);
|
||||
m_messageTextViews[i].setTextColor(KDColorWhite);
|
||||
}
|
||||
}
|
||||
|
||||
void ExamPopUpController::ContentView::drawRect(KDContext * ctx, KDRect rect) const {
|
||||
ctx->fillRect(bounds(), KDColorBlack);
|
||||
}
|
||||
|
||||
void ExamPopUpController::ContentView::setSelectedButton(int selectedButton) {
|
||||
m_cancelButton.setHighlighted(selectedButton == 0);
|
||||
m_okButton.setHighlighted(selectedButton == 1);
|
||||
Container::activeApp()->setFirstResponder(selectedButton == 0 ? &m_cancelButton : &m_okButton);
|
||||
}
|
||||
|
||||
int ExamPopUpController::ContentView::selectedButton() {
|
||||
if (m_cancelButton.isHighlighted()) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ExamPopUpController::ContentView::setMessagesForExamMode(GlobalPreferences::ExamMode mode) {
|
||||
for (int i = 0; i < k_maxNumberOfLines; i++) {
|
||||
m_messageTextViews[i].setMessage(ExamModeConfiguration::examModeActivationWarningMessage(mode, i));
|
||||
}
|
||||
}
|
||||
|
||||
int ExamPopUpController::ContentView::numberOfSubviews() const {
|
||||
return 6;
|
||||
}
|
||||
|
||||
View * ExamPopUpController::ContentView::subviewAtIndex(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return &m_warningTextView;
|
||||
case 4:
|
||||
return &m_cancelButton;
|
||||
case 5:
|
||||
return &m_okButton;
|
||||
default:
|
||||
return &m_messageTextViews[index-1];
|
||||
}
|
||||
}
|
||||
|
||||
void ExamPopUpController::ContentView::layoutSubviews(bool force) {
|
||||
KDCoordinate height = bounds().height();
|
||||
KDCoordinate width = bounds().width();
|
||||
KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height();
|
||||
m_warningTextView.setFrame(KDRect(0, k_topMargin, width, textHeight), force);
|
||||
for (int i = 0; i < k_maxNumberOfLines; i++) {
|
||||
m_messageTextViews[i].setFrame(KDRect(0, k_topMargin+k_paragraphHeight+(i+1)*textHeight, width, textHeight), force);
|
||||
}
|
||||
m_cancelButton.setFrame(KDRect(k_buttonMargin, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight), force);
|
||||
m_okButton.setFrame(KDRect(2*k_buttonMargin+(width-3*k_buttonMargin)/2, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight), force);
|
||||
}
|
||||
|
||||
@@ -1,53 +1,20 @@
|
||||
#ifndef APPS_EXAM_POP_UP_CONTROLLER_H
|
||||
#define APPS_EXAM_POP_UP_CONTROLLER_H
|
||||
|
||||
#include <escher.h>
|
||||
#include <escher/pop_up_controller.h>
|
||||
#include "exam_pop_up_controller_delegate.h"
|
||||
#include "global_preferences.h"
|
||||
|
||||
class HighContrastButton : public Button {
|
||||
public:
|
||||
using Button::Button;
|
||||
KDColor highlightedBackgroundColor() const override { return Palette::ButtonBackgroundSelectedHighContrast; }
|
||||
};
|
||||
|
||||
class ExamPopUpController : public ViewController {
|
||||
class ExamPopUpController : public PopUpController {
|
||||
public:
|
||||
ExamPopUpController(ExamPopUpControllerDelegate * delegate);
|
||||
void setTargetExamMode(GlobalPreferences::ExamMode mode);
|
||||
GlobalPreferences::ExamMode targetExamMode() const { return m_targetExamMode; }
|
||||
// View Controller
|
||||
View * view() override;
|
||||
void viewDidDisappear() override;
|
||||
// Responder
|
||||
void didBecomeFirstResponder() override;
|
||||
bool handleEvent(Ion::Events::Event event) override;
|
||||
private:
|
||||
class ContentView : public View {
|
||||
public:
|
||||
ContentView(Responder * parentResponder);
|
||||
void drawRect(KDContext * ctx, KDRect rect) const override;
|
||||
void setSelectedButton(int selectedButton);
|
||||
int selectedButton();
|
||||
void setMessagesForExamMode(GlobalPreferences::ExamMode mode);
|
||||
private:
|
||||
constexpr static KDCoordinate k_buttonMargin = 10;
|
||||
constexpr static KDCoordinate k_buttonHeight = 20;
|
||||
constexpr static KDCoordinate k_topMargin = 12;
|
||||
constexpr static KDCoordinate k_paragraphHeight = 20;
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
HighContrastButton m_cancelButton;
|
||||
HighContrastButton m_okButton;
|
||||
MessageTextView m_warningTextView;
|
||||
constexpr static int k_maxNumberOfLines = 3;
|
||||
MessageTextView m_messageTextViews[k_maxNumberOfLines];
|
||||
};
|
||||
ContentView m_contentView;
|
||||
constexpr static int k_numberOfLines = 3;
|
||||
GlobalPreferences::ExamMode m_targetExamMode;
|
||||
ExamPopUpControllerDelegate * m_delegate;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -16,6 +16,12 @@ public:
|
||||
static GlobalPreferences * sharedGlobalPreferences();
|
||||
I18n::Language language() const { return m_language; }
|
||||
void setLanguage(I18n::Language language) { m_language = language; }
|
||||
I18n::Country country() const { return m_country; }
|
||||
void setCountry(I18n::Country country) { m_country = country; }
|
||||
CountryPreferences::AvailableExamModes availableExamModes() const { return I18n::CountryPreferencesArray[static_cast<uint8_t>(m_country)].availableExamModes(); }
|
||||
CountryPreferences::MethodForQuartiles methodForQuartiles() const { return I18n::CountryPreferencesArray[static_cast<uint8_t>(m_country)].methodForQuartiles(); }
|
||||
Poincare::Preferences::UnitFormat unitFormat() const { return I18n::CountryPreferencesArray[static_cast<uint8_t>(m_country)].unitFormat(); }
|
||||
CountryPreferences::HomeAppsLayout homeAppsLayout() const { return I18n::CountryPreferencesArray[static_cast<uint8_t>(m_country)].homeAppsLayout(); }
|
||||
bool isInExamMode() const { return (int8_t)examMode() > 0; }
|
||||
bool isInExamModeSymbolic() const { return !((int8_t)examMode() > 1); }
|
||||
ExamMode examMode() const;
|
||||
@@ -30,15 +36,18 @@ public:
|
||||
void setFont(const KDFont * font) { m_font = font; }
|
||||
constexpr static int NumberOfBrightnessStates = 15;
|
||||
private:
|
||||
static_assert(I18n::NumberOfLanguages > 0, "I18n::NumberOfLanguages is not superior to 0"); // There should already have be an error when processing an empty EPSILON_I18N flag
|
||||
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
|
||||
GlobalPreferences() :
|
||||
m_language((I18n::Language)0),
|
||||
m_country((I18n::Country)0),
|
||||
m_examMode(ExamMode::Unknown),
|
||||
m_tempExamMode(ExamMode::Standard),
|
||||
m_showPopUp(true),
|
||||
m_brightnessLevel(Ion::Backlight::MaxBrightness),
|
||||
m_font(KDFont::LargeFont) {}
|
||||
I18n::Language m_language;
|
||||
I18n::Country m_country;
|
||||
static_assert((int8_t)GlobalPreferences::ExamMode::Off == 0, "GlobalPreferences::isInExamMode() is not right");
|
||||
static_assert((int8_t)GlobalPreferences::ExamMode::Unknown < 0, "GlobalPreferences::isInExamMode() is not right");
|
||||
mutable ExamMode m_examMode;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
apps += Graph::App
|
||||
app_headers += apps/graph/app.h
|
||||
|
||||
app_graph_test_src = $(addprefix apps/graph/,\
|
||||
continuous_function_store.cpp \
|
||||
)
|
||||
|
||||
app_graph_src = $(addprefix apps/graph/,\
|
||||
app.cpp \
|
||||
continuous_function_store.cpp \
|
||||
graph/banner_view.cpp \
|
||||
graph/calculation_graph_controller.cpp \
|
||||
graph/calculation_parameter_controller.cpp \
|
||||
@@ -31,8 +34,15 @@ app_graph_src = $(addprefix apps/graph/,\
|
||||
values/values_controller.cpp \
|
||||
)
|
||||
|
||||
app_graph_src += $(app_graph_test_src)
|
||||
apps_src += $(app_graph_src)
|
||||
|
||||
i18n_files += $(call i18n_without_universal_for,graph/base)
|
||||
|
||||
tests_src += $(addprefix apps/graph/test/,\
|
||||
caching.cpp \
|
||||
helper.cpp \
|
||||
ranges.cpp \
|
||||
)
|
||||
|
||||
$(eval $(call depends_on_image,apps/graph/app.cpp,apps/graph/graph_icon.png))
|
||||
|
||||
@@ -55,10 +55,10 @@ void App::Snapshot::tidy() {
|
||||
App::App(Snapshot * snapshot) :
|
||||
FunctionApp(snapshot, &m_inputViewController),
|
||||
m_listController(&m_listFooter, &m_listHeader, &m_listFooter, this),
|
||||
m_listFooter(&m_listHeader, &m_listController, &m_listController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGrey),
|
||||
m_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->modelVersion(), snapshot->previousModelsVersions(), snapshot->rangeVersion(), snapshot->angleUnitVersion(), &m_graphHeader),
|
||||
m_graphController(&m_graphAlternateEmptyViewController, this, snapshot->graphRange(), snapshot->cursor(), snapshot->indexFunctionSelectedByCursor(), snapshot->rangeVersion(), &m_graphHeader),
|
||||
m_graphAlternateEmptyViewController(&m_graphHeader, &m_graphController, &m_graphController),
|
||||
m_graphHeader(&m_graphStackViewController, &m_graphAlternateEmptyViewController, &m_graphController),
|
||||
m_graphStackViewController(&m_tabViewController, &m_graphHeader),
|
||||
|
||||
@@ -12,22 +12,22 @@ IntervalTheta = "θ interval"
|
||||
IntervalX = "x interval"
|
||||
FunctionDomain = "Plotbereik"
|
||||
FunctionColor = "Functiekleur"
|
||||
NoFunction = "Geen functie"
|
||||
NoActivatedFunction = "Geen functie is ingeschakelt"
|
||||
PlotOptions = "Plot opties"
|
||||
NoFunction = "Geen functie gedefinieerd"
|
||||
NoActivatedFunction = "Geen functie ingeschakeld"
|
||||
PlotOptions = "Plotopties"
|
||||
Compute = "Bereken"
|
||||
Zeros = "Nulpunten"
|
||||
Tangent = "Tangens"
|
||||
Tangent = "Raaklijn"
|
||||
Intersection = "Snijpunt"
|
||||
Preimage = "Inverse beeld"
|
||||
Preimage = "Origineel"
|
||||
SelectLowerBound = "Selecteer ondergrens "
|
||||
SelectUpperBound = "Selecteer bovengrens "
|
||||
NoMaximumFound = "Geen maximum gevonden"
|
||||
NoMinimumFound = "Geen minimum gevonden"
|
||||
NoZeroFound = "Geen nulpunt gevonden"
|
||||
NoIntersectionFound = "Geen snijpunt gevonden"
|
||||
NoPreimageFound = "Geen inverse beeld gevonden"
|
||||
NoPreimageFound = "Geen origineel gevonden"
|
||||
DerivativeFunctionColumn = "Afgeleide functie kolom"
|
||||
HideDerivativeColumn = "Verberg de afgeleide functie"
|
||||
AllowedCharactersAZaz09 = "Toegestane tekens: A-Z, a-z, 0-9, _"
|
||||
ReservedName = "Voorbehouden naam"
|
||||
ReservedName = "Gereserveerde naam"
|
||||
|
||||
@@ -16,8 +16,9 @@ public:
|
||||
return recordSatisfyingTestAtIndex(i, &isFunctionActiveOfType, &plotType);
|
||||
}
|
||||
Shared::ExpiringPointer<Shared::ContinuousFunction> modelForRecord(Ion::Storage::Record record) const { return Shared::ExpiringPointer<Shared::ContinuousFunction>(static_cast<Shared::ContinuousFunction *>(privateModelForRecord(record))); }
|
||||
private:
|
||||
Shared::ContinuousFunctionCache * cacheAtIndex(int i) const { return (i < Shared::ContinuousFunctionCache::k_numberOfAvailableCaches) ? m_functionCaches + i : nullptr; }
|
||||
Ion::Storage::Record::ErrorStatus addEmptyModel() override;
|
||||
private:
|
||||
const char * modelExtension() const override { return Ion::Storage::funcExtension; }
|
||||
Shared::ExpressionModelHandle * setMemoizedModelAtIndex(int cacheIndex, Ion::Storage::Record record) const override;
|
||||
Shared::ExpressionModelHandle * memoizedModelAtIndex(int cacheIndex) const override;
|
||||
@@ -26,6 +27,8 @@ private:
|
||||
return isFunctionActive(model, context) && plotType == static_cast<Shared::ContinuousFunction *>(model)->plotType();
|
||||
}
|
||||
mutable Shared::ContinuousFunction m_functions[k_maxNumberOfMemoizedModels];
|
||||
mutable Shared::ContinuousFunctionCache m_functionCaches[Shared::ContinuousFunctionCache::k_numberOfAvailableCaches];
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ void CalculationGraphController::viewWillAppear() {
|
||||
m_isActive = true;
|
||||
assert(App::app()->functionStore()->modelForRecord(m_record)->plotType() == Shared::ContinuousFunction::PlotType::Cartesian);
|
||||
m_cursor->moveTo(pointOfInterest.x1(), pointOfInterest.x1(), pointOfInterest.x2());
|
||||
m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
|
||||
m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth());
|
||||
m_bannerView->setNumberOfSubviews(Shared::XYBannerView::k_numberOfSubviews);
|
||||
reloadBannerView();
|
||||
}
|
||||
@@ -64,7 +64,7 @@ bool CalculationGraphController::handleEnter() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CalculationGraphController::moveCursorHorizontally(int direction, bool fast) {
|
||||
bool CalculationGraphController::moveCursorHorizontally(int direction, int scrollspeed) {
|
||||
if (!m_isActive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ protected:
|
||||
bool m_isActive;
|
||||
private:
|
||||
bool handleEnter() override;
|
||||
bool moveCursorHorizontally(int direction, bool fast = false) override;
|
||||
bool moveCursorHorizontally(int direction, int scrollSpeed = 1) override;
|
||||
Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; }
|
||||
Shared::CurveView * curveView() override { return m_graphView; }
|
||||
};
|
||||
|
||||
@@ -12,6 +12,9 @@ public:
|
||||
TELEMETRY_ID("Minimum");
|
||||
private:
|
||||
Poincare::Coordinate2D<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override;
|
||||
// Prevent horizontal panning to preserve search interval
|
||||
float cursorRightMarginRatio() override { return 0.0f; }
|
||||
float cursorLeftMarginRatio() override { return 0.0f; }
|
||||
};
|
||||
|
||||
class MaximumGraphController : public CalculationGraphController {
|
||||
@@ -21,6 +24,9 @@ public:
|
||||
TELEMETRY_ID("Maximum");
|
||||
private:
|
||||
Poincare::Coordinate2D<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override;
|
||||
// Prevent horizontal panning to preserve search interval
|
||||
float cursorRightMarginRatio() override { return 0.0f; }
|
||||
float cursorLeftMarginRatio() override { return 0.0f; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ using namespace Shared;
|
||||
|
||||
namespace Graph {
|
||||
|
||||
GraphController::GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * previousModelsVersions, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header) :
|
||||
FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, curveViewRange, &m_view, cursor, indexFunctionSelectedByCursor, modelVersion, previousModelsVersions, rangeVersion, angleUnitVersion),
|
||||
GraphController::GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, ButtonRowController * header) :
|
||||
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),
|
||||
@@ -35,118 +35,10 @@ void GraphController::viewWillAppear() {
|
||||
selectFunctionWithCursor(indexFunctionSelectedByCursor());
|
||||
}
|
||||
|
||||
bool GraphController::defautRangeIsNormalized() const {
|
||||
bool GraphController::defaultRangeIsNormalized() const {
|
||||
return functionStore()->displaysNonCartesianFunctions();
|
||||
}
|
||||
|
||||
void GraphController::interestingFunctionRange(ExpiringPointer<ContinuousFunction> f, float tMin, float tMax, float step, float * xm, float * xM, float * ym, float * yM) const {
|
||||
Poincare::Context * context = textFieldDelegateApp()->localContext();
|
||||
const int balancedBound = std::floor((tMax-tMin)/2/step);
|
||||
for (int j = -balancedBound; j <= balancedBound ; j++) {
|
||||
float t = (tMin+tMax)/2 + step * j;
|
||||
Coordinate2D<float> xy = f->evaluateXYAtParameter(t, context);
|
||||
float x = xy.x1();
|
||||
float y = xy.x2();
|
||||
if (!std::isnan(x) && !std::isinf(x) && !std::isnan(y) && !std::isinf(y)) {
|
||||
*xm = std::min(*xm, x);
|
||||
*xM = std::max(*xM, x);
|
||||
*ym = std::min(*ym, y);
|
||||
*yM = std::max(*yM, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GraphController::interestingRanges(float * xm, float * xM, float * ym, float * yM) const {
|
||||
float resultxMin = FLT_MAX;
|
||||
float resultxMax = -FLT_MAX;
|
||||
float resultyMin = FLT_MAX;
|
||||
float resultyMax = -FLT_MAX;
|
||||
assert(functionStore()->numberOfActiveFunctions() > 0);
|
||||
int functionsCount = 0;
|
||||
if (functionStore()->displaysNonCartesianFunctions(&functionsCount)) {
|
||||
for (int i = 0; i < functionsCount; i++) {
|
||||
ExpiringPointer<ContinuousFunction> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i));
|
||||
if (f->plotType() == ContinuousFunction::PlotType::Cartesian) {
|
||||
continue;
|
||||
}
|
||||
/* Scan x-range from the middle to the extrema in order to get balanced
|
||||
* y-range for even functions (y = 1/x). */
|
||||
double tMin = f->tMin();
|
||||
double tMax = f->tMax();
|
||||
assert(!std::isnan(tMin));
|
||||
assert(!std::isnan(tMax));
|
||||
assert(!std::isnan(f->rangeStep()));
|
||||
interestingFunctionRange(f, tMin, tMax, f->rangeStep(), &resultxMin, &resultxMax, &resultyMin, &resultyMax);
|
||||
}
|
||||
if (resultxMin > resultxMax) {
|
||||
resultxMin = - Range1D::k_default;
|
||||
resultxMax = Range1D::k_default;
|
||||
}
|
||||
} else {
|
||||
resultxMin = const_cast<GraphController *>(this)->interactiveCurveViewRange()->xMin();
|
||||
resultxMax = const_cast<GraphController *>(this)->interactiveCurveViewRange()->xMax();
|
||||
}
|
||||
/* In practice, a step smaller than a pixel's width is needed for sampling
|
||||
* the values of a function. Otherwise some relevant extremal values may be
|
||||
* missed. */
|
||||
for (int i = 0; i < functionsCount; i++) {
|
||||
ExpiringPointer<ContinuousFunction> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i));
|
||||
if (f->plotType() != ContinuousFunction::PlotType::Cartesian) {
|
||||
continue;
|
||||
}
|
||||
/* Scan x-range from the middle to the extrema in order to get balanced
|
||||
* y-range for even functions (y = 1/x). */
|
||||
assert(!std::isnan(f->tMin()));
|
||||
assert(!std::isnan(f->tMax()));
|
||||
const double tMin = std::max(f->tMin(), resultxMin);
|
||||
const double tMax = std::min(f->tMax(), resultxMax);
|
||||
const double step = (tMax - tMin) / (2.0 * (m_view.bounds().width() - 1.0));
|
||||
interestingFunctionRange(f, tMin, tMax, step, &resultxMin, &resultxMax, &resultyMin, &resultyMax);
|
||||
}
|
||||
if (resultyMin > resultyMax) {
|
||||
resultyMin = - Range1D::k_default;
|
||||
resultyMax = Range1D::k_default;
|
||||
}
|
||||
|
||||
*xm = resultxMin;
|
||||
*xM = resultxMax;
|
||||
*ym = resultyMin;
|
||||
*yM = resultyMax;
|
||||
}
|
||||
|
||||
float GraphController::interestingXHalfRange() const {
|
||||
float characteristicRange = 0.0f;
|
||||
Poincare::Context * context = textFieldDelegateApp()->localContext();
|
||||
ContinuousFunctionStore * store = functionStore();
|
||||
int nbActiveFunctions = store->numberOfActiveFunctions();
|
||||
double tMin = INFINITY;
|
||||
double tMax = -INFINITY;
|
||||
for (int i = 0; i < nbActiveFunctions; i++) {
|
||||
ExpiringPointer<ContinuousFunction> f = store->modelForRecord(store->activeRecordAtIndex(i));
|
||||
float fRange = f->expressionReduced(context).characteristicXRange(context, Poincare::Preferences::sharedPreferences()->angleUnit());
|
||||
if (!std::isnan(fRange) && !std::isinf(fRange)) {
|
||||
characteristicRange = std::max(fRange, characteristicRange);
|
||||
}
|
||||
// Compute the combined range of the functions
|
||||
assert(f->plotType() == ContinuousFunction::PlotType::Cartesian); // So that tMin tMax represents xMin xMax
|
||||
tMin = std::min<double>(tMin, f->tMin());
|
||||
tMax = std::max<double>(tMax, f->tMax());
|
||||
}
|
||||
constexpr float rangeMultiplicator = 1.6f;
|
||||
if (characteristicRange > 0.0f ) {
|
||||
return rangeMultiplicator * characteristicRange;
|
||||
}
|
||||
float defaultXHalfRange = InteractiveCurveViewRangeDelegate::interestingXHalfRange();
|
||||
assert(tMin <= tMax);
|
||||
if (tMin >= -defaultXHalfRange && tMax <= defaultXHalfRange) {
|
||||
/* If the combined Range of the functions is smaller than the default range,
|
||||
* use it. */
|
||||
float f = rangeMultiplicator * (float)std::max(std::fabs(tMin), std::fabs(tMax));
|
||||
return (std::isnan(f) || std::isinf(f)) ? defaultXHalfRange : f;
|
||||
}
|
||||
return defaultXHalfRange;
|
||||
}
|
||||
|
||||
void GraphController::selectFunctionWithCursor(int functionIndex) {
|
||||
FunctionGraphController::selectFunctionWithCursor(functionIndex);
|
||||
ExpiringPointer<ContinuousFunction> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(functionIndex));
|
||||
@@ -165,9 +57,9 @@ void GraphController::reloadBannerView() {
|
||||
reloadDerivativeInBannerViewForCursorOnFunction(m_cursor, record);
|
||||
}
|
||||
|
||||
bool GraphController::moveCursorHorizontally(int direction, bool fast) {
|
||||
bool GraphController::moveCursorHorizontally(int direction, int scrollSpeed) {
|
||||
Ion::Storage::Record record = functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor());
|
||||
return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, record, fast);
|
||||
return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, record, scrollSpeed);
|
||||
}
|
||||
|
||||
int GraphController::nextCurveIndexVertically(bool goingUp, int currentSelectedCurve, Poincare::Context * context) const {
|
||||
@@ -187,10 +79,6 @@ double GraphController::defaultCursorT(Ion::Storage::Record record) {
|
||||
return function->tMin();
|
||||
}
|
||||
|
||||
bool GraphController::shouldSetDefaultOnModelChange() const {
|
||||
return functionStore()->displaysNonCartesianFunctions();
|
||||
}
|
||||
|
||||
void GraphController::jumpToLeftRightCurve(double t, int direction, int functionsCount, Ion::Storage::Record record) {
|
||||
if (functionsCount == 1) {
|
||||
return;
|
||||
|
||||
@@ -15,28 +15,25 @@ 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 * modelVersion, uint32_t * previousModelsVersions, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header);
|
||||
GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, ButtonRowController * header);
|
||||
I18n::Message emptyMessage() override;
|
||||
void viewWillAppear() override;
|
||||
bool displayDerivativeInBanner() const { return m_displayDerivativeInBanner; }
|
||||
void setDisplayDerivativeInBanner(bool displayDerivative) { m_displayDerivativeInBanner = displayDerivative; }
|
||||
float interestingXHalfRange() const override;
|
||||
void interestingRanges(float * xm, float * xM, float * ym, float * yM) const override;
|
||||
private:
|
||||
int estimatedBannerNumberOfLines() const override { return 1 + m_displayDerivativeInBanner; }
|
||||
void selectFunctionWithCursor(int functionIndex) override;
|
||||
BannerView * bannerView() override { return &m_bannerView; }
|
||||
void reloadBannerView() override;
|
||||
bool moveCursorHorizontally(int direction, bool fast = false) override;
|
||||
bool moveCursorHorizontally(int direction, int scrollSpeed = 1) override;
|
||||
int nextCurveIndexVertically(bool goingUp, int currentSelectedCurve, Poincare::Context * context) const override;
|
||||
double defaultCursorT(Ion::Storage::Record record) override;
|
||||
Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; }
|
||||
GraphView * functionGraphView() override { return &m_view; }
|
||||
CurveParameterController * curveParameterController() override { return &m_curveParameterController; }
|
||||
ContinuousFunctionStore * functionStore() const override { return static_cast<ContinuousFunctionStore *>(Shared::FunctionGraphController::functionStore()); }
|
||||
bool defautRangeIsNormalized() const override;
|
||||
bool defaultRangeIsNormalized() const override;
|
||||
void interestingFunctionRange(Shared::ExpiringPointer<Shared::ContinuousFunction> f, float tMin, float tMax, float step, float * xm, float * xM, float * ym, float * yM) const;
|
||||
bool shouldSetDefaultOnModelChange() const override;
|
||||
void jumpToLeftRightCurve(double t, int direction, int functionsCount, Ion::Storage::Record record) override;
|
||||
|
||||
Shared::RoundCursorView m_cursorView;
|
||||
|
||||
@@ -10,7 +10,7 @@ using namespace Poincare;
|
||||
|
||||
namespace Graph {
|
||||
|
||||
bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, bool fast) {
|
||||
bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, int scrollSpeed) {
|
||||
ExpiringPointer<ContinuousFunction> function = App::app()->functionStore()->modelForRecord(record);
|
||||
double tCursorPosition = cursor->t();
|
||||
double t = tCursorPosition;
|
||||
@@ -27,11 +27,11 @@ bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCurso
|
||||
function = App::app()->functionStore()->modelForRecord(record); // Reload the expiring pointer
|
||||
double dir = (direction > 0 ? 1.0 : -1.0);
|
||||
double step = function->plotType() == ContinuousFunction::PlotType::Cartesian ? range->xGridUnit()/numberOfStepsInGradUnit : (tMax-tMin)/k_definitionDomainDivisor;
|
||||
if (fast) {
|
||||
// TODO Navigate quicker the longer the user presses? (slow start)
|
||||
step *= 5.0;
|
||||
}
|
||||
t += dir * step;
|
||||
t += dir * step * scrollSpeed;
|
||||
|
||||
// If possible, round t so that f(x) matches f evaluated at displayed x
|
||||
t = FunctionBannerDelegate::getValueDisplayedOnBanner(t, App::app()->localContext(), 0.05 * step, true);
|
||||
|
||||
t = std::max(tMin, std::min(tMax, t));
|
||||
Coordinate2D<double> xy = function->evaluateXYAtParameter(t, App::app()->localContext());
|
||||
cursor->moveTo(t, xy.x1(), xy.x2());
|
||||
|
||||
@@ -11,7 +11,7 @@ class App;
|
||||
|
||||
class GraphControllerHelper {
|
||||
protected:
|
||||
bool privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, bool fast = false);
|
||||
bool privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, int scrollSpeed = 1);
|
||||
void reloadDerivativeInBannerViewForCursorOnFunction(Shared::CurveViewCursor * cursor, Ion::Storage::Record record);
|
||||
virtual BannerView * bannerView() = 0;
|
||||
private:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "graph_view.h"
|
||||
#include "../app.h"
|
||||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Shared;
|
||||
|
||||
@@ -27,7 +28,8 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const {
|
||||
const int activeFunctionsCount = functionStore->numberOfActiveFunctions();
|
||||
for (int i = 0; i < activeFunctionsCount ; i++) {
|
||||
Ion::Storage::Record record = functionStore->activeRecordAtIndex(i);
|
||||
ExpiringPointer<ContinuousFunction> f = functionStore->modelForRecord(record);;
|
||||
ExpiringPointer<ContinuousFunction> f = functionStore->modelForRecord(record);
|
||||
ContinuousFunctionCache * cch = functionStore->cacheAtIndex(i);
|
||||
Shared::ContinuousFunction::PlotType type = f->plotType();
|
||||
Poincare::Expression e = f->expressionReduced(context());
|
||||
if (e.isUndefined() || (
|
||||
@@ -38,21 +40,24 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const {
|
||||
}
|
||||
float tmin = f->tMin();
|
||||
float tmax = f->tMax();
|
||||
/* The step is a fraction of tmax-tmin. We will evaluate the function at
|
||||
* every step and if the consecutive dots are close enough, we won't
|
||||
* evaluate any more dot within the step. We pick a very strange fraction
|
||||
* denominator to avoid evaluating a periodic function periodically. For
|
||||
* example, if tstep was (tmax - tmin)/10, the polar function r(θ) = sin(5θ)
|
||||
* defined on 0..2π would be evaluated on r(0) = 0, r(π/5) = 0, r(2*π/5) = 0
|
||||
* which would lead to no curve at all. With 10.0938275501223, the
|
||||
* problematic functions are the functions whose period is proportionned to
|
||||
* 10.0938275501223 which are hopefully rare enough.
|
||||
* TODO: The drawCurve algorithm should use the derivative function to know
|
||||
* how fast the function moves... */
|
||||
float tstep = (tmax-tmin)/10.0938275501223f;
|
||||
|
||||
// Cartesian
|
||||
float tCacheMin, tCacheStep, tStepNonCartesian;
|
||||
if (type == ContinuousFunction::PlotType::Cartesian) {
|
||||
float rectLeft = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin);
|
||||
/* Here, tCacheMin can depend on rect (and change as the user move)
|
||||
* because cache can be panned for cartesian curves, instead of being
|
||||
* entirely invalidated. */
|
||||
tCacheMin = std::isnan(rectLeft) ? tmin : std::max(tmin, rectLeft);
|
||||
tCacheStep = pixelWidth();
|
||||
} else {
|
||||
tCacheMin = tmin;
|
||||
// Compute tCacheStep and tStepNonCartesian
|
||||
ContinuousFunctionCache::ComputeNonCartesianSteps(&tStepNonCartesian, &tCacheStep, tmax, tmin);
|
||||
}
|
||||
ContinuousFunctionCache::PrepareForCaching(f.operator->(), cch, tCacheMin, tCacheStep);
|
||||
|
||||
if (type == Shared::ContinuousFunction::PlotType::Cartesian) {
|
||||
// Cartesian
|
||||
drawCartesianCurve(ctx, rect, tmin, tmax, [](float t, void * model, void * context) {
|
||||
ContinuousFunction * f = (ContinuousFunction *)model;
|
||||
Poincare::Context * c = (Poincare::Context *)context;
|
||||
@@ -72,18 +77,22 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const {
|
||||
float maxAbscissa = pixelToFloat(Axis::Horizontal, rect.right());
|
||||
drawSegment(ctx, rect, minAbscissa, tangentParameterA*minAbscissa+tangentParameterB, maxAbscissa, tangentParameterA*maxAbscissa+tangentParameterB, Palette::GraphTangent, false);
|
||||
}
|
||||
continue;
|
||||
} else if (type == Shared::ContinuousFunction::PlotType::Polar) {
|
||||
// Polar
|
||||
drawPolarCurve(ctx, rect, tmin, tmax, tStepNonCartesian, [](float t, void * model, void * context) {
|
||||
ContinuousFunction * f = (ContinuousFunction *)model;
|
||||
Poincare::Context * c = (Poincare::Context *)context;
|
||||
return f->evaluateXYAtParameter(t, c);
|
||||
}, f.operator->(), context(), false, f->color());
|
||||
} else {
|
||||
// Parametric
|
||||
assert(type == Shared::ContinuousFunction::PlotType::Parametric);
|
||||
drawCurve(ctx, rect, tmin, tmax, tStepNonCartesian, [](float t, void * model, void * context) {
|
||||
ContinuousFunction * f = (ContinuousFunction *)model;
|
||||
Poincare::Context * c = (Poincare::Context *)context;
|
||||
return f->evaluateXYAtParameter(t, c);
|
||||
}, f.operator->(), context(), false, f->color());
|
||||
}
|
||||
|
||||
// Polar or parametric
|
||||
assert(
|
||||
type == Shared::ContinuousFunction::PlotType::Polar ||
|
||||
type == Shared::ContinuousFunction::PlotType::Parametric);
|
||||
drawCurve(ctx, rect, tmin, tmax, tstep, [](float t, void * model, void * context) {
|
||||
ContinuousFunction * f = (ContinuousFunction *)model;
|
||||
Poincare::Context * c = (Poincare::Context *)context;
|
||||
return f->evaluateXYAtParameter(t, c);
|
||||
}, f.operator->(), context(), false, f->color());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,19 @@ namespace Graph {
|
||||
|
||||
class GraphView : public Shared::FunctionGraphView {
|
||||
public:
|
||||
/* The step is a fraction of tmax-tmin. We will evaluate the function at
|
||||
* every step and if the consecutive dots are close enough, we won't
|
||||
* evaluate any more dot within the step. We pick a very strange fraction
|
||||
* denominator to avoid evaluating a periodic function periodically. For
|
||||
* example, if tstep was (tmax - tmin)/10, the polar function r(θ) = sin(5θ)
|
||||
* defined on 0..2π would be evaluated on r(0) = 0, r(π/5) = 0, r(2*π/5) = 0
|
||||
* which would lead to no curve at all. With 10.0938275501223, the
|
||||
* problematic functions are the functions whose period is proportionned to
|
||||
* 10.0938275501223 which are hopefully rare enough.
|
||||
* TODO: The drawCurve algorithm should use the derivative function to know
|
||||
* how fast the function moves... */
|
||||
static constexpr float k_graphStepDenominator = 10.0938275501223f;
|
||||
|
||||
GraphView(Shared::InteractiveCurveViewRange * graphRange,
|
||||
Shared::CurveViewCursor * cursor, Shared::BannerView * bannerView, Shared::CursorView * cursorView);
|
||||
void reload() override;
|
||||
|
||||
@@ -13,6 +13,9 @@ private:
|
||||
void reloadBannerView() override;
|
||||
Poincare::Coordinate2D<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override;
|
||||
Ion::Storage::Record m_intersectedRecord;
|
||||
// Prevent horizontal panning to preserve search interval
|
||||
float cursorRightMarginRatio() override { return 0.0f; }
|
||||
float cursorLeftMarginRatio() override { return 0.0f; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ public:
|
||||
TELEMETRY_ID("Root");
|
||||
private:
|
||||
Poincare::Coordinate2D<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override;
|
||||
// Prevent horizontal panning to preserve search interval
|
||||
float cursorRightMarginRatio() override { return 0.0f; }
|
||||
float cursorLeftMarginRatio() override { return 0.0f; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ const char * TangentGraphController::title() {
|
||||
|
||||
void TangentGraphController::viewWillAppear() {
|
||||
Shared::SimpleInteractiveCurveViewController::viewWillAppear();
|
||||
m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
|
||||
m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth());
|
||||
m_graphView->drawTangent(true);
|
||||
m_graphView->setOkView(nullptr);
|
||||
m_graphView->selectMainView(true);
|
||||
@@ -51,7 +51,7 @@ bool TangentGraphController::textFieldDidFinishEditing(TextField * textField, co
|
||||
assert(function->plotType() == Shared::ContinuousFunction::PlotType::Cartesian);
|
||||
double y = function->evaluate2DAtParameter(floatBody, myApp->localContext()).x2();
|
||||
m_cursor->moveTo(floatBody, floatBody, y);
|
||||
interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
|
||||
interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth());
|
||||
reloadBannerView();
|
||||
curveView()->reload();
|
||||
return true;
|
||||
@@ -90,7 +90,7 @@ void TangentGraphController::reloadBannerView() {
|
||||
m_bannerView->reload();
|
||||
}
|
||||
|
||||
bool TangentGraphController::moveCursorHorizontally(int direction, bool fast) {
|
||||
bool TangentGraphController::moveCursorHorizontally(int direction, int scrollSpeed) {
|
||||
return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, m_record);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ private:
|
||||
Shared::CurveView * curveView() override { return m_graphView; }
|
||||
BannerView * bannerView() override { return m_bannerView; };
|
||||
void reloadBannerView() override;
|
||||
bool moveCursorHorizontally(int direction, bool fast = false) override;
|
||||
bool moveCursorHorizontally(int direction, int scrollSpeed = 1) override;
|
||||
bool handleEnter() override;
|
||||
GraphView * m_graphView;
|
||||
BannerView * m_bannerView;
|
||||
|
||||
@@ -10,7 +10,13 @@ namespace Graph {
|
||||
DomainParameterController::DomainParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) :
|
||||
FloatParameterController<float>(parentResponder),
|
||||
m_domainCells{},
|
||||
m_record()
|
||||
m_record(),
|
||||
m_tempDomain(),
|
||||
m_confirmPopUpController(Invocation([](void * context, void * sender) {
|
||||
Container::activeApp()->dismissModalViewController();
|
||||
((DomainParameterController *)context)->stackController()->pop();
|
||||
return true;
|
||||
}, this))
|
||||
{
|
||||
for (int i = 0; i < k_totalNumberOfCell; i++) {
|
||||
m_domainCells[i].setParentResponder(&m_selectableTableView);
|
||||
@@ -18,12 +24,10 @@ DomainParameterController::DomainParameterController(Responder * parentResponder
|
||||
}
|
||||
}
|
||||
|
||||
const char * DomainParameterController::title() {
|
||||
return I18n::translate(I18n::Message::FunctionDomain);
|
||||
}
|
||||
|
||||
int DomainParameterController::numberOfRows() const {
|
||||
return k_totalNumberOfCell+1;
|
||||
void DomainParameterController::viewWillAppear() {
|
||||
// Initialize m_tempParameters to the extracted value.
|
||||
extractParameters();
|
||||
FloatParameterController::viewWillAppear();
|
||||
}
|
||||
|
||||
void DomainParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) {
|
||||
@@ -56,10 +60,6 @@ void DomainParameterController::willDisplayCellForIndex(HighlightCell * cell, in
|
||||
FloatParameterController::willDisplayCellForIndex(cell, index);
|
||||
}
|
||||
|
||||
int DomainParameterController::reusableParameterCellCount(int type) {
|
||||
return k_totalNumberOfCell;
|
||||
}
|
||||
|
||||
HighlightCell * DomainParameterController::reusableParameterCell(int index, int type) {
|
||||
assert(index >= 0 && index < k_totalNumberOfCell);
|
||||
return &m_domainCells[index];
|
||||
@@ -70,20 +70,49 @@ bool DomainParameterController::handleEvent(Ion::Events::Event event) {
|
||||
stackController()->pop();
|
||||
return true;
|
||||
}
|
||||
if (event == Ion::Events::Back && !equalTempParameters()) {
|
||||
// Open pop-up to confirm discarding values
|
||||
Container::activeApp()->displayModalViewController(&m_confirmPopUpController, 0.f, 0.f, Metric::ExamPopUpTopMargin, Metric::PopUpRightMargin, Metric::ExamPopUpBottomMargin, Metric::PopUpLeftMargin);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
float DomainParameterController::parameterAtIndex(int index) {
|
||||
return index == 0 ? function()->tMin() : function()->tMax();
|
||||
return index == 0 ? m_tempDomain.min() : m_tempDomain.max();
|
||||
}
|
||||
|
||||
void DomainParameterController::extractParameters() {
|
||||
setParameterAtIndex(0, function()->tMin());
|
||||
setParameterAtIndex(1, function()->tMax());
|
||||
/* Setting m_tempDomain tMin might affect m_tempDomain.max(), but setting tMax
|
||||
* right after will not affect m_tempDomain.min() because Function's Range1D
|
||||
* parameters are valid (tMax>tMin), and final tMin value is already set.
|
||||
* Same happens in confirmParameters when setting function's parameters from
|
||||
* valid m_tempDomain parameters. */
|
||||
assert(equalTempParameters());
|
||||
}
|
||||
|
||||
bool DomainParameterController::setParameterAtIndex(int parameterIndex, float f) {
|
||||
// TODO: what to do if the xmin > xmax?
|
||||
parameterIndex == 0 ? function()->setTMin(f) : function()->setTMax(f);
|
||||
/* Setting Min (or Max) parameter can alter the previously set Max
|
||||
* (or Min) parameter if Max <= Min. */
|
||||
parameterIndex == 0 ? m_tempDomain.setMin(f) : m_tempDomain.setMax(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DomainParameterController::confirmParameters() {
|
||||
function()->setTMin(parameterAtIndex(0));
|
||||
function()->setTMax(parameterAtIndex(1));
|
||||
// See comment on Range1D initialization in extractParameters
|
||||
assert(equalTempParameters());
|
||||
}
|
||||
|
||||
bool DomainParameterController::equalTempParameters() {
|
||||
return function()->tMin() == m_tempDomain.min() && function()->tMax() == m_tempDomain.max();
|
||||
}
|
||||
|
||||
void DomainParameterController::buttonAction() {
|
||||
confirmParameters();
|
||||
StackViewController * stack = stackController();
|
||||
stack->pop();
|
||||
stack->pop();
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "../../shared/continuous_function.h"
|
||||
#include "../../shared/expiring_pointer.h"
|
||||
#include "../../shared/float_parameter_controller.h"
|
||||
#include "../../shared/range_1D.h"
|
||||
#include "../../shared/discard_pop_up_controller.h"
|
||||
|
||||
namespace Graph {
|
||||
|
||||
@@ -14,17 +16,18 @@ public:
|
||||
DomainParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate);
|
||||
|
||||
// ViewController
|
||||
const char * title() override;
|
||||
const char * title() override { return I18n::translate(I18n::Message::FunctionDomain); }
|
||||
TELEMETRY_ID("DomainParameter");
|
||||
|
||||
// ListViewDataSource
|
||||
int numberOfRows() const override;
|
||||
int numberOfRows() const override { return k_totalNumberOfCell+1; }
|
||||
void willDisplayCellForIndex(HighlightCell * cell, int index) override;
|
||||
|
||||
void setRecord(Ion::Storage::Record record) { m_record = record; }
|
||||
private:
|
||||
constexpr static int k_totalNumberOfCell = 2;
|
||||
int reusableParameterCellCount(int type) override;
|
||||
void viewWillAppear() override;
|
||||
int reusableParameterCellCount(int type) override { return k_totalNumberOfCell; }
|
||||
HighlightCell * reusableParameterCell(int index, int type) override;
|
||||
bool handleEvent(Ion::Events::Event event) override;
|
||||
bool setParameterAtIndex(int parameterIndex, float f) override;
|
||||
@@ -32,8 +35,16 @@ private:
|
||||
void buttonAction() override;
|
||||
InfinityTolerance infinityAllowanceForRow(int row) const override;
|
||||
Shared::ExpiringPointer<Shared::ContinuousFunction> function() const;
|
||||
// Applies temporary parameters to function.
|
||||
void confirmParameters();
|
||||
// Extracts parameters from function, setting m_tempDomain parameters.
|
||||
void extractParameters();
|
||||
// Return true if temporary parameters and function parameters are equal.
|
||||
bool equalTempParameters();
|
||||
MessageTableCellWithEditableText m_domainCells[k_totalNumberOfCell];
|
||||
Ion::Storage::Record m_record;
|
||||
Shared::Range1D m_tempDomain;
|
||||
Shared::DiscardPopUpController m_confirmPopUpController;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ namespace Graph {
|
||||
TextFieldFunctionTitleCell::TextFieldFunctionTitleCell(ListController * listController, Orientation orientation, const KDFont * font) :
|
||||
Shared::FunctionTitleCell(orientation),
|
||||
Responder(listController),
|
||||
m_textField(Shared::Function::k_parenthesedThetaArgumentByteLength, this, m_textFieldBuffer, k_textFieldBufferSize, k_textFieldBufferSize, nullptr, listController, font, 1.0f, 0.5f)
|
||||
m_textField(Shared::Function::k_parenthesedThetaArgumentByteLength, this, m_textFieldBuffer, k_textFieldBufferSize, k_textFieldBufferSize, nullptr, listController, font, 1.0f, 0.5f),
|
||||
m_textFieldBuffer("")
|
||||
{
|
||||
}
|
||||
|
||||
@@ -62,6 +63,11 @@ void TextFieldFunctionTitleCell::layoutSubviews(bool force) {
|
||||
m_textField.setAlignment(horizontalAlignment, verticalAlignment());
|
||||
}
|
||||
|
||||
void TextFieldFunctionTitleCell::reloadCell() {
|
||||
layoutSubviews();
|
||||
FunctionTitleCell::reloadCell();
|
||||
}
|
||||
|
||||
void TextFieldFunctionTitleCell::didBecomeFirstResponder() {
|
||||
if (isEditing()) {
|
||||
Container::activeApp()->setFirstResponder(&m_textField);
|
||||
|
||||
@@ -35,6 +35,7 @@ public:
|
||||
return &m_textField;
|
||||
}
|
||||
void layoutSubviews(bool force = false) override;
|
||||
void reloadCell() override;
|
||||
|
||||
// Responder
|
||||
void didBecomeFirstResponder() override;
|
||||
|
||||
124
apps/graph/test/caching.cpp
Normal file
124
apps/graph/test/caching.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
#include <quiz.h>
|
||||
#include "helper.h"
|
||||
#include <cmath>
|
||||
|
||||
using namespace Poincare;
|
||||
using namespace Shared;
|
||||
|
||||
namespace Graph {
|
||||
|
||||
bool floatEquals(float a, float b, float tolerance = 1.f/static_cast<float>(Ion::Display::Height)) {
|
||||
/* The default value for the tolerance is chosen so that the error introduced
|
||||
* by caching would not typically be visible on screen. */
|
||||
return (std::isnan(a) && std::isnan(b)) || IsApproximatelyEqual(a, b, tolerance, 0.);
|
||||
}
|
||||
|
||||
void assert_check_cartesian_cache_against_function(ContinuousFunction * function, ContinuousFunctionCache * cache, Context * context, float tMin) {
|
||||
/* We set the cache to nullptr to force the evaluation (otherwise we would be
|
||||
* comparing the cache against itself). */
|
||||
function->setCache(nullptr);
|
||||
|
||||
float t;
|
||||
for (int i = 0; i < Ion::Display::Width; i++) {
|
||||
t = tMin + i*cache->step();
|
||||
Coordinate2D<float> cacheValues = cache->valueForParameter(function, context, t);
|
||||
Coordinate2D<float> functionValues = function->evaluateXYAtParameter(t, context);
|
||||
quiz_assert(floatEquals(t, cacheValues.x1()));
|
||||
quiz_assert(floatEquals(t, functionValues.x1()));
|
||||
quiz_assert(floatEquals(cacheValues.x2(), functionValues.x2()));
|
||||
}
|
||||
/* We set back the cache, so that it will not be invalidated in
|
||||
* PrepareForCaching later. */
|
||||
function->setCache(cache);
|
||||
}
|
||||
|
||||
void assert_cartesian_cache_stays_valid_while_panning(ContinuousFunction * function, Context * context, InteractiveCurveViewRange * range, CurveViewCursor * cursor, ContinuousFunctionStore * store, float step) {
|
||||
ContinuousFunctionCache * cache = store->cacheAtIndex(0);
|
||||
assert(cache);
|
||||
|
||||
float tMin, tStep;
|
||||
constexpr float margin = 0.04f;
|
||||
constexpr int numberOfMoves = 30;
|
||||
for (int i = 0; i < numberOfMoves; i++) {
|
||||
cursor->moveTo(cursor->t() + step, cursor->x() + step, function->evaluateXYAtParameter(cursor->x() + step, context).x2());
|
||||
range->panToMakePointVisible(cursor->x(), cursor->y(), margin, margin, margin, margin, (range->xMax() - range->xMin()) / (Ion::Display::Width - 1));
|
||||
tMin = range->xMin();
|
||||
tStep = (range->xMax() - range->xMin()) / (Ion::Display::Width - 1);
|
||||
ContinuousFunctionCache::PrepareForCaching(function, cache, tMin, tStep);
|
||||
assert_check_cartesian_cache_against_function(function, cache, context, tMin);
|
||||
}
|
||||
}
|
||||
|
||||
void assert_check_polar_cache_against_function(ContinuousFunction * function, Context * context, InteractiveCurveViewRange * range, ContinuousFunctionStore * store) {
|
||||
ContinuousFunctionCache * cache = store->cacheAtIndex(0);
|
||||
assert(cache);
|
||||
|
||||
float tMin = range->xMin();
|
||||
float tMax = range->xMax();
|
||||
|
||||
float tStep, tCacheStep;
|
||||
ContinuousFunctionCache::ComputeNonCartesianSteps(&tStep, &tCacheStep, tMax, tMin);
|
||||
|
||||
ContinuousFunctionCache::PrepareForCaching(function, cache, tMin, tCacheStep);
|
||||
|
||||
// Fill the cache
|
||||
float t;
|
||||
for (int i = 0; i < Ion::Display::Width / 2; i++) {
|
||||
t = tMin + i*cache->step();
|
||||
function->evaluateXYAtParameter(t, context);
|
||||
}
|
||||
|
||||
function->setCache(nullptr);
|
||||
for (int i = 0; i < Ion::Display::Width / 2; i++) {
|
||||
t = tMin + i*cache->step();
|
||||
Coordinate2D<float> cacheValues = cache->valueForParameter(function, context, t);
|
||||
Coordinate2D<float> functionValues = function->evaluateXYAtParameter(t, context);
|
||||
quiz_assert(floatEquals(cacheValues.x1(), functionValues.x1()));
|
||||
quiz_assert(floatEquals(cacheValues.x2(), functionValues.x2()));
|
||||
}
|
||||
}
|
||||
|
||||
void assert_cache_stays_valid(ContinuousFunction::PlotType type, const char * definition, float rangeXMin = -5, float rangeXMax = 5) {
|
||||
GlobalContext globalContext;
|
||||
ContinuousFunctionStore functionStore;
|
||||
|
||||
InteractiveCurveViewRange graphRange;
|
||||
graphRange.setXMin(rangeXMin);
|
||||
graphRange.setXMax(rangeXMax);
|
||||
graphRange.setYMin(-3.f);
|
||||
graphRange.setYMax(3.f);
|
||||
|
||||
CurveViewCursor cursor;
|
||||
ContinuousFunction * function = addFunction(definition, type, &functionStore, &globalContext);
|
||||
Coordinate2D<float> origin = function->evaluateXYAtParameter(0.f, &globalContext);
|
||||
cursor.moveTo(0.f, origin.x1(), origin.x2());
|
||||
|
||||
if (type == Cartesian) {
|
||||
assert_cartesian_cache_stays_valid_while_panning(function, &globalContext, &graphRange, &cursor, &functionStore, 2.f);
|
||||
assert_cartesian_cache_stays_valid_while_panning(function, &globalContext, &graphRange, &cursor, &functionStore, -0.4f);
|
||||
} else {
|
||||
assert_check_polar_cache_against_function(function, &globalContext, &graphRange, &functionStore);
|
||||
}
|
||||
|
||||
functionStore.removeAll();
|
||||
}
|
||||
|
||||
QUIZ_CASE(graph_caching) {
|
||||
assert_cache_stays_valid(Cartesian, "x");
|
||||
assert_cache_stays_valid(Cartesian, "x^2");
|
||||
assert_cache_stays_valid(Cartesian, "sin(x)");
|
||||
assert_cache_stays_valid(Cartesian, "sin(x)", -1e6f, 2e8f);
|
||||
assert_cache_stays_valid(Cartesian, "sin(x^2)");
|
||||
assert_cache_stays_valid(Cartesian, "1/x");
|
||||
assert_cache_stays_valid(Cartesian, "1/x", -5e-5f, 5e-5f);
|
||||
assert_cache_stays_valid(Cartesian, "-ℯ^x");
|
||||
|
||||
assert_cache_stays_valid(Polar, "1", 0.f, 360.f);
|
||||
assert_cache_stays_valid(Polar, "θ", 0.f, 360.f);
|
||||
assert_cache_stays_valid(Polar, "sin(θ)", 0.f, 360.f);
|
||||
assert_cache_stays_valid(Polar, "sin(θ)", 2e-4f, 1e-3f);
|
||||
assert_cache_stays_valid(Polar, "cos(5θ)", 0.f, 360.f);
|
||||
assert_cache_stays_valid(Polar, "cos(5θ)", -1e8f, 1e8f);
|
||||
}
|
||||
|
||||
}
|
||||
16
apps/graph/test/helper.cpp
Normal file
16
apps/graph/test/helper.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "helper.h"
|
||||
|
||||
namespace Graph {
|
||||
|
||||
ContinuousFunction * addFunction(const char * definition, ContinuousFunction::PlotType type, ContinuousFunctionStore * store, Context * context) {
|
||||
Ion::Storage::Record::ErrorStatus err = store->addEmptyModel();
|
||||
assert(err == Ion::Storage::Record::ErrorStatus::None);
|
||||
(void) err; // Silence compilation warning about unused variable.
|
||||
Ion::Storage::Record record = store->recordAtIndex(store->numberOfModels() - 1);
|
||||
ContinuousFunction * f = static_cast<ContinuousFunction *>(store->modelForRecord(record).operator->());
|
||||
f->setPlotType(type, Poincare::Preferences::AngleUnit::Degree, context);
|
||||
f->setContent(definition, context);
|
||||
return f;
|
||||
}
|
||||
|
||||
}
|
||||
20
apps/graph/test/helper.h
Normal file
20
apps/graph/test/helper.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef APPS_GRAPH_TEST_HELPER_H
|
||||
#define APPS_GRAPH_TEST_HELPER_H
|
||||
|
||||
#include "../app.h"
|
||||
#include "../../poincare/test/helper.h"
|
||||
|
||||
using namespace Poincare;
|
||||
using namespace Shared;
|
||||
|
||||
namespace Graph {
|
||||
|
||||
constexpr ContinuousFunction::PlotType Cartesian = ContinuousFunction::PlotType::Cartesian;
|
||||
constexpr ContinuousFunction::PlotType Polar = ContinuousFunction::PlotType::Polar;
|
||||
constexpr ContinuousFunction::PlotType Parametric = ContinuousFunction::PlotType::Parametric;
|
||||
|
||||
ContinuousFunction * addFunction(const char * definition, ContinuousFunction::PlotType type, ContinuousFunctionStore * store, Context * context);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
216
apps/graph/test/ranges.cpp
Normal file
216
apps/graph/test/ranges.cpp
Normal file
@@ -0,0 +1,216 @@
|
||||
#include <quiz.h>
|
||||
#include "helper.h"
|
||||
|
||||
using namespace Poincare;
|
||||
using namespace Shared;
|
||||
|
||||
namespace Graph {
|
||||
|
||||
class AdHocGraphController : public InteractiveCurveViewRangeDelegate {
|
||||
public:
|
||||
/* These margins are obtained from instance methods of the various derived
|
||||
* class of SimpleInteractiveCurveViewController. As we cannot create an
|
||||
* instance of this class here, we define those directly. */
|
||||
static constexpr float k_topMargin = 0.068f;
|
||||
static constexpr float k_bottomMargin = 0.132948f;
|
||||
static constexpr float k_leftMargin = 0.04f;
|
||||
static constexpr float k_rightMargin = 0.04f;
|
||||
|
||||
static float Ratio() { return InteractiveCurveViewRange::NormalYXRatio() / (1.f + k_topMargin + k_bottomMargin); }
|
||||
|
||||
Context * context() { return &m_context; }
|
||||
ContinuousFunctionStore * functionStore() const { return &m_store; }
|
||||
|
||||
// InteractiveCurveViewRangeDelegate
|
||||
bool defaultRangeIsNormalized() const override { return functionStore()->displaysNonCartesianFunctions(); }
|
||||
void interestingRanges(InteractiveCurveViewRange * range) override { DefaultInterestingRanges(range, context(), functionStore(), Ratio()); }
|
||||
float addMargin(float x, float range, bool isVertical, bool isMin) override { return DefaultAddMargin(x, range, isVertical, isMin, k_topMargin, k_bottomMargin, k_leftMargin, k_rightMargin); }
|
||||
void updateZoomButtons() override {}
|
||||
|
||||
private:
|
||||
mutable GlobalContext m_context;
|
||||
mutable ContinuousFunctionStore m_store;
|
||||
};
|
||||
|
||||
bool float_equal(float a, float b, float tolerance = 10.f * FLT_EPSILON) {
|
||||
return IsApproximatelyEqual(a, b, tolerance, 0.);
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
void assert_best_range_is(const char * const (&definitions)[N], ContinuousFunction::PlotType const (&plotTypes)[N], float targetXMin, float targetXMax, float targetYMin, float targetYMax, Poincare::Preferences::AngleUnit angleUnit = Radian) {
|
||||
assert(std::isfinite(targetXMin) && std::isfinite(targetXMax) && std::isfinite(targetYMin) && std::isfinite(targetYMax)
|
||||
&& targetXMin < targetXMax && targetYMin < targetYMax);
|
||||
|
||||
Preferences::sharedPreferences()->setAngleUnit(angleUnit);
|
||||
|
||||
AdHocGraphController graphController;
|
||||
InteractiveCurveViewRange graphRange(&graphController);
|
||||
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
addFunction(definitions[i], plotTypes[i], graphController.functionStore(), graphController.context());
|
||||
}
|
||||
graphRange.setDefault();
|
||||
float xMin = graphRange.xMin();
|
||||
float xMax = graphRange.xMax();
|
||||
float yMin = graphRange.yMin();
|
||||
float yMax = graphRange.yMax();
|
||||
quiz_assert(float_equal(xMin, targetXMin) && float_equal(xMax, targetXMax) && float_equal(yMin, targetYMin) && float_equal(yMax, targetYMax));
|
||||
|
||||
graphController.functionStore()->removeAll();
|
||||
}
|
||||
|
||||
void assert_best_cartesian_range_is(const char * definition, float targetXMin, float targetXMax, float targetYMin, float targetYMax, Poincare::Preferences::AngleUnit angleUnit = Radian, ContinuousFunction::PlotType plotType = Cartesian) {
|
||||
const char * definitionArray[1] = { definition };
|
||||
ContinuousFunction::PlotType plotTypeArray[1] = { plotType };
|
||||
assert_best_range_is(definitionArray, plotTypeArray, targetXMin, targetXMax, targetYMin, targetYMax, angleUnit);
|
||||
}
|
||||
|
||||
QUIZ_CASE(graph_ranges_single_function) {
|
||||
assert_best_cartesian_range_is("undef", -10, 10, -5.81249952, 4.81249952);
|
||||
assert_best_cartesian_range_is("x!", -10, 10, -5.81249952, 4.81249952);
|
||||
|
||||
assert_best_cartesian_range_is("0", -10, 10, -5.81249952, 4.81249952);
|
||||
assert_best_cartesian_range_is("1", -10, 10, -4.81249952, 5.81249952);
|
||||
assert_best_cartesian_range_is("-100", -10, 10, -105.8125, -95.1875);
|
||||
assert_best_cartesian_range_is("0.01", -10, 10, -5.81249952, 4.81249952);
|
||||
|
||||
assert_best_cartesian_range_is("x", -10, 10, -5.66249943, 4.96249962);
|
||||
assert_best_cartesian_range_is("x+1", -12, 10, -6.19374943, 5.49374962);
|
||||
assert_best_cartesian_range_is("-x+5", -6, 17, -6.55937433, 5.65937471);
|
||||
assert_best_cartesian_range_is("x/2+2", -15, 7, -6.19374943, 5.49374962);
|
||||
|
||||
|
||||
assert_best_cartesian_range_is("x^2", -10, 10, -1.31249952, 9.3125);
|
||||
assert_best_cartesian_range_is("x^3", -10, 10, -5.16249943, 5.46249962);
|
||||
assert_best_cartesian_range_is("-2x^6", -10, 10, -16000, 2000);
|
||||
assert_best_cartesian_range_is("3x^2+x+10", -12, 11, 7.84062624, 20.0593758);
|
||||
|
||||
assert_best_cartesian_range_is("1/x", -4.51764774, 4.51764774, -2.60000014, 2.20000005);
|
||||
assert_best_cartesian_range_is("1/(1-x)", -3.51176548, 5.71176529, -2.60000014, 2.29999995);
|
||||
assert_best_cartesian_range_is("1/(x^2+1)", -3.4000001, 3.4000001, -0.200000003, 1.10000002);
|
||||
|
||||
assert_best_cartesian_range_is("sin(x)", -15, 15, -1.39999998, 1.20000005, Radian);
|
||||
assert_best_cartesian_range_is("cos(x)", -1000, 1000, -1.39999998, 1.20000005, Degree);
|
||||
assert_best_cartesian_range_is("tan(x)", -1000, 1000, -3.9000001, 3.4000001, Gradian);
|
||||
assert_best_cartesian_range_is("tan(x-100)", -1200, 1200, -4, 3.5, Gradian);
|
||||
|
||||
assert_best_cartesian_range_is("ℯ^x", -10, 10, -1.71249962, 8.91249943);
|
||||
assert_best_cartesian_range_is("ℯ^x+4", -10, 10, 2.28750038, 12.9124994);
|
||||
assert_best_cartesian_range_is("ℯ^(-x)", -10, 10, -1.71249962, 8.91249943);
|
||||
assert_best_cartesian_range_is("(1-x)ℯ^(1/(1-x))", -1.8, 2.9, -3, 5.1);
|
||||
|
||||
assert_best_cartesian_range_is("ln(x)", -2.85294199, 8.25294113, -3.5, 2.4000001);
|
||||
assert_best_cartesian_range_is("log(x)", -0.900000036, 3.20000005, -1.23906231, 0.939062357);
|
||||
|
||||
assert_best_cartesian_range_is("√(x)", -3, 10, -2.10312462, 4.80312443);
|
||||
assert_best_cartesian_range_is("√(x^2+1)-x", -10, 10, -1.26249981, 9.36249924);
|
||||
assert_best_cartesian_range_is("root(x^3+1,3)-x", -2, 2.5, -0.445312381, 1.94531238);
|
||||
}
|
||||
|
||||
QUIZ_CASE(graph_ranges_several_functions) {
|
||||
{
|
||||
const char * definitions[] = {"ℯ^x", "ln(x)"};
|
||||
ContinuousFunction::PlotType types[] = {Cartesian, Cartesian};
|
||||
assert_best_range_is(definitions, types, -10, 10, -2.81249952, 7.81249952);
|
||||
}
|
||||
{
|
||||
const char * definitions[] = {"x/2+2", "-x+5"};
|
||||
ContinuousFunction::PlotType types[] = {Cartesian, Cartesian};
|
||||
assert_best_range_is(definitions, types, -16, 17, -9.21562386, 8.31562424);
|
||||
}
|
||||
{
|
||||
const char * definitions[] = {"sin(θ)", "cos(θ)"};
|
||||
ContinuousFunction::PlotType types[] = {Polar, Polar};
|
||||
assert_best_range_is(definitions, types, -1.63235319, 2.13235331, -0.800000011, 1.20000005);
|
||||
}
|
||||
}
|
||||
|
||||
void assert_zooms_to(float xMin, float xMax, float yMin, float yMax, float targetXMin, float targetXMax, float targetYMin, float targetYMax, bool conserveRatio, bool zoomIn) {
|
||||
float ratio = zoomIn ? 1.f / ZoomCurveViewController::k_zoomOutRatio : ZoomCurveViewController::k_zoomOutRatio;
|
||||
|
||||
InteractiveCurveViewRange graphRange;
|
||||
graphRange.setXMin(xMin);
|
||||
graphRange.setXMax(xMax);
|
||||
graphRange.setYMin(yMin);
|
||||
graphRange.setYMax(yMax);
|
||||
|
||||
float xCenter = (xMax + xMin) / 2.f;
|
||||
float yCenter = (yMax + yMin) / 2.f;
|
||||
|
||||
graphRange.zoom(ratio, xCenter, yCenter);
|
||||
|
||||
quiz_assert(float_equal(graphRange.xMin(), targetXMin) && float_equal(graphRange.xMax(), targetXMax) && float_equal(graphRange.yMin(), targetYMin) && float_equal(graphRange.yMax(), targetYMax));
|
||||
quiz_assert(float_equal((yMax - yMin) / (xMax - xMin), (targetYMax - targetYMin) / (targetXMax - targetXMin)) == conserveRatio);
|
||||
}
|
||||
|
||||
void assert_zooms_in_to(float xMin, float xMax, float yMin, float yMax, float targetXMin, float targetXMax, float targetYMin, float targetYMax, bool conserveRatio) {
|
||||
assert_zooms_to(xMin, xMax, yMin, yMax, targetXMin, targetXMax, targetYMin, targetYMax, conserveRatio, true);
|
||||
}
|
||||
|
||||
void assert_zooms_out_to(float xMin, float xMax, float yMin, float yMax, float targetXMin, float targetXMax, float targetYMin, float targetYMax, bool conserveRatio) {
|
||||
assert_zooms_to(xMin, xMax, yMin, yMax, targetXMin, targetXMax, targetYMin, targetYMax, conserveRatio, false);
|
||||
}
|
||||
|
||||
QUIZ_CASE(graph_ranges_zoom) {
|
||||
assert_zooms_in_to(
|
||||
-12, 12, -12, 12,
|
||||
-8, 8, -8, 8,
|
||||
true);
|
||||
|
||||
assert_zooms_in_to(
|
||||
-3, 3, 0, 1e-4,
|
||||
-3, 3, 0, 1e-4,
|
||||
true);
|
||||
|
||||
assert_zooms_out_to(
|
||||
-10, 10, -10, 10,
|
||||
-15, 15, -15, 15,
|
||||
true);
|
||||
|
||||
assert_zooms_out_to(
|
||||
-1, 1, 9e7, 1e8,
|
||||
-1.5, 1.5, 87500000, 1e8,
|
||||
false);
|
||||
}
|
||||
|
||||
void assert_orthonormality(float xMin, float xMax, float yMin, float yMax, bool orthonormal) {
|
||||
InteractiveCurveViewRange graphRange;
|
||||
graphRange.setXMin(xMin);
|
||||
graphRange.setXMax(xMax);
|
||||
graphRange.setYMin(yMin);
|
||||
graphRange.setYMax(yMax);
|
||||
|
||||
quiz_assert(graphRange.isOrthonormal() == orthonormal);
|
||||
}
|
||||
|
||||
void assert_is_orthonormal(float xMin, float xMax, float yMin, float yMax) {
|
||||
assert_orthonormality(xMin, xMax, yMin, yMax, true);
|
||||
}
|
||||
|
||||
void assert_is_not_orthonormal(float xMin, float xMax, float yMin, float yMax) {
|
||||
assert_orthonormality(xMin, xMax, yMin, yMax, false);
|
||||
}
|
||||
|
||||
QUIZ_CASE(graph_ranges_orthonormal) {
|
||||
assert_is_orthonormal(-10, 10, -5.8125, 4.8125);
|
||||
assert_is_orthonormal(11.37037, 17.2963, 7.529894, 10.67804);
|
||||
assert_is_orthonormal(-1.94574, -1.165371, -2.476379, -2.061809);
|
||||
assert_is_orthonormal(0, 1000000, 0, 531250);
|
||||
assert_is_orthonormal(-3.2e-3f, 3.2e-3f, -1.7e-3f, 1.7e-3f);
|
||||
assert_is_not_orthonormal(-10, 10, -10, 10);
|
||||
assert_is_not_orthonormal(-10, 10, -5.8125, 4.8126);
|
||||
assert_is_not_orthonormal(1234548, 1234568, 1234556, 1234568);
|
||||
|
||||
/* The ratio is 0.55 instead of 0.53125, but depending on the magnitude of
|
||||
* the bounds, it can land inside the margin of error. */
|
||||
assert_is_not_orthonormal(0, 20, 0, 11);
|
||||
assert_is_orthonormal(1e6, 1e6 + 20, 1e6, 1e6 + 11);
|
||||
|
||||
/* The ration is the desired 0.53125, but if the bounds are near equal
|
||||
* numbers of large magnitude, the loss odf precision leaves us without any
|
||||
* significant bits. */
|
||||
assert_is_orthonormal(0, 3.2, 0, 1.7);
|
||||
assert_is_not_orthonormal(1e7, 1e7 + 3.2, 0, 1.7);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,12 +11,13 @@
|
||||
#include "led_test_controller.h"
|
||||
#include "serial_number_controller.h"
|
||||
#include "vblank_test_controller.h"
|
||||
#include "../shared/shared_app.h"
|
||||
|
||||
namespace HardwareTest {
|
||||
|
||||
class App : public ::App {
|
||||
public:
|
||||
class Snapshot : public ::App::Snapshot {
|
||||
class Snapshot : public ::SharedApp::Snapshot {
|
||||
public:
|
||||
App * unpack(Container * container) override;
|
||||
Descriptor * descriptor() override;
|
||||
|
||||
@@ -1,116 +1,25 @@
|
||||
#include "pop_up_controller.h"
|
||||
#include <apps/i18n.h>
|
||||
#include "../apps_container.h"
|
||||
#include <assert.h>
|
||||
#include <escher/app.h>
|
||||
|
||||
namespace HardwareTest {
|
||||
|
||||
PopUpController::PopUpController() :
|
||||
ViewController(nullptr),
|
||||
m_contentView(this)
|
||||
::PopUpController(
|
||||
4,
|
||||
Invocation(
|
||||
[](void * context, void * sender) {
|
||||
AppsContainer * appsContainer = AppsContainer::sharedAppsContainer();
|
||||
bool switched = appsContainer->switchTo(appsContainer->hardwareTestAppSnapshot());
|
||||
assert(switched);
|
||||
(void) switched; // Silence compilation warning about unused variable.
|
||||
return true;
|
||||
}, this)
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
View * PopUpController::view() {
|
||||
return &m_contentView;
|
||||
}
|
||||
|
||||
void PopUpController::didBecomeFirstResponder() {
|
||||
m_contentView.setSelectedButton(0);
|
||||
}
|
||||
|
||||
bool PopUpController::handleEvent(Ion::Events::Event event) {
|
||||
if (event == Ion::Events::Left && m_contentView.selectedButton() == 1) {
|
||||
m_contentView.setSelectedButton(0);
|
||||
return true;
|
||||
}
|
||||
if (event == Ion::Events::Right && m_contentView.selectedButton() == 0) {
|
||||
m_contentView.setSelectedButton(1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
PopUpController::ContentView::ContentView(Responder * parentResponder) :
|
||||
Responder(parentResponder),
|
||||
m_cancelButton(this, I18n::Message::Cancel, Invocation([](void * context, void * sender) {
|
||||
Container::activeApp()->dismissModalViewController();
|
||||
return true;
|
||||
}, this), KDFont::SmallFont),
|
||||
m_okButton(this, I18n::Message::Ok, Invocation([](void * context, void * sender) {
|
||||
AppsContainer * appsContainer = AppsContainer::sharedAppsContainer();
|
||||
bool switched = appsContainer->switchTo(appsContainer->hardwareTestAppSnapshot());
|
||||
assert(switched);
|
||||
(void) switched; // Silence compilation warning about unused variable.
|
||||
return true;
|
||||
}, this), KDFont::SmallFont),
|
||||
m_warningTextView(KDFont::SmallFont, I18n::Message::Warning, 0.5, 0.5, KDColorWhite, KDColorBlack),
|
||||
m_messageTextView1(KDFont::SmallFont, I18n::Message::HardwareTestLaunch1, 0.5, 0.5, KDColorWhite, KDColorBlack),
|
||||
m_messageTextView2(KDFont::SmallFont, I18n::Message::HardwareTestLaunch2, 0.5, 0.5, KDColorWhite, KDColorBlack),
|
||||
m_messageTextView3(KDFont::SmallFont, I18n::Message::HardwareTestLaunch3, 0.5, 0.5, KDColorWhite, KDColorBlack),
|
||||
m_messageTextView4(KDFont::SmallFont, I18n::Message::HardwareTestLaunch4, 0.5, 0.5, KDColorWhite, KDColorBlack)
|
||||
{
|
||||
}
|
||||
|
||||
void PopUpController::ContentView::drawRect(KDContext * ctx, KDRect rect) const {
|
||||
ctx->fillRect(bounds(), KDColorBlack);
|
||||
}
|
||||
|
||||
void PopUpController::ContentView::setSelectedButton(int selectedButton) {
|
||||
m_cancelButton.setHighlighted(selectedButton == 0);
|
||||
m_okButton.setHighlighted(selectedButton == 1);
|
||||
if (selectedButton == 0) {
|
||||
Container::activeApp()->setFirstResponder(&m_cancelButton);
|
||||
} else {
|
||||
Container::activeApp()->setFirstResponder(&m_okButton);
|
||||
}
|
||||
}
|
||||
|
||||
int PopUpController::ContentView::selectedButton() {
|
||||
if (m_cancelButton.isHighlighted()) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int PopUpController::ContentView::numberOfSubviews() const {
|
||||
return 7;
|
||||
}
|
||||
|
||||
View * PopUpController::ContentView::subviewAtIndex(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return &m_warningTextView;
|
||||
case 1:
|
||||
return &m_messageTextView1;
|
||||
case 2:
|
||||
return &m_messageTextView2;
|
||||
case 3:
|
||||
return &m_messageTextView3;
|
||||
case 4:
|
||||
return &m_messageTextView4;
|
||||
case 5:
|
||||
return &m_cancelButton;
|
||||
case 6:
|
||||
return &m_okButton;
|
||||
default:
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PopUpController::ContentView::layoutSubviews(bool force) {
|
||||
KDCoordinate height = bounds().height();
|
||||
KDCoordinate width = bounds().width();
|
||||
KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height();
|
||||
m_warningTextView.setFrame(KDRect(0, k_topMargin, width, textHeight), force);
|
||||
m_messageTextView1.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+textHeight, width, textHeight), force);
|
||||
m_messageTextView2.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+2*textHeight, width, textHeight), force);
|
||||
m_messageTextView3.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+3*textHeight, width, textHeight), force);
|
||||
m_messageTextView4.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+4*textHeight, width, textHeight), force);
|
||||
m_cancelButton.setFrame(KDRect(k_buttonMargin, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight), force);
|
||||
m_okButton.setFrame(KDRect(2*k_buttonMargin+(width-3*k_buttonMargin)/2, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight), force);
|
||||
m_contentView.setMessage(0, I18n::Message::HardwareTestLaunch1);
|
||||
m_contentView.setMessage(1, I18n::Message::HardwareTestLaunch2);
|
||||
m_contentView.setMessage(2, I18n::Message::HardwareTestLaunch3);
|
||||
m_contentView.setMessage(3, I18n::Message::HardwareTestLaunch4);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,43 +1,15 @@
|
||||
#ifndef HARDWARE_TEST_POP_UP_CONTROLLER_H
|
||||
#define HARDWARE_TEST_POP_UP_CONTROLLER_H
|
||||
#ifndef POP_UP_CONTROLLER_H
|
||||
#define POP_UP_CONTROLLER_H
|
||||
|
||||
#include <escher.h>
|
||||
#include <escher/pop_up_controller.h>
|
||||
|
||||
namespace HardwareTest {
|
||||
|
||||
class PopUpController : public ViewController {
|
||||
class PopUpController : public ::PopUpController {
|
||||
public:
|
||||
PopUpController();
|
||||
View * view() override;
|
||||
void didBecomeFirstResponder() override;
|
||||
bool handleEvent(Ion::Events::Event event) override;
|
||||
private:
|
||||
class ContentView : public View, public Responder {
|
||||
public:
|
||||
ContentView(Responder * parentResponder);
|
||||
void drawRect(KDContext * ctx, KDRect rect) const override;
|
||||
void setSelectedButton(int selectedButton);
|
||||
int selectedButton();
|
||||
private:
|
||||
constexpr static KDCoordinate k_buttonMargin = 10;
|
||||
constexpr static KDCoordinate k_buttonHeight = 20;
|
||||
constexpr static KDCoordinate k_topMargin = 8;
|
||||
constexpr static KDCoordinate k_paragraphHeight = 20;
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews(bool force = false) override;
|
||||
Button m_cancelButton;
|
||||
Button m_okButton;
|
||||
MessageTextView m_warningTextView;
|
||||
MessageTextView m_messageTextView1;
|
||||
MessageTextView m_messageTextView2;
|
||||
MessageTextView m_messageTextView3;
|
||||
MessageTextView m_messageTextView4;
|
||||
};
|
||||
ContentView m_contentView;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,9 +1,28 @@
|
||||
app_home_src = $(addprefix apps/home/,\
|
||||
app.cpp \
|
||||
app_cell.cpp \
|
||||
apps_layout.py \
|
||||
controller.cpp \
|
||||
)
|
||||
|
||||
apps_src += $(app_home_src)
|
||||
|
||||
i18n_files += $(call i18n_without_universal_for,home/base)
|
||||
|
||||
# Apps layout file generation
|
||||
|
||||
# The header is refered to as <apps/home/apps_layout.h> so make sure it's
|
||||
# findable this way
|
||||
SFLAGS += -I$(BUILD_DIR)
|
||||
|
||||
apps_layout = apps/home/apps_layout.csv
|
||||
|
||||
$(eval $(call rule_for, \
|
||||
APPS_LAYOUT, \
|
||||
apps/home/apps_layout.cpp, \
|
||||
, \
|
||||
$$(PYTHON) apps/home/apps_layout.py --layouts $(apps_layout) --header $$(subst .cpp,.h,$$@) --implementation $$@ --apps $$(EPSILON_APPS), \
|
||||
global \
|
||||
))
|
||||
|
||||
$(BUILD_DIR)/apps/home/apps_layout.h: $(BUILD_DIR)/apps/home/apps_layout.cpp
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <escher.h>
|
||||
#include "controller.h"
|
||||
#include "../shared/shared_app.h"
|
||||
|
||||
namespace Home {
|
||||
|
||||
@@ -13,7 +14,7 @@ public:
|
||||
I18n::Message name() override;
|
||||
I18n::Message upperName() override;
|
||||
};
|
||||
class Snapshot : public ::App::Snapshot, public SelectableTableViewDataSource {
|
||||
class Snapshot : public ::SharedApp::Snapshot, public SelectableTableViewDataSource {
|
||||
public:
|
||||
App * unpack(Container * container) override;
|
||||
Descriptor * descriptor() override;
|
||||
|
||||
2
apps/home/apps_layout.csv
Normal file
2
apps/home/apps_layout.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
Default,calculation,rpn,graph,code,statistics,probability,solver,atom,sequence,regression,settings
|
||||
HidePython,calculation,rpn,graph,code,statistics,probability,solver,atom,sequence,regression,settings
|
||||
|
90
apps/home/apps_layout.py
Normal file
90
apps/home/apps_layout.py
Normal file
@@ -0,0 +1,90 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This script builds the .h/.cpp files for managing the placement of
|
||||
# applications on the home menu, from the apps_layout.csv file.
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import io
|
||||
|
||||
parser = argparse.ArgumentParser(description="Build tools for the placement of applications on the home menu.")
|
||||
parser.add_argument('--header', help='the .h file to generate')
|
||||
parser.add_argument('--implementation', help='the .cpp file to generate')
|
||||
parser.add_argument('--apps', nargs='+', help='apps that are actually compiled')
|
||||
parser.add_argument('--layouts', help='the apps_layout.csv file')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
def build_permutation(apps, appsOrdered):
|
||||
res = [0] * len(apps)
|
||||
i = 0
|
||||
for app in appsOrdered:
|
||||
if app in apps:
|
||||
res[i] = apps.index(app) + 1
|
||||
i += 1
|
||||
return res
|
||||
|
||||
def parse_config_file(layouts, apps):
|
||||
res = {'styles':[], 'permutations':[]}
|
||||
with io.open(layouts, "r", encoding="utf-8") as f:
|
||||
csvreader = csv.reader(f, delimiter=',')
|
||||
for row in csvreader:
|
||||
res['styles'].append(row[0])
|
||||
res['permutations'].append(build_permutation(apps, row[1:]))
|
||||
return res
|
||||
|
||||
data = parse_config_file(args.layouts, args.apps)
|
||||
|
||||
def print_header(header, data):
|
||||
f = open(header, "w")
|
||||
f.write("#ifndef HOME_APPS_LAYOUT_H\n")
|
||||
f.write("#define HOME_APPS_LAYOUT_H\n")
|
||||
f.write("// This file is auto-generated by apps_layout.py\n\n")
|
||||
f.write("namespace Home {\n\n")
|
||||
|
||||
f.write("int PermutedAppSnapshotIndex(int index);\n\n")
|
||||
|
||||
f.write("}\n\n")
|
||||
f.write("#endif")
|
||||
f.close()
|
||||
|
||||
def print_implementation(implementation, data):
|
||||
f = open(implementation, "w")
|
||||
f.write("// This file is auto-generated by apps_layout.py\n\n")
|
||||
f.write('#include "apps_layout.h"\n')
|
||||
f.write("#include <apps/country_preferences.h>\n")
|
||||
f.write("#include <apps/global_preferences.h>\n")
|
||||
f.write("#include <assert.h>\n\n")
|
||||
f.write("namespace Home {\n\n")
|
||||
|
||||
styles = data['styles']
|
||||
permutations = data['permutations']
|
||||
|
||||
f.write("/* Permutations are built so that Permutation[n] is the index of the snapshot\n")
|
||||
f.write(" * for the nth app in the Home menu. */\n\n")
|
||||
|
||||
for i in range(len(styles)):
|
||||
f.write("static constexpr int " + styles[i] + "AppsPermutation[] = {\n")
|
||||
f.write(" 0,\n")
|
||||
for j in permutations[i]:
|
||||
f.write(" " + str(j) + ",\n")
|
||||
f.write("};\n")
|
||||
f.write('static_assert(' + styles[i] + 'AppsPermutation[0] == 0, "The Home apps must always be at index 0");\n\n')
|
||||
|
||||
f.write("int PermutedAppSnapshotIndex(int index) {\n")
|
||||
f.write(" CountryPreferences::HomeAppsLayout currentLayout = GlobalPreferences::sharedGlobalPreferences()->homeAppsLayout();\n")
|
||||
for i in range(len(styles)):
|
||||
f.write(" if (currentLayout == CountryPreferences::HomeAppsLayout::" + styles[i] + ") {\n")
|
||||
f.write(" return " + styles[i] + "AppsPermutation[index];")
|
||||
f.write(" }\n")
|
||||
f.write(" assert(false);\n")
|
||||
f.write(" return -1;\n")
|
||||
f.write("}\n\n")
|
||||
|
||||
f.write("}\n\n")
|
||||
f.close()
|
||||
|
||||
if args.header:
|
||||
print_header(args.header, data)
|
||||
if args.implementation:
|
||||
print_implementation(args.implementation, data)
|
||||
@@ -1,4 +1,4 @@
|
||||
Apps = "Aplicaciones"
|
||||
AppsCapital = "OMEGA"
|
||||
ForbidenAppInExamMode1 = "Esta aplicación está"
|
||||
ForbidenAppInExamMode2 = "prohibida en el modo examen"
|
||||
ForbidenAppInExamMode1 = "Esta aplicación está prohibida"
|
||||
ForbidenAppInExamMode2 = "en el modo de examen"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "controller.h"
|
||||
#include "app.h"
|
||||
#include <apps/home/apps_layout.h>
|
||||
#include "../apps_container.h"
|
||||
#include "../global_preferences.h"
|
||||
#include "../exam_mode_configuration.h"
|
||||
@@ -67,7 +68,6 @@ Controller::Controller(Responder * parentResponder, SelectableTableViewDataSourc
|
||||
bool Controller::handleEvent(Ion::Events::Event event) {
|
||||
if (event == Ion::Events::OK || event == Ion::Events::EXE) {
|
||||
AppsContainer * container = AppsContainer::sharedAppsContainer();
|
||||
int index = selectionDataSource()->selectedRow()*k_numberOfColumns+selectionDataSource()->selectedColumn()+1;
|
||||
|
||||
#ifdef HOME_DISPLAY_EXTERNALS
|
||||
if (index >= container->numberOfApps()) {
|
||||
@@ -96,9 +96,8 @@ bool Controller::handleEvent(Ion::Events::Event event) {
|
||||
}
|
||||
} else {
|
||||
#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::Snapshot * selectedSnapshot = container->appSnapshotAtIndex(PermutedAppSnapshotIndex(selectionDataSource()->selectedRow() * k_numberOfColumns + selectionDataSource()->selectedColumn() + 1));
|
||||
if (ExamModeConfiguration::appIsForbiddenInExamMode(selectedSnapshot->descriptor()->name(), GlobalPreferences::sharedGlobalPreferences()->examMode())) {
|
||||
} else {
|
||||
bool switched = container->switchTo(selectedSnapshot);
|
||||
assert(switched);
|
||||
@@ -111,14 +110,14 @@ bool Controller::handleEvent(Ion::Events::Event event) {
|
||||
}
|
||||
|
||||
if (event == Ion::Events::Home || event == Ion::Events::Back) {
|
||||
return m_view.selectableTableView()->selectCellAtLocation(0,0);
|
||||
return m_view.selectableTableView()->selectCellAtLocation(0, 0);
|
||||
}
|
||||
|
||||
if (event == Ion::Events::Right && selectionDataSource()->selectedRow() < numberOfRows()) {
|
||||
return m_view.selectableTableView()->selectCellAtLocation(0, selectionDataSource()->selectedRow()+1);
|
||||
if (event == Ion::Events::Right && selectionDataSource()->selectedRow() < numberOfRows() - 1) {
|
||||
return m_view.selectableTableView()->selectCellAtLocation(0, selectionDataSource()->selectedRow() + 1);
|
||||
}
|
||||
if (event == Ion::Events::Left && selectionDataSource()->selectedRow() > 0) {
|
||||
return m_view.selectableTableView()->selectCellAtLocation(numberOfColumns()-1, selectionDataSource()->selectedRow()-1);
|
||||
return m_view.selectableTableView()->selectCellAtLocation(numberOfColumns() - 1, selectionDataSource()->selectedRow() - 1);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -146,7 +145,7 @@ View * Controller::view() {
|
||||
}
|
||||
|
||||
int Controller::numberOfRows() const {
|
||||
return ((numberOfIcons()-1)/k_numberOfColumns)+1;
|
||||
return ((numberOfIcons() - 1) / k_numberOfColumns) + 1;
|
||||
}
|
||||
|
||||
int Controller::numberOfColumns() const {
|
||||
@@ -172,7 +171,7 @@ int Controller::reusableCellCount() const {
|
||||
void Controller::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) {
|
||||
AppCell * appCell = (AppCell *)cell;
|
||||
AppsContainer * container = AppsContainer::sharedAppsContainer();
|
||||
int appIndex = (j*k_numberOfColumns+i)+1;
|
||||
int appIndex = (j * k_numberOfColumns + i) + 1;
|
||||
if (appIndex >= container->numberOfApps()) {
|
||||
#ifdef HOME_DISPLAY_EXTERNALS
|
||||
External::Archive::File app_file;
|
||||
@@ -207,7 +206,7 @@ void Controller::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) {
|
||||
#endif
|
||||
} else {
|
||||
appCell->setVisible(true);
|
||||
::App::Descriptor * descriptor = container->appSnapshotAtIndex(appIndex)->descriptor();
|
||||
::App::Descriptor * descriptor = container->appSnapshotAtIndex(PermutedAppSnapshotIndex(appIndex))->descriptor();
|
||||
appCell->setAppDescriptor(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
150
apps/i18n.py
150
apps/i18n.py
@@ -6,18 +6,22 @@
|
||||
# properly draw upper case letters with accents, we remove them here.
|
||||
# It works with Python 2 and Python 3
|
||||
|
||||
import sys
|
||||
import re
|
||||
import unicodedata
|
||||
import argparse
|
||||
import csv
|
||||
import io
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description="Process some i18n files.")
|
||||
parser.add_argument('--header', help='the .h file to generate')
|
||||
parser.add_argument('--implementation', help='the .cpp file to generate')
|
||||
parser.add_argument('--locales', nargs='+', help='locale to actually generate')
|
||||
parser.add_argument('--countries', nargs='+', help='countries to actually generate')
|
||||
parser.add_argument('--codepoints', help='the code_points.h file')
|
||||
parser.add_argument('--countrypreferences', help='the country_preferences.csv file')
|
||||
parser.add_argument('--languagepreferences', help='the language_preferences.csv file')
|
||||
parser.add_argument('--files', nargs='+', help='an i18n file')
|
||||
parser.add_argument('--generateISO6391locales', type=int, nargs='+', help='whether to generate the ISO6391 codes for the languages (for instance "en" for english)')
|
||||
|
||||
@@ -67,6 +71,18 @@ def split_line(line):
|
||||
def locale_from_filename(filename):
|
||||
return re.match(r".*\.([a-z]+)\.i18n", filename).group(1)
|
||||
|
||||
def check_redundancy(messages, data, locales):
|
||||
redundant_names = set()
|
||||
for name in messages:
|
||||
redundancy = True
|
||||
for i in range(1, len(locales)):
|
||||
redundancy = redundancy and data[locales[i]][name] == data[locales[i-1]][name]
|
||||
if redundancy:
|
||||
redundant_names.add(name)
|
||||
if (len(redundant_names) > 0):
|
||||
sys.stderr.write("Some localized messages are redundant and can be made universal :\n\t" + "\n\t".join(sorted(redundant_names)) + "\n")
|
||||
sys.exit(-1)
|
||||
|
||||
def parse_files(files):
|
||||
data = {}
|
||||
messages = set()
|
||||
@@ -91,6 +107,7 @@ def parse_files(files):
|
||||
else:
|
||||
messages.add(name)
|
||||
data[locale][name] = definition
|
||||
#check_redundancy(messages, data, args.locales) # FIXME
|
||||
return {"messages": sorted(messages), "universal_messages": sorted(universal_messages), "data": data}
|
||||
|
||||
def parse_codepoints(file):
|
||||
@@ -114,46 +131,110 @@ def parse_codepoints(file):
|
||||
|
||||
codepoints = parse_codepoints(args.codepoints)
|
||||
|
||||
def print_header(data, path, locales):
|
||||
def parse_csv_with_header(file):
|
||||
res = []
|
||||
with io.open(file, 'r', encoding='utf-8') as csvfile:
|
||||
csvreader = csv.reader(csvfile, delimiter=',')
|
||||
for row in csvreader:
|
||||
res.append(row)
|
||||
return (res[0], res[1:])
|
||||
|
||||
def parse_country_preferences(file):
|
||||
countryPreferences = {}
|
||||
header, records = parse_csv_with_header(file)
|
||||
for record in records:
|
||||
countryPreferences[record[0]] = [header[i] + "::" + record[i] for i in range(1, len(record))]
|
||||
return countryPreferences
|
||||
|
||||
countryPreferences = parse_country_preferences(args.countrypreferences)
|
||||
|
||||
def parse_language_preferences(file):
|
||||
languagePreferences = {}
|
||||
header, records = parse_csv_with_header(file)
|
||||
for record in records:
|
||||
languagePreferences[record[0]] = (header[1], record[1])
|
||||
return languagePreferences
|
||||
|
||||
languagePreferences = parse_language_preferences(args.languagepreferences)
|
||||
|
||||
def print_block_from_list(target, header, data, beautify=lambda arg: arg, prefix=" ", footer="};\n\n"):
|
||||
target.write(header)
|
||||
for i in range(len(data)):
|
||||
target.write(prefix + beautify(data[i]) + ",\n")
|
||||
target.write(footer)
|
||||
|
||||
def print_header(data, path, locales, countries):
|
||||
f = open(path, "w")
|
||||
f.write("#ifndef APPS_I18N_H\n")
|
||||
f.write("#define APPS_I18N_H\n\n")
|
||||
f.write("// This file is auto-generated by i18n.py\n\n")
|
||||
f.write("#include <escher.h>\n\n")
|
||||
f.write("#include <escher.h>\n")
|
||||
f.write("#include <apps/country_preferences.h>\n\n")
|
||||
f.write("namespace I18n {\n\n")
|
||||
f.write("constexpr static int NumberOfLanguages = %d;\n\n" % len(locales))
|
||||
f.write("constexpr static int NumberOfCountries = %d;\n\n" % len(countries))
|
||||
|
||||
# Messages enumeration
|
||||
f.write("enum class Message : uint16_t {\n")
|
||||
f.write(" Default = 0,\n")
|
||||
for message in data["universal_messages"]:
|
||||
f.write(" " + message + ",\n")
|
||||
f.write("\n")
|
||||
f.write(" LocalizedMessageMarker,\n\n")
|
||||
for message in data["messages"]:
|
||||
f.write(" " + message + ",\n")
|
||||
f.write("};\n\n")
|
||||
print_block_from_list(f,
|
||||
"enum class Message : uint16_t {\n Default = 0,\n",
|
||||
data["universal_messages"],
|
||||
footer="\n")
|
||||
print_block_from_list(f,
|
||||
" LocalizedMessageMarker,\n\n",
|
||||
data["messages"])
|
||||
|
||||
# Languages enumeration
|
||||
f.write("enum class Language : uint16_t {\n")
|
||||
index = 0
|
||||
for locale in locales:
|
||||
f.write(" " + locale.upper() + (" = 0" if (index < 1) else "") +",\n")
|
||||
index = index + 1
|
||||
f.write("};\n\n")
|
||||
print_block_from_list(f,
|
||||
"enum class Language : uint8_t {\n",
|
||||
locales,
|
||||
lambda arg: arg.upper())
|
||||
|
||||
# Language names
|
||||
f.write("constexpr const Message LanguageNames[NumberOfLanguages] = {\n");
|
||||
for locale in locales:
|
||||
f.write(" Message::Language" + locale.upper() + ",\n")
|
||||
f.write("};\n\n")
|
||||
print_block_from_list(f,
|
||||
"constexpr const Message LanguageNames[NumberOfLanguages] = {\n",
|
||||
locales,
|
||||
lambda arg: arg.upper(),
|
||||
" Message::Language")
|
||||
|
||||
if generate_ISO6391():
|
||||
# Language ISO639-1 codes
|
||||
f.write("constexpr const Message LanguageISO6391Names[NumberOfLanguages] = {\n");
|
||||
for locale in locales:
|
||||
f.write(" Message::LanguageISO6391" + locale.upper() + ",\n")
|
||||
f.write("};\n\n")
|
||||
print_block_from_list(f,
|
||||
"constexpr const Message LanguageISO6391Names[NumberOfLanguages] = {\n",
|
||||
locales,
|
||||
lambda arg: arg.upper(),
|
||||
" Message::LanguageISO6391")
|
||||
|
||||
# Countries enumeration
|
||||
print_block_from_list(f,
|
||||
"enum class Country : uint8_t {\n",
|
||||
countries,
|
||||
lambda arg: arg.upper())
|
||||
defaultCountry = countries[-1]
|
||||
|
||||
# Country names
|
||||
print_block_from_list(f,
|
||||
"constexpr const Message CountryNames[NumberOfCountries] = {\n",
|
||||
countries,
|
||||
lambda arg: arg.upper(),
|
||||
" Message::Country")
|
||||
|
||||
# Language preferences
|
||||
f.write("constexpr static Country DefaultCountryForLanguage[NumberOfLanguages] = {\n")
|
||||
for language in locales:
|
||||
key = language if (language in languagePreferences) else '??'
|
||||
header, country = languagePreferences[key]
|
||||
line = " " + header + "::" + (country if country in countries else defaultCountry)
|
||||
f.write(line + ",\n")
|
||||
f.write("};\n\n")
|
||||
|
||||
# Country preferences
|
||||
f.write("constexpr static CountryPreferences CountryPreferencesArray[] = {\n")
|
||||
for country in countries:
|
||||
key = country if (country in countryPreferences) else defaultCountry
|
||||
line = " CountryPreferences("
|
||||
for param in countryPreferences[key]:
|
||||
line += param + ", "
|
||||
f.write(line[:-2] + "),\n")
|
||||
f.write("};\n\n")
|
||||
|
||||
f.write("}\n\n")
|
||||
f.write("#endif\n")
|
||||
@@ -178,11 +259,10 @@ def print_implementation(data, path, locales):
|
||||
f = open(path, "a") # Re-open the file as text
|
||||
f.write(";\n")
|
||||
f.write("\n")
|
||||
f.write("constexpr static const char * universalMessages[%d] = {\n" % (len(data["universal_messages"])+1))
|
||||
f.write(" universalDefault,\n")
|
||||
for message in data["universal_messages"]:
|
||||
f.write(" universal" + message + ",\n")
|
||||
f.write("};\n\n")
|
||||
print_block_from_list(f,
|
||||
"constexpr static const char * universalMessages[%d] = {\n universalDefault,\n" % (len(data["universal_messages"])+1),
|
||||
data["universal_messages"],
|
||||
prefix=" universal")
|
||||
|
||||
# Write the localized messages
|
||||
for message in data["messages"]:
|
||||
@@ -226,6 +306,6 @@ def print_implementation(data, path, locales):
|
||||
|
||||
data = parse_files(args.files)
|
||||
if args.header:
|
||||
print_header(data, args.header, args.locales)
|
||||
print_header(data, args.header, args.locales, args.countries)
|
||||
if args.implementation:
|
||||
print_implementation(data, args.implementation, args.locales)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user