[Update] Epsilon 15.3.1

This commit is contained in:
Joachim LF
2021-01-21 17:27:39 +01:00
700 changed files with 15118 additions and 7297 deletions

View File

@@ -1,5 +1,17 @@
name: Continuous integration 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: jobs:
# nintendo_3ds: # nintendo_3ds:
@@ -11,7 +23,7 @@ jobs:
# - run: echo ::set-env name=DEVKITPRO::/opt/devkitpro # - run: echo ::set-env name=DEVKITPRO::/opt/devkitpro
# - run: echo ::set-env name=DEVKITARM::/opt/devkitpro/devkitARM # - run: echo ::set-env name=DEVKITARM::/opt/devkitpro/devkitARM
# - run: echo ::set-env name=PATH::$DEVKITPRO/tools/bin:$DEVKITARM/bin:$PATH # - run: echo ::set-env name=PATH::$DEVKITPRO/tools/bin:$DEVKITARM/bin:$PATH
# - uses: actions/checkout@v1 # - uses: actions/checkout@v1
# with: # with:
# submodules: true # submodules: true
@@ -94,6 +106,23 @@ jobs:
with: with:
name: epsilon-binpack-n0110.tgz name: epsilon-binpack-n0110.tgz
path: output/release/device/n0110/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: web:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -121,3 +150,34 @@ jobs:
name: epsilon-linux.bin name: epsilon-linux.bin
path: output/release/simulator/linux/epsilon.bin path: output/release/simulator/linux/epsilon.bin
- run: make -j2 PLATFORM=simulator test.headless.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

View File

@@ -42,7 +42,10 @@ apps_src += $(addprefix apps/,\
title_bar_view.cpp \ 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;) snapshots_declaration = $(foreach i,$(apps),$(i)::Snapshot m_snapshot$(subst :,,$(i))Snapshot;)
apps_declaration = $(foreach i,$(apps),$(i) m_$(subst :,,$(i));) 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 # 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 # The header is refered to as <apps/i18n.h> so make sure it's findable this way
SFLAGS += -I$(BUILD_DIR) SFLAGS += -I$(BUILD_DIR)
@@ -65,14 +71,14 @@ i18n_files += $(addprefix apps/language_,$(addsuffix _iso6391.universal.i18n, $(
endif endif
i18n_files += $(call i18n_with_universal_for,shared) 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) i18n_files += $(call i18n_without_universal_for,variables)
$(eval $(call rule_for, \ $(eval $(call rule_for, \
I18N, \ I18N, \
apps/i18n.cpp, \ apps/i18n.cpp, \
$(i18n_files), \ $(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 \ 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)/apps/i18n.h
$(call object_for,$(apps_src) $(tests_src)): $(BUILD_DIR)/python/port/genhdr/qstrdefs.generated.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/,\ apps_tests_src += $(addprefix apps/,\
alternate_empty_nested_menu_controller.cpp \ alternate_empty_nested_menu_controller.cpp \

View File

@@ -59,8 +59,23 @@ AppsContainer::AppsContainer() :
} }
bool AppsContainer::poincareCircuitBreaker() { bool AppsContainer::poincareCircuitBreaker() {
constexpr uint64_t minimalPressDuration = 20;
static uint64_t beginningOfInterruption = 0;
Ion::Keyboard::State state = Ion::Keyboard::scan(); 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() { App::Snapshot * AppsContainer::hardwareTestAppSnapshot() {
@@ -181,7 +196,7 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) {
} }
if (changedZoom) { if (changedZoom) {
KDIonContext::sharedContext()->updatePostProcessingEffects(); KDIonContext::sharedContext()->updatePostProcessingEffects();
redrawWindow(true); redrawWindow();
return true; return true;
} }
} }
@@ -341,7 +356,7 @@ void AppsContainer::shutdownDueToLowBattery() {
* case. */ * case. */
return; return;
} }
while (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) { while (Ion::Battery::level() == Ion::Battery::Charge::EMPTY && !Ion::USB::isPlugged()) {
Ion::Backlight::setBrightness(0); Ion::Backlight::setBrightness(0);
if (!GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { if (!GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) {
/* Unless the LED is lit up for the exam mode, switch off the LED. IF the /* 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(); return m_window.updateAlphaLock();
} }
OnBoarding::PopUpController * AppsContainer::promptController() { OnBoarding::PromptController * AppsContainer::promptController() {
if (k_promptNumberOfMessages == 0) { if (k_promptNumberOfMessages == 0) {
return nullptr; return nullptr;
} }
return &m_promptController; return &m_promptController;
} }
void AppsContainer::redrawWindow(bool force) { void AppsContainer::redrawWindow() {
m_window.redraw(force); m_window.redraw();
} }
void AppsContainer::activateExamMode(GlobalPreferences::ExamMode examMode) { void AppsContainer::activateExamMode(GlobalPreferences::ExamMode examMode) {

View File

@@ -16,8 +16,8 @@
#include "global_preferences.h" #include "global_preferences.h"
#include "backlight_dimming_timer.h" #include "backlight_dimming_timer.h"
#include "shared/global_context.h" #include "shared/global_context.h"
#include "on_boarding/pop_up_controller.h"
#include "clock_timer.h" #include "clock_timer.h"
#include "on_boarding/prompt_controller.h"
#include <ion/events.h> #include <ion/events.h>
@@ -47,8 +47,8 @@ public:
void displayExamModePopUp(GlobalPreferences::ExamMode mode); void displayExamModePopUp(GlobalPreferences::ExamMode mode);
void shutdownDueToLowBattery(); void shutdownDueToLowBattery();
void setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus newStatus); void setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus newStatus);
OnBoarding::PopUpController * promptController(); OnBoarding::PromptController * promptController();
void redrawWindow(bool force = false); void redrawWindow();
void activateExamMode(GlobalPreferences::ExamMode examMode); void activateExamMode(GlobalPreferences::ExamMode examMode);
// Exam pop-up controller delegate // Exam pop-up controller delegate
void examDeactivatingPopUpIsDismissed() override; void examDeactivatingPopUpIsDismissed() override;
@@ -74,7 +74,7 @@ private:
MathToolbox m_mathToolbox; MathToolbox m_mathToolbox;
MathVariableBoxController m_variableBoxController; MathVariableBoxController m_variableBoxController;
ExamPopUpController m_examPopUpController; ExamPopUpController m_examPopUpController;
OnBoarding::PopUpController m_promptController; OnBoarding::PromptController m_promptController;
BatteryTimer m_batteryTimer; BatteryTimer m_batteryTimer;
SuspendTimer m_suspendTimer; SuspendTimer m_suspendTimer;
BacklightDimmingTimer m_backlightDimmingTimer; BacklightDimmingTimer m_backlightDimmingTimer;

View File

@@ -9,7 +9,7 @@ BatteryTimer::BatteryTimer() :
bool BatteryTimer::fire() { bool BatteryTimer::fire() {
AppsContainer * container = AppsContainer::sharedAppsContainer(); AppsContainer * container = AppsContainer::sharedAppsContainer();
bool needRedrawing = container->updateBatteryState(); 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(); container->shutdownDueToLowBattery();
} }
return needRedrawing; return needRedrawing;

View File

@@ -17,6 +17,7 @@ app_calculation_src = $(addprefix apps/calculation/,\
additional_outputs/integer_list_controller.cpp \ additional_outputs/integer_list_controller.cpp \
additional_outputs/scrollable_three_expressions_cell.cpp \ additional_outputs/scrollable_three_expressions_cell.cpp \
additional_outputs/list_controller.cpp \ additional_outputs/list_controller.cpp \
additional_outputs/matrix_list_controller.cpp \
additional_outputs/rational_list_controller.cpp \ additional_outputs/rational_list_controller.cpp \
additional_outputs/trigonometry_graph_cell.cpp \ additional_outputs/trigonometry_graph_cell.cpp \
additional_outputs/trigonometry_list_controller.cpp \ additional_outputs/trigonometry_list_controller.cpp \

View File

@@ -11,7 +11,7 @@ ExpressionsListController::ExpressionsListController(EditExpressionController *
ListController(editExpressionController), ListController(editExpressionController),
m_cells{} 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()); m_cells[i].setParentResponder(m_listController.selectableTableView());
} }
} }
@@ -21,15 +21,20 @@ void ExpressionsListController::didEnterResponderChain(Responder * previousFirst
} }
int ExpressionsListController::reusableCellCount(int type) { int ExpressionsListController::reusableCellCount(int type) {
return k_maxNumberOfCells; return k_maxNumberOfRows;
} }
void ExpressionsListController::viewDidDisappear() { void ExpressionsListController::viewDidDisappear() {
ListController::viewDidDisappear(); ListController::viewDidDisappear();
// Reset cell memoization to avoid taking extra space in the pool // Reset layout and cell memoization to avoid taking extra space in the pool
for (int i = 0; i < k_maxNumberOfCells; i++) { for (int i = 0; i < k_maxNumberOfRows; i++) {
m_cells[i].setLayout(Layout()); 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) { HighlightCell * ExpressionsListController::reusableCell(int index, int type) {
@@ -43,24 +48,34 @@ KDCoordinate ExpressionsListController::rowHeight(int j) {
} }
void ExpressionsListController::willDisplayCellForIndex(HighlightCell * cell, int index) { 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); ExpressionTableCellWithPointer * myCell = static_cast<ExpressionTableCellWithPointer *>(cell);
myCell->setLayout(layoutAtIndex(index)); myCell->setLayout(layoutAtIndex(index));
myCell->setAccessoryMessage(messageAtIndex(index)); myCell->setAccessoryMessage(messageAtIndex(index));
myCell->reloadScroll(); 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) { void ExpressionsListController::setExpression(Poincare::Expression e) {
// Reinitialize memoization // Reinitialize memoization
for (int i = 0; i < k_maxNumberOfCells; i++) { for (int i = 0; i < k_maxNumberOfRows; i++) {
m_layouts[i] = Layout(); m_layouts[i] = Layout();
} }
m_expression = e; m_expression = e;
} }
Poincare::Layout ExpressionsListController::layoutAtIndex(int index) { Poincare::Layout ExpressionsListController::layoutAtIndex(int index) {
if (m_layouts[index].isUninitialized()) {
computeLayoutAtIndex(index);
}
assert(!m_layouts[index].isUninitialized()); assert(!m_layouts[index].isUninitialized());
return m_layouts[index]; return m_layouts[index];
} }

View File

@@ -22,22 +22,22 @@ public:
KDCoordinate rowHeight(int j) override; KDCoordinate rowHeight(int j) override;
int typeAtLocation(int i, int j) override { return 0; } int typeAtLocation(int i, int j) override { return 0; }
void willDisplayCellForIndex(HighlightCell * cell, int index) override; void willDisplayCellForIndex(HighlightCell * cell, int index) override;
int numberOfRows() const override;
// IllustratedListController // IllustratedListController
void setExpression(Poincare::Expression e) override; void setExpression(Poincare::Expression e) override;
protected: protected:
constexpr static int k_maxNumberOfCells = 4; constexpr static int k_maxNumberOfRows = 5;
int textAtIndex(char * buffer, size_t bufferSize, int index) override; int textAtIndex(char * buffer, size_t bufferSize, int index) override;
Poincare::Expression m_expression; Poincare::Expression m_expression;
// Memoization of layouts // Memoization of layouts
mutable Poincare::Layout m_layouts[k_maxNumberOfCells]; mutable Poincare::Layout m_layouts[k_maxNumberOfRows];
private: private:
Poincare::Layout layoutAtIndex(int index); Poincare::Layout layoutAtIndex(int index);
virtual void computeLayoutAtIndex(int index) = 0;
virtual I18n::Message messageAtIndex(int index) = 0; virtual I18n::Message messageAtIndex(int index) = 0;
// Cells // Cells
ExpressionTableCellWithPointer m_cells[k_maxNumberOfCells]; ExpressionTableCellWithPointer m_cells[k_maxNumberOfRows];
}; };
} }

View File

@@ -11,6 +11,7 @@ namespace Calculation {
IllustratedListController::IllustratedListController(EditExpressionController * editExpressionController) : IllustratedListController::IllustratedListController(EditExpressionController * editExpressionController) :
ListController(editExpressionController, this), ListController(editExpressionController, this),
m_calculationStore(m_calculationStoreBuffer, k_calculationStoreBufferSize),
m_additionalCalculationCells{} m_additionalCalculationCells{}
{ {
for (int i = 0; i < k_maxNumberOfAdditionalCalculations; i++) { for (int i = 0; i < k_maxNumberOfAdditionalCalculations; i++) {

View File

@@ -39,7 +39,10 @@ protected:
private: private:
int textAtIndex(char * buffer, size_t bufferSize, int index) override; int textAtIndex(char * buffer, size_t bufferSize, int index) override;
virtual CodePoint expressionSymbol() const = 0; 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_maxNumberOfAdditionalCalculations = 4;
constexpr static int k_calculationStoreBufferSize = k_maxNumberOfAdditionalCalculations * (sizeof(Calculation) + Calculation::k_numberOfExpressions * Constant::MaxSerializedExpressionSize + sizeof(Calculation *));
char m_calculationStoreBuffer[k_calculationStoreBufferSize];
// Cells // Cells
virtual HighlightCell * illustrationCell() = 0; virtual HighlightCell * illustrationCell() = 0;
ScrollableThreeExpressionsCell m_additionalCalculationCells[k_maxNumberOfAdditionalCalculations]; ScrollableThreeExpressionsCell m_additionalCalculationCells[k_maxNumberOfAdditionalCalculations];

View File

@@ -11,10 +11,6 @@ using namespace Shared;
namespace Calculation { namespace Calculation {
int IntegerListController::numberOfRows() const {
return 3 + factorExpressionIsComputable();
}
Integer::Base baseAtIndex(int index) { Integer::Base baseAtIndex(int index) {
switch (index) { switch (index) {
case 0: case 0:
@@ -27,12 +23,20 @@ Integer::Base baseAtIndex(int index) {
} }
} }
void IntegerListController::computeLayoutAtIndex(int index) { void IntegerListController::setExpression(Poincare::Expression e) {
assert(m_expression.type() == ExpressionNode::Type::BasedInteger); ExpressionsListController::setExpression(e);
// For index = k_indexOfFactorExpression, the layout is assumed to be alreday memoized because it is needed to compute the numberOfRows static_assert(k_maxNumberOfRows >= k_indexOfFactorExpression + 1, "k_maxNumberOfRows must be greater than k_indexOfFactorExpression");
assert(index < k_indexOfFactorExpression); assert(!m_expression.isUninitialized() && m_expression.type() == ExpressionNode::Type::BasedInteger);
Integer i = static_cast<BasedInteger &>(m_expression).integer(); Integer integer = static_cast<BasedInteger &>(m_expression).integer();
m_layouts[index] = i.createLayout(baseAtIndex(index)); 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) { 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;
}
} }

View File

@@ -10,13 +10,11 @@ public:
IntegerListController(EditExpressionController * editExpressionController) : IntegerListController(EditExpressionController * editExpressionController) :
ExpressionsListController(editExpressionController) {} ExpressionsListController(editExpressionController) {}
//ListViewDataSource void setExpression(Poincare::Expression e) override;
int numberOfRows() const override;
private: private:
static constexpr int k_indexOfFactorExpression = 3; static constexpr int k_indexOfFactorExpression = 3;
void computeLayoutAtIndex(int index) override;
I18n::Message messageAtIndex(int index) override; I18n::Message messageAtIndex(int index) override;
bool factorExpressionIsComputable() const;
}; };
} }

View 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]];
}
}

View 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

View File

@@ -9,34 +9,31 @@ using namespace Shared;
namespace Calculation { namespace Calculation {
int RationalListController::numberOfRows() const {
return 2;
}
Integer extractInteger(const Expression e) { Integer extractInteger(const Expression e) {
assert(e.type() == ExpressionNode::Type::BasedInteger); assert(e.type() == ExpressionNode::Type::BasedInteger);
return static_cast<const BasedInteger &>(e).integer(); 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; bool negative = false;
Expression div = m_expression; Expression div = m_expression;
if (m_expression.type() == ExpressionNode::Type::Opposite) { if (m_expression.type() == ExpressionNode::Type::Opposite) {
negative = true; negative = true;
div = m_expression.childAtIndex(0); div = m_expression.childAtIndex(0);
} }
assert(div.type() == ExpressionNode::Type::Division); assert(div.type() == ExpressionNode::Type::Division);
Integer numerator = extractInteger(div.childAtIndex(0)); Integer numerator = extractInteger(div.childAtIndex(0));
numerator.setNegative(negative); numerator.setNegative(negative);
Integer denominator = extractInteger(div.childAtIndex(1)); Integer denominator = extractInteger(div.childAtIndex(1));
Expression e;
if (index == 0) { int index = 0;
e = Integer::CreateMixedFraction(numerator, denominator); m_layouts[index++] = PoincareHelpers::CreateLayout(Integer::CreateMixedFraction(numerator, denominator));
} else { m_layouts[index++] = PoincareHelpers::CreateLayout(Integer::CreateEuclideanDivision(numerator, denominator));
assert(index == 1);
e = Integer::CreateEuclideanDivision(numerator, denominator);
}
m_layouts[index] = PoincareHelpers::CreateLayout(e);
} }
I18n::Message RationalListController::messageAtIndex(int index) { I18n::Message RationalListController::messageAtIndex(int index) {

View File

@@ -10,10 +10,9 @@ public:
RationalListController(EditExpressionController * editExpressionController) : RationalListController(EditExpressionController * editExpressionController) :
ExpressionsListController(editExpressionController) {} ExpressionsListController(editExpressionController) {}
//ListViewDataSource void setExpression(Poincare::Expression e) override;
int numberOfRows() const override;
private: private:
void computeLayoutAtIndex(int index) override;
I18n::Message messageAtIndex(int index) override; I18n::Message messageAtIndex(int index) override;
int textAtIndex(char * buffer, size_t bufferSize, int index) override; int textAtIndex(char * buffer, size_t bufferSize, int index) override;
}; };

View File

@@ -15,112 +15,62 @@ namespace Calculation {
void UnitListController::setExpression(Poincare::Expression e) { void UnitListController::setExpression(Poincare::Expression e) {
ExpressionsListController::setExpression(e); ExpressionsListController::setExpression(e);
assert(!m_expression.isUninitialized()); assert(!m_expression.isUninitialized());
// Reinitialize m_memoizedExpressions static_assert(k_maxNumberOfRows >= 3, "k_maxNumberOfRows must be greater than 3");
for (size_t i = 0; i < k_maxNumberOfCells; i++) {
m_memoizedExpressions[i] = Expression(); 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, in both
// 1. First rows: miscellaneous classic units for some dimensions * metric and imperial units. */
Expression copy = m_expression.clone(); Expression copy = m_expression.clone();
Expression units; Expression units;
// Reduce to be able to recognize units // Reduce to be able to recognize units
PoincareHelpers::Reduce(&copy, App::app()->localContext(), ExpressionNode::ReductionTarget::User); PoincareHelpers::ReduceAndRemoveUnit(&copy, App::app()->localContext(), ExpressionNode::ReductionTarget::User, &units);
copy = copy.removeUnit(&units); double value = Shared::PoincareHelpers::ApproximateToScalar<double>(copy, App::app()->localContext());
bool requireSimplification = false; ExpressionNode::ReductionContext reductionContext(
bool canChangeUnitPrefix = false; 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)) { // 2. SI units only
// 1.a. Turn speed into km/h assert(numberOfExpressions < k_maxNumberOfRows - 1);
m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder( expressions[numberOfExpressions] = m_expression.clone();
m_expression.clone(), Shared::PoincareHelpers::Simplify(&expressions[numberOfExpressions], App::app()->localContext(), ExpressionNode::ReductionTarget::User, Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::InternationalSystem);
Multiplication::Builder( numberOfExpressions++;
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. IS units only /* 3. Get rid of duplicates
assert(numberOfMemoizedExpressions < k_maxNumberOfCells - 1); * We find duplicates by comparing the serializations, to eliminate
m_memoizedExpressions[numberOfMemoizedExpressions] = m_expression.clone(); * expressions that only differ by the types of their number nodes. */
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
Expression reduceExpression = m_expression.clone(); 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); Shared::PoincareHelpers::Simplify(&reduceExpression, App::app()->localContext(), ExpressionNode::ReductionTarget::User, Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::None);
currentExpressionIndex = 1; int currentExpressionIndex = 0;
while (currentExpressionIndex < numberOfMemoizedExpressions) { while (currentExpressionIndex < numberOfExpressions) {
bool duplicateFound = false; bool duplicateFound = false;
for (size_t i = 0; i < currentExpressionIndex + 1; i++) { constexpr int buffersSize = Constant::MaxSerializedExpressionSize;
// Compare the currentExpression to all previous memoized expressions and to m_expression char buffer1[buffersSize];
Expression comparedExpression = i == currentExpressionIndex ? reduceExpression : m_memoizedExpressions[i]; 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()); assert(!comparedExpression.isUninitialized());
if (comparedExpression.isIdenticalTo(m_memoizedExpressions[currentExpressionIndex])) { char buffer2[buffersSize];
numberOfMemoizedExpressions--; int size2 = PoincareHelpers::Serialize(comparedExpression, buffer2, buffersSize);
if (size1 == size2 && strcmp(buffer1, buffer2) == 0) {
numberOfExpressions--;
// Shift next expressions // Shift next expressions
for (size_t j = currentExpressionIndex; j < numberOfMemoizedExpressions; j++) { for (int j = currentExpressionIndex; j < numberOfExpressions; j++) {
m_memoizedExpressions[j] = m_memoizedExpressions[j+1]; expressions[j] = expressions[j+1];
} }
// Remove last expression // Remove last expression
m_memoizedExpressions[numberOfMemoizedExpressions] = Expression(); expressions[numberOfExpressions] = Expression();
// The current expression has been discarded, no need to increment the current index // The current expression has been discarded, no need to increment the current index
duplicateFound = true; duplicateFound = true;
break; break;
@@ -131,21 +81,12 @@ void UnitListController::setExpression(Poincare::Expression e) {
currentExpressionIndex++; currentExpressionIndex++;
} }
} }
} // Memoize layouts
for (size_t i = 0; i < k_maxNumberOfRows; i++) {
int UnitListController::numberOfRows() const { if (!expressions[i].isUninitialized()) {
int nbOfRows = 0; m_layouts[i] = Shared::PoincareHelpers::CreateLayout(expressions[i]);
for (size_t i = 0; i < k_maxNumberOfCells; i++) {
if (!m_memoizedExpressions[i].isUninitialized()) {
nbOfRows++;
} }
} }
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) { I18n::Message UnitListController::messageAtIndex(int index) {

View File

@@ -12,13 +12,8 @@ public:
void setExpression(Poincare::Expression e) override; void setExpression(Poincare::Expression e) override;
//ListViewDataSource
int numberOfRows() const override;
private: private:
void computeLayoutAtIndex(int index) override;
I18n::Message messageAtIndex(int index) override; I18n::Message messageAtIndex(int index) override;
// Memoization of expressions
mutable Poincare::Expression m_memoizedExpressions[k_maxNumberOfCells];
}; };
} }

View File

@@ -31,6 +31,8 @@ App * App::Snapshot::unpack(Container * container) {
void App::Snapshot::reset() { void App::Snapshot::reset() {
m_calculationStore.deleteAll(); m_calculationStore.deleteAll();
m_cacheBuffer[0] = 0;
m_cacheBufferInformation = 0;
} }
App::Descriptor * App::Snapshot::descriptor() { App::Descriptor * App::Snapshot::descriptor() {
@@ -38,14 +40,14 @@ App::Descriptor * App::Snapshot::descriptor() {
return &descriptor; return &descriptor;
} }
void App::Snapshot::tidy() { App::Snapshot::Snapshot() : m_calculationStore(m_calculationBuffer, k_calculationBufferSize)
m_calculationStore.tidy(); {
} }
App::App(Snapshot * snapshot) : App::App(Snapshot * snapshot) :
ExpressionFieldDelegateApp(snapshot, &m_editExpressionController), ExpressionFieldDelegateApp(snapshot, &m_editExpressionController),
m_historyController(&m_editExpressionController, snapshot->calculationStore()), 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 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();
} }
} }

View File

@@ -6,6 +6,7 @@
#include "history_controller.h" #include "history_controller.h"
#include "../shared/text_field_delegate_app.h" #include "../shared/text_field_delegate_app.h"
#include <escher.h> #include <escher.h>
#include "../shared/shared_app.h"
namespace Calculation { namespace Calculation {
@@ -18,15 +19,22 @@ public:
App::Descriptor::ExaminationLevel examinationLevel() override; App::Descriptor::ExaminationLevel examinationLevel() override;
const Image * icon() override; const Image * icon() override;
}; };
class Snapshot : public ::App::Snapshot { class Snapshot : public ::SharedApp::Snapshot {
public: public:
Snapshot();
App * unpack(Container * container) override; App * unpack(Container * container) override;
void reset() override; void reset() override;
Descriptor * descriptor() override; Descriptor * descriptor() override;
CalculationStore * calculationStore() { return &m_calculationStore; } CalculationStore * calculationStore() { return &m_calculationStore; }
char * cacheBuffer() { return m_cacheBuffer; }
size_t * cacheBufferInformationAddress() { return &m_cacheBufferInformation; }
private: private:
void tidy() override;
CalculationStore m_calculationStore; 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() { static App * app() {
return static_cast<App *>(Container::activeApp()); return static_cast<App *>(Container::activeApp());
@@ -36,9 +44,12 @@ public:
bool layoutFieldDidReceiveEvent(::LayoutField * layoutField, Ion::Events::Event event) override; bool layoutFieldDidReceiveEvent(::LayoutField * layoutField, Ion::Events::Event event) override;
// TextFieldDelegateApp // TextFieldDelegateApp
bool isAcceptableExpression(const Poincare::Expression expression) override; bool isAcceptableExpression(const Poincare::Expression expression) override;
private: private:
App(Snapshot * snapshot); App(Snapshot * snapshot);
HistoryController m_historyController; HistoryController m_historyController;
void didBecomeActive(Window * window) override;
void willBecomeInactive() override;
EditExpressionController m_editExpressionController; EditExpressionController m_editExpressionController;
}; };

View File

@@ -7,3 +7,8 @@ BinaryBase = "Binär"
PrimeFactors = "Primfaktoren" PrimeFactors = "Primfaktoren"
MixedFraction = "Gemischte Zahl" MixedFraction = "Gemischte Zahl"
EuclideanDivision = "Division mit Rest" EuclideanDivision = "Division mit Rest"
AdditionalDeterminant = "Determinante"
AdditionalInverse = "Inverse"
AdditionalRowEchelonForm = "Stufenform"
AdditionalReducedRowEchelonForm = "Reduzierte Stufenform"
AdditionalTrace = "Spur"

View File

@@ -7,3 +7,8 @@ BinaryBase = "Binary"
PrimeFactors = "Prime factors" PrimeFactors = "Prime factors"
MixedFraction = "Mixed fraction" MixedFraction = "Mixed fraction"
EuclideanDivision = "Euclidean division" EuclideanDivision = "Euclidean division"
AdditionalDeterminant = "Determinant"
AdditionalInverse = "Inverse"
AdditionalRowEchelonForm = "Row echelon form"
AdditionalReducedRowEchelonForm = "Reduced row echelon form"
AdditionalTrace = "Trace"

View File

@@ -7,3 +7,8 @@ BinaryBase = "Binario"
PrimeFactors = "Factores primos" PrimeFactors = "Factores primos"
MixedFraction = "Fracción mixta" MixedFraction = "Fracción mixta"
EuclideanDivision = "División euclidiana" EuclideanDivision = "División euclidiana"
AdditionalDeterminant = "Determinante"
AdditionalInverse = "Inversa"
AdditionalRowEchelonForm = "Matriz escalonada"
AdditionalReducedRowEchelonForm = "Matriz escalonada reducida"
AdditionalTrace = "Traza"

View File

@@ -7,3 +7,8 @@ BinaryBase = "Binaire"
PrimeFactors = "Facteurs premiers" PrimeFactors = "Facteurs premiers"
MixedFraction = "Fraction mixte" MixedFraction = "Fraction mixte"
EuclideanDivision = "Division euclidienne" EuclideanDivision = "Division euclidienne"
AdditionalDeterminant = "Déterminant"
AdditionalInverse = "Inverse"
AdditionalRowEchelonForm = "Forme échelonnée"
AdditionalReducedRowEchelonForm = "Forme échelonnée réduite"
AdditionalTrace = "Trace"

View File

@@ -4,6 +4,11 @@ AdditionalResults = "Risultati complementari"
DecimalBase = "Decimale" DecimalBase = "Decimale"
HexadecimalBase = "Esadecimale" HexadecimalBase = "Esadecimale"
BinaryBase = "Binario" BinaryBase = "Binario"
PrimeFactors = "Fattori primi" PrimeFactors = "Fattorizzazione"
MixedFraction = "Frazione mista" MixedFraction = "Frazione mista"
EuclideanDivision = "Divisione euclidea" EuclideanDivision = "Divisione euclidea"
AdditionalDeterminant = "Determinante"
AdditionalInverse = "Inversa"
AdditionalRowEchelonForm = "Matrice a scalini"
AdditionalReducedRowEchelonForm = "Matrice ridotta a scalini"
AdditionalTrace = "Traccia"

View File

@@ -1,9 +1,14 @@
CalculApp = "Calculatie" CalculApp = "Rekenen"
CalculAppCapital = "CALCULATIE" CalculAppCapital = "REKENEN"
AdditionalResults = "Bijkomende resultaten" AdditionalResults = "Aanvullende resultaten"
DecimalBase = "Decimaal" DecimalBase = "Decimaal"
HexadecimalBase = "Hexadecimaal" HexadecimalBase = "Hexadecimaal"
BinaryBase = "Binaire" BinaryBase = "Binair"
PrimeFactors = "Priemfactoren" PrimeFactors = "Ontbinding"
MixedFraction = "Gemengde breuk" MixedFraction = "Gemengde breuk"
EuclideanDivision = "Geheeltallige deling" EuclideanDivision = "Geheeltallige deling"
AdditionalDeterminant = "Determinant"
AdditionalInverse = "Inverse"
AdditionalRowEchelonForm = "Echelonvorm"
AdditionalReducedRowEchelonForm = "Gereduceerde echelonvorm"
AdditionalTrace = "Spoor"

View File

@@ -4,6 +4,11 @@ AdditionalResults = "Resultados adicionais"
DecimalBase = "Decimal" DecimalBase = "Decimal"
HexadecimalBase = "Hexadecimal" HexadecimalBase = "Hexadecimal"
BinaryBase = "Binário" BinaryBase = "Binário"
PrimeFactors = "Fatores primos" PrimeFactors = "Fatorização"
MixedFraction = "Fração mista" MixedFraction = "Fração mista"
EuclideanDivision = "Divisão euclidiana" EuclideanDivision = "Divisão euclidiana"
AdditionalDeterminant = "Determinante"
AdditionalInverse = "Matriz inversa"
AdditionalRowEchelonForm = "Matriz escalonada"
AdditionalReducedRowEchelonForm = "Matriz escalonada reduzida"
AdditionalTrace = "Traço"

View File

@@ -169,7 +169,8 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) {
ExpressionNode::Type::Sum, ExpressionNode::Type::Sum,
ExpressionNode::Type::Derivative, ExpressionNode::Type::Derivative,
ExpressionNode::Type::ConfidenceInterval, ExpressionNode::Type::ConfidenceInterval,
ExpressionNode::Type::PredictionInterval ExpressionNode::Type::PredictionInterval,
ExpressionNode::Type::Sequence
}; };
return e.isOfType(approximateOnlyTypes, sizeof(approximateOnlyTypes)/sizeof(ExpressionNode::Type)); return e.isOfType(approximateOnlyTypes, sizeof(approximateOnlyTypes)/sizeof(ExpressionNode::Type));
}, context) }, context)
@@ -218,7 +219,7 @@ Calculation::EqualSign Calculation::exactAndApproximateDisplayedOutputsAreEqual(
Preferences * preferences = Preferences::sharedPreferences(); Preferences * preferences = Preferences::sharedPreferences();
// TODO: complex format should not be needed here (as it is not used to create layouts) // 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); 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; return m_equalSign;
} else { } else {
/* Do not override m_equalSign in case there is enough room in the pool /* 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()) { if (o.hasUnit()) {
Expression unit; Expression unit;
PoincareHelpers::Reduce(&o, PoincareHelpers::ReduceAndRemoveUnit(&o, App::app()->localContext(), ExpressionNode::ReductionTarget::User, &unit, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined, ExpressionNode::UnitConversion::None);
App::app()->localContext(), double value = PoincareHelpers::ApproximateToScalar<double>(o, App::app()->localContext());
ExpressionNode::ReductionTarget::User, return (Unit::ShouldDisplayAdditionalOutputs(value, unit, GlobalPreferences::sharedGlobalPreferences()->unitFormat())) ? AdditionalInformationType::Unit : AdditionalInformationType::None;
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;
}
} }
if (o.isBasedIntegerCappedBy(k_maximalIntegerWithAdditionalInformation)) { if (o.isBasedIntegerCappedBy(k_maximalIntegerWithAdditionalInformation)) {
return AdditionalInformationType::Integer; return AdditionalInformationType::Integer;
@@ -291,6 +269,9 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co
if (o.hasDefinedComplexApproximation(context, complexFormat, preferences->angleUnit())) { if (o.hasDefinedComplexApproximation(context, complexFormat, preferences->angleUnit())) {
return AdditionalInformationType::Complex; return AdditionalInformationType::Complex;
} }
if (o.type() == ExpressionNode::Type::Matrix) {
return AdditionalInformationType::Matrix;
}
return AdditionalInformationType::None; return AdditionalInformationType::None;
} }

View File

@@ -21,6 +21,7 @@ class CalculationStore;
class Calculation { class Calculation {
friend CalculationStore; friend CalculationStore;
public: public:
static constexpr int k_numberOfExpressions = 4;
enum class EqualSign : uint8_t { enum class EqualSign : uint8_t {
Unknown, Unknown,
Approximation, Approximation,
@@ -40,6 +41,7 @@ public:
Rational, Rational,
Trigonometry, Trigonometry,
Unit, Unit,
Matrix,
Complex Complex
}; };
static bool DisplaysExact(DisplayOutput d) { return d != DisplayOutput::ApproximateOnly; } 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 * calculations instead of clearing less space, then fail to serialize, clear
* more space, fail to serialize, clear more space, etc., until reaching * more space, fail to serialize, clear more space, etc., until reaching
* sufficient free space. */ * 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() : Calculation() :
m_displayOutput(DisplayOutput::Unknown), m_displayOutput(DisplayOutput::Unknown),
@@ -93,8 +95,6 @@ public:
// Additional Information // Additional Information
AdditionalInformationType additionalInformationType(Poincare::Context * context); AdditionalInformationType additionalInformationType(Poincare::Context * context);
private: private:
static constexpr int maxWidth = 314;
static constexpr int k_numberOfExpressions = 4;
static constexpr KDCoordinate k_heightComputationFailureHeight = 50; static constexpr KDCoordinate k_heightComputationFailureHeight = 50;
static constexpr const char * k_maximalIntegerWithAdditionalInformation = "10000000000000000"; static constexpr const char * k_maximalIntegerWithAdditionalInformation = "10000000000000000";

View File

@@ -12,58 +12,47 @@ using namespace Shared;
namespace Calculation { namespace Calculation {
CalculationStore::CalculationStore() : CalculationStore::CalculationStore(char * buffer, int size) :
m_bufferEnd(m_buffer), m_buffer(buffer),
m_numberOfCalculations(0), m_bufferSize(size),
m_slidedBuffer(false), m_calculationAreaEnd(m_buffer),
m_indexOfFirstMemoizedCalculationPointer(0) 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) { ExpiringPointer<Calculation> CalculationStore::calculationAtIndex(int i) {
assert(!m_slidedBuffer);
assert(i >= 0 && i < m_numberOfCalculations); assert(i >= 0 && i < m_numberOfCalculations);
assert(m_indexOfFirstMemoizedCalculationPointer >= 0); // m_buffer is the adress of the oldest calculation in calculation store
if (i >= m_indexOfFirstMemoizedCalculationPointer && i < m_indexOfFirstMemoizedCalculationPointer + k_numberOfMemoizedCalculationPointers) { Calculation * c = (Calculation *) m_buffer;
// The calculation is within the range of memoized calculations if (i != m_numberOfCalculations-1) {
Calculation * c = m_memoizedCalculationPointers[i-m_indexOfFirstMemoizedCalculationPointer]; // The calculation we want is not the oldest one so we get its pointer
if (c != nullptr) { c = *reinterpret_cast<Calculation**>(addressOfPointerToCalculationOfIndex(i+1));
// The pointer was memoized
return ExpiringPointer<Calculation>(c);
}
c = bufferCalculationAtIndex(i);
m_memoizedCalculationPointers[i-m_indexOfFirstMemoizedCalculationPointer] = c;
return c;
} }
// Slide the memoization buffer return ExpiringPointer<Calculation>(c);
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);
} }
// Pushes an expression in the store
ExpiringPointer<Calculation> CalculationStore::push(const char * text, Context * context, HeightComputer heightComputer) { 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 */ * might be deleted */
Expression ans = ansExpression(context); Expression ans = ansExpression(context);
// Prepare the buffer for the new calculation /* Prepare the buffer for the new calculation
int minSize = Calculation::MinimalSize(); *The minimal size to store the new calculation is the minimal size of a calculation plus the pointer to its end */
assert(k_bufferSize > minSize); int minSize = Calculation::MinimalSize() + sizeof(Calculation *);
while (remainingBufferSize() < minSize || m_numberOfCalculations > k_maxNumberOfCalculations) { assert(m_bufferSize > minSize);
deleteLastCalculation(); 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 // 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. */ * available, so this memmove will not overide anything. */
Calculation newCalc = Calculation(); Calculation newCalc = Calculation();
size_t calcSize = sizeof(newCalc); size_t calcSize = sizeof(newCalc);
memmove(nextSerializationLocation, &newCalc, calcSize); memcpy(beginingOfFreeSpace, &newCalc, calcSize);
nextSerializationLocation += calcSize; beginingOfFreeSpace += calcSize;
} }
/* Add the input expression. /* Add the input expression.
* We do not store directly the text entered by the user because we do not * We do not store directly the text entered by the user because we do not
* want to keep Ans symbol in the calculation store. */ * 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); 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 /* If the input does not fit in the store (event if the current
* calculation is the only calculation), just replace the calculation with * calculation is the only calculation), just replace the calculation with
* undef. */ * undef. */
return emptyStoreAndPushUndef(context, heightComputer); return emptyStoreAndPushUndef(context, heightComputer);
} }
nextSerializationLocation += strlen(nextSerializationLocation) + 1; beginingOfFreeSpace += strlen(beginingOfFreeSpace) + 1;
} }
// Compute and serialize the outputs // Compute and serialize the outputs
@@ -110,30 +99,27 @@ ExpiringPointer<Calculation> CalculationStore::push(const char * text, Context *
if (i == numberOfOutputs - 1) { if (i == numberOfOutputs - 1) {
numberOfSignificantDigits = Poincare::Preferences::sharedPreferences()->numberOfSignificantDigits(); 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 /* If the exat/approximate output does not fit in the store (event if the
* current calculation is the only calculation), replace the output with * current calculation is the only calculation), replace the output with
* undef if it fits, else replace the whole calcualtion with undef. */ * undef if it fits, else replace the whole calcualtion with undef. */
Expression undef = Undefined::Builder(); Expression undef = Undefined::Builder();
if (!pushSerializeExpression(undef, nextSerializationLocation, &newCalculationsLocation)) { if (!pushSerializeExpression(undef, beginingOfFreeSpace, &endOfFreeSpace)) {
return emptyStoreAndPushUndef(context, heightComputer); 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 // The new calculation is now stored
size_t slideSize = m_buffer + k_bufferSize - newCalculationsLocation;
memcpy(nextSerializationLocation, newCalculationsLocation, slideSize);
m_slidedBuffer = false;
m_numberOfCalculations++; m_numberOfCalculations++;
m_bufferEnd+= nextSerializationLocation - m_buffer;
// Clean the memoization // The end of the calculation storage area is updated
resetMemoizedModelsAfterCalculationIndex(-1); m_calculationAreaEnd += beginingOfFreeSpace - previousCalc;
ExpiringPointer<Calculation> calculation = ExpiringPointer<Calculation>(reinterpret_cast<Calculation *>(previousCalc));
ExpiringPointer<Calculation> calculation = ExpiringPointer<Calculation>(reinterpret_cast<Calculation *>(m_buffer));
/* Heights are computed now to make sure that the display output is decided /* 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 * 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 * 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; return calculation;
} }
// Delete the calculation of index i
void CalculationStore::deleteCalculationAtIndex(int i) { void CalculationStore::deleteCalculationAtIndex(int i) {
assert(i >= 0 && i < m_numberOfCalculations); assert(i >= 0 && i < m_numberOfCalculations);
assert(!m_slidedBuffer); if (i == 0) {
ExpiringPointer<Calculation> calcI = calculationAtIndex(i); ExpiringPointer<Calculation> lastCalculationPointer = calculationAtIndex(0);
char * nextCalc = reinterpret_cast<char *>(calcI->next()); m_calculationAreaEnd = (char *)(lastCalculationPointer.pointer());
assert(m_bufferEnd >= nextCalc); m_numberOfCalculations--;
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();
return; 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) { Expression CalculationStore::ansExpression(Context * context) {
if (numberOfCalculations() == 0) { if (numberOfCalculations() == 0) {
return Rational::Builder(0); return Rational::Builder(0);
@@ -192,92 +184,42 @@ Expression CalculationStore::ansExpression(Context * context) {
return mostRecentCalculation->exactOutput(); return mostRecentCalculation->exactOutput();
} }
Calculation * CalculationStore::bufferCalculationAtIndex(int i) { // Push converted expression in the buffer
int currentIndex = 0;
for (Calculation * c : *this) {
if (currentIndex == i) {
return c;
}
currentIndex++;
}
assert(false);
return nullptr;
}
bool CalculationStore::pushSerializeExpression(Expression e, char * location, char * * newCalculationsLocation, int numberOfSignificantDigits) { bool CalculationStore::pushSerializeExpression(Expression e, char * location, char * * newCalculationsLocation, int numberOfSignificantDigits) {
assert(m_slidedBuffer); assert(*newCalculationsLocation <= m_buffer + m_bufferSize);
assert(*newCalculationsLocation <= m_buffer + k_bufferSize);
bool expressionIsPushed = false; bool expressionIsPushed = false;
while (true) { while (true) {
size_t locationSize = *newCalculationsLocation - location; size_t locationSize = *newCalculationsLocation - location;
expressionIsPushed = (PoincareHelpers::Serialize(e, location, locationSize, numberOfSignificantDigits) < (int)locationSize-1); 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; break;
} }
*newCalculationsLocation = *newCalculationsLocation + deleteLastCalculation(); *newCalculationsLocation = *newCalculationsLocation + deleteOldestCalculation();
assert(*newCalculationsLocation <= m_buffer + k_bufferSize); assert(*newCalculationsLocation <= m_buffer + m_bufferSize);
} }
return expressionIsPushed; 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) { Shared::ExpiringPointer<Calculation> CalculationStore::emptyStoreAndPushUndef(Context * context, HeightComputer heightComputer) {
/* We end up here as a result of a failed calculation push. The store /* 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. */ * attributes are not necessarily clean, so we need to reset them. */
m_slidedBuffer = false;
deleteAll(); deleteAll();
return push(Undefined::Name(), context, heightComputer); return push(Undefined::Name(), context, heightComputer);
} }
void CalculationStore::resetMemoizedModelsAfterCalculationIndex(int index) { // Recompute memoized pointers to the calculations after index i
if (index < m_indexOfFirstMemoizedCalculationPointer) { void CalculationStore::recomputeMemoizedPointersAfterCalculationIndex(int index) {
memset(&m_memoizedCalculationPointers, 0, k_numberOfMemoizedCalculationPointers * sizeof(Calculation *)); assert(index < m_numberOfCalculations);
return; // Clear pointer and recompute new ones
} Calculation * c = calculationAtIndex(index).pointer();
if (index >= m_indexOfFirstMemoizedCalculationPointer + k_numberOfMemoizedCalculationPointers) { Calculation * nextCalc;
return; while (index != 0) {
} nextCalc = c->next();
for (int i = index - m_indexOfFirstMemoizedCalculationPointer; i < k_numberOfMemoizedCalculationPointers; i++) { memcpy(addressOfPointerToCalculationOfIndex(index), &nextCalc, sizeof(Calculation *));
m_memoizedCalculationPointers[i] = nullptr; c = nextCalc;
index--;
} }
} }

View File

@@ -7,37 +7,45 @@
namespace Calculation { namespace Calculation {
/* To optimize the storage space, we use one big buffer for all calculations. /*
* 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 The calculations are stored one after another while pointers to the end of each
* (for input and outputs). To optimize the storage, we then wanted to put all calculation are stored at the end of the buffer, in the opposite direction.
* outputs in a cache where they could be deleted to add a new entry, and By doing so, we can memoize every calculation entered while not limiting
* recomputed on cache miss. However, the computation depends too much on the the number of calculation stored in the buffer.
* state of the memory for this to be possible. For instance:
* 6->a If the remaining space is too small for storing a new calculation, we
* a+1 delete the oldest one.
* Perform some big computations that remove a+1 from the cache
* Delete a from the variable box. Memory layout :
* Scroll up to display a+1 : a does not exist anymore so the outputs won't be <- Available space for new calculations ->
* recomputed correctly. +--------------------------------------------------------------------------------------------------------------------+
* | | | | | | | | | |
* Now we do not cap the number of calculations and just delete the oldests to | Calculation 3 | Calculation 2 | Calculation 1 | Calculation O | |p0|p1|p2|p3|
* create space for a new calculation. */ | Oldest | | | | | | | | |
+--------------------------------------------------------------------------------------------------------------------+
^ ^ ^ ^ ^ ^
m_buffer p3 p2 p1 p0 a
m_calculationAreaEnd = p0
a = addressOfPointerToCalculation(0)
*/
class CalculationStore { class CalculationStore {
public: public:
CalculationStore(); CalculationStore();
CalculationStore(char * buffer, int size);
Shared::ExpiringPointer<Calculation> calculationAtIndex(int i); Shared::ExpiringPointer<Calculation> calculationAtIndex(int i);
typedef KDCoordinate (*HeightComputer)(Calculation * c, bool expanded); typedef KDCoordinate (*HeightComputer)(Calculation * c, bool expanded);
Shared::ExpiringPointer<Calculation> push(const char * text, Poincare::Context * context, HeightComputer heightComputer); Shared::ExpiringPointer<Calculation> push(const char * text, Poincare::Context * context, HeightComputer heightComputer);
void deleteCalculationAtIndex(int i); void deleteCalculationAtIndex(int i);
void deleteAll(); void deleteAll();
int remainingBufferSize() const { assert(m_calculationAreaEnd >= m_buffer); return m_bufferSize - (m_calculationAreaEnd - m_buffer) - m_numberOfCalculations*sizeof(Calculation*); }
int numberOfCalculations() const { return m_numberOfCalculations; } int numberOfCalculations() const { return m_numberOfCalculations; }
Poincare::Expression ansExpression(Poincare::Context * context); Poincare::Expression ansExpression(Poincare::Context * context);
void tidy(); int bufferSize() { return m_bufferSize; }
private: private:
static constexpr int k_maxNumberOfCalculations = 25;
static constexpr int k_bufferSize = 10 * Calculation::k_numberOfExpressions * Constant::MaxSerializedExpressionSize;
class CalculationIterator { class CalculationIterator {
public: public:
@@ -53,26 +61,22 @@ private:
}; };
CalculationIterator begin() const { return CalculationIterator(m_buffer); } 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); 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); Shared::ExpiringPointer<Calculation> emptyStoreAndPushUndef(Poincare::Context * context, HeightComputer heightComputer);
char m_buffer[k_bufferSize]; char * m_buffer;
const char * m_bufferEnd; int m_bufferSize;
const char * m_calculationAreaEnd;
int m_numberOfCalculations; int m_numberOfCalculations;
bool m_slidedBuffer;
size_t deleteOldestCalculation();
char * addressOfPointerToCalculationOfIndex(int i) {return m_buffer + m_bufferSize - (m_numberOfCalculations - i)*sizeof(Calculation *);}
// Memoization // Memoization
static constexpr int k_numberOfMemoizedCalculationPointers = 10; char * beginingOfMemoizationArea() {return addressOfPointerToCalculationOfIndex(0);};
void resetMemoizedModelsAfterCalculationIndex(int index); void recomputeMemoizedPointersAfterCalculationIndex(int index);
int m_indexOfFirstMemoizedCalculationPointer;
mutable Calculation * m_memoizedCalculationPointers[k_numberOfMemoizedCalculationPointers];
}; };
} }

View File

@@ -38,13 +38,14 @@ void EditExpressionController::ContentView::reload() {
markRectAsDirty(bounds()); 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), ViewController(parentResponder),
m_cacheBuffer(cacheBuffer),
m_cacheBufferInformation(cacheBufferInformation),
m_historyController(historyController), m_historyController(historyController),
m_calculationStore(calculationStore), m_calculationStore(calculationStore),
m_contentView(this, static_cast<CalculationSelectableTableView *>(m_historyController->view()), inputEventHandlerDelegate, this, this) m_contentView(this, static_cast<CalculationSelectableTableView *>(m_historyController->view()), inputEventHandlerDelegate, this, this)
{ {
m_cacheBuffer[0] = 0;
} }
void EditExpressionController::insertTextBody(const char * text) { void EditExpressionController::insertTextBody(const char * text) {
@@ -58,6 +59,15 @@ void EditExpressionController::didBecomeFirstResponder() {
Container::activeApp()->setFirstResponder(m_contentView.expressionField()); 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() { void EditExpressionController::viewWillAppear() {
m_historyController->viewWillAppear(); m_historyController->viewWillAppear();
} }
@@ -128,7 +138,7 @@ bool EditExpressionController::inputViewDidReceiveEvent(Ion::Events::Event event
} }
if (event == Ion::Events::Up) { if (event == Ion::Events::Up) {
if (m_calculationStore->numberOfCalculations() > 0) { if (m_calculationStore->numberOfCalculations() > 0) {
m_cacheBuffer[0] = 0; clearCacheBuffer();
m_contentView.expressionField()->setEditing(false, false); m_contentView.expressionField()->setEditing(false, false);
Container::activeApp()->setFirstResponder(m_historyController); Container::activeApp()->setFirstResponder(m_historyController);
} }

View File

@@ -7,7 +7,6 @@
#include "../shared/text_field_delegate.h" #include "../shared/text_field_delegate.h"
#include "../shared/layout_field_delegate.h" #include "../shared/layout_field_delegate.h"
#include "history_controller.h" #include "history_controller.h"
#include "calculation_store.h"
#include "selectable_table_view.h" #include "selectable_table_view.h"
namespace Calculation { namespace Calculation {
@@ -15,11 +14,23 @@ namespace Calculation {
/* TODO: implement a split view */ /* TODO: implement a split view */
class EditExpressionController : public ViewController, public Shared::TextFieldDelegate, public Shared::LayoutFieldDelegate { class EditExpressionController : public ViewController, public Shared::TextFieldDelegate, public Shared::LayoutFieldDelegate {
public: 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; } View * view() override { return &m_contentView; }
void didBecomeFirstResponder() override; void didBecomeFirstResponder() override;
void viewWillAppear() override; void viewWillAppear() override;
void insertTextBody(const char * text); void insertTextBody(const char * text);
void restoreInput();
void memoizeInput();
/* TextFieldDelegate */ /* TextFieldDelegate */
bool textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) override; bool textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) override;
@@ -47,11 +58,12 @@ private:
ExpressionField m_expressionField; ExpressionField m_expressionField;
}; };
void reloadView(); void reloadView();
void clearCacheBuffer() { m_cacheBuffer[0] = 0; *m_cacheBufferInformation = 0; }
bool inputViewDidReceiveEvent(Ion::Events::Event event, bool shouldDuplicateLastCalculation); bool inputViewDidReceiveEvent(Ion::Events::Event event, bool shouldDuplicateLastCalculation);
bool inputViewDidFinishEditing(const char * text, Poincare::Layout layoutR); bool inputViewDidFinishEditing(const char * text, Poincare::Layout layoutR);
bool inputViewDidAbortEditing(const char * text); bool inputViewDidAbortEditing(const char * text);
static constexpr int k_cacheBufferSize = Constant::MaxSerializedExpressionSize; char * m_cacheBuffer;
char m_cacheBuffer[k_cacheBufferSize]; size_t * m_cacheBufferInformation;
HistoryController * m_historyController; HistoryController * m_historyController;
CalculationStore * m_calculationStore; CalculationStore * m_calculationStore;
ContentView m_contentView; ContentView m_contentView;

View File

@@ -17,7 +17,8 @@ HistoryController::HistoryController(EditExpressionController * editExpressionCo
m_integerController(editExpressionController), m_integerController(editExpressionController),
m_rationalController(editExpressionController), m_rationalController(editExpressionController),
m_trigonometryController(editExpressionController), m_trigonometryController(editExpressionController),
m_unitController(editExpressionController) m_unitController(editExpressionController),
m_matrixController(editExpressionController)
{ {
for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) { for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) {
m_calculationHistory[i].setParentResponder(&m_selectableTableView); m_calculationHistory[i].setParentResponder(&m_selectableTableView);
@@ -110,6 +111,8 @@ bool HistoryController::handleEvent(Ion::Events::Event event) {
vc = &m_rationalController; vc = &m_rationalController;
} else if (additionalInfoType == Calculation::AdditionalInformationType::Unit) { } else if (additionalInfoType == Calculation::AdditionalInformationType::Unit) {
vc = &m_unitController; vc = &m_unitController;
} else if (additionalInfoType == Calculation::AdditionalInformationType::Matrix) {
vc = &m_matrixController;
} }
if (vc) { if (vc) {
vc->setExpression(e); vc->setExpression(e);

View File

@@ -10,6 +10,7 @@
#include "additional_outputs/rational_list_controller.h" #include "additional_outputs/rational_list_controller.h"
#include "additional_outputs/trigonometry_list_controller.h" #include "additional_outputs/trigonometry_list_controller.h"
#include "additional_outputs/unit_list_controller.h" #include "additional_outputs/unit_list_controller.h"
#include "additional_outputs/matrix_list_controller.h"
namespace Calculation { namespace Calculation {
@@ -48,6 +49,7 @@ private:
RationalListController m_rationalController; RationalListController m_rationalController;
TrigonometryListController m_trigonometryController; TrigonometryListController m_trigonometryController;
UnitListController m_unitController; UnitListController m_unitController;
MatrixListController m_matrixController;
}; };
} }

View File

@@ -5,9 +5,17 @@
#include <assert.h> #include <assert.h>
#include "../calculation_store.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 Poincare;
using namespace Calculation; 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) { void assert_store_is(CalculationStore * store, const char * * result) {
for (int i = 0; i < store->numberOfCalculations(); i++) { for (int i = 0; i < store->numberOfCalculations(); i++) {
quiz_assert(strcmp(store->calculationAtIndex(i)->inputText(), result[i]) == 0); 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) { QUIZ_CASE(calculation_store) {
Shared::GlobalContext globalContext; Shared::GlobalContext globalContext;
CalculationStore store; CalculationStore store(calculationBuffer,calculationBufferSize);
// Store is now {9, 8, 7, 6, 5, 4, 3, 2, 1, 0} // 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"}; const char * result[] = {"9", "8", "7", "6", "5", "4", "3", "2", "1", "0"};
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
@@ -29,101 +37,117 @@ QUIZ_CASE(calculation_store) {
assert_store_is(&store, result); assert_store_is(&store, result);
for (int i = 9; i > 0; i = i-2) { for (int i = 9; i > 0; i = i-2) {
store.deleteCalculationAtIndex(i); store.deleteCalculationAtIndex(i);
} }
// Store is now {9, 7, 5, 3, 1} // Store is now {9, 7, 5, 3, 1}
const char * result2[] = {"9", "7", "5", "3", "1"}; const char * result2[] = {"9", "7", "5", "3", "1"};
assert_store_is(&store, result2); assert_store_is(&store, result2);
store.deleteAll(); 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) { QUIZ_CASE(calculation_ans) {
Shared::GlobalContext globalContext; Shared::GlobalContext globalContext;
CalculationStore store; CalculationStore store(calculationBuffer,calculationBufferSize);
store.push("1+3/4", &globalContext, dummyHeight); store.push("1+3/4", &globalContext, dummyHeight);
store.push("ans+2/3", &globalContext, dummyHeight); store.push("ans+2/3", &globalContext, dummyHeight);
Shared::ExpiringPointer<::Calculation::Calculation> lastCalculation = store.calculationAtIndex(0); 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); quiz_assert(strcmp(lastCalculation->exactOutputText(),"29/12") == 0);
store.push("ans+0.22", &globalContext, dummyHeight); store.push("ans+0.22", &globalContext, dummyHeight);
lastCalculation = store.calculationAtIndex(0); lastCalculation = store.calculationAtIndex(0);
quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximateToggle); quiz_assert(lastCalculation->displayOutput(&globalContext) == DisplayOutput::ExactAndApproximateToggle);
quiz_assert(strcmp(lastCalculation->approximateOutputText(::Calculation::Calculation::NumberOfSignificantDigits::Maximal),"2.6366666666667") == 0); quiz_assert(strcmp(lastCalculation->approximateOutputText(NumberOfSignificantDigits::Maximal),"2.6366666666667") == 0);
store.deleteAll(); 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); store->push(input, context, dummyHeight);
Shared::ExpiringPointer<::Calculation::Calculation> lastCalculation = store->calculationAtIndex(0); Shared::ExpiringPointer<::Calculation::Calculation> lastCalculation = store->calculationAtIndex(0);
quiz_assert(lastCalculation->displayOutput(context) == display); quiz_assert(lastCalculation->displayOutput(context) == display);
if (sign != ::Calculation::Calculation::EqualSign::Unknown) { if (sign != EqualSign::Unknown) {
quiz_assert(lastCalculation->exactAndApproximateDisplayedOutputsAreEqual(context) == sign); quiz_assert(lastCalculation->exactAndApproximateDisplayedOutputsAreEqual(context) == sign);
} }
if (exactOutput) { if (exactOutput) {
quiz_assert_print_if_failure(strcmp(lastCalculation->exactOutputText(), exactOutput) == 0, input); quiz_assert_print_if_failure(strcmp(lastCalculation->exactOutputText(), exactOutput) == 0, input);
} }
if (displayedApproximateOutput) { 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) { 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(); store->deleteAll();
} }
QUIZ_CASE(calculation_significant_digits) { QUIZ_CASE(calculation_significant_digits) {
Shared::GlobalContext globalContext; 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("123456789", DisplayOutput::ExactAndApproximate, 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("1234567", DisplayOutput::ApproximateOnly, EqualSign::Equal, "1234567", "1234567", "1234567", &globalContext, &store);
} }
QUIZ_CASE(calculation_display_exact_approximate) { QUIZ_CASE(calculation_display_exact_approximate) {
Shared::GlobalContext globalContext; 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/2", DisplayOutput::ExactAndApproximate, 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/3", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("1/0", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); assertCalculationIs("1/0", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
assertCalculationIs("2x-x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); assertCalculationIs("2x-x", DisplayOutput::ApproximateOnly, 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,2,3]]", DisplayOutput::ApproximateOnly, 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("[[1,x,3]]", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "undef", "undef", &globalContext, &store);
assertCalculationIs("28^7", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); assertCalculationIs("28^7", DisplayOutput::ExactAndApproximate, 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("3+√(2)→a", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "√(2)+3", nullptr, nullptr, &globalContext, &store);
Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); 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(); 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(); 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(); 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+random()", DisplayOutput::ApproximateOnly, 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("1+1+round(1.343,2)", DisplayOutput::ApproximateOnly, 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("randint(2,2)+3", DisplayOutput::ApproximateOnly, 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("confidence(0.5,2)+3", DisplayOutput::ApproximateOnly, 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("prediction(0.5,2)+3", DisplayOutput::ApproximateOnly, 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("prediction95(0.5,2)+3", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
} }
QUIZ_CASE(calculation_symbolic_computation) { QUIZ_CASE(calculation_symbolic_computation) {
Shared::GlobalContext globalContext; 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("x+x+1+3+√(π)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
assertCalculationIs("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); assertCalculationIs("f(x)", DisplayOutput::ApproximateOnly, 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("1+x→f(x)", DisplayOutput::ExactOnly, 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(x)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
assertCalculationIs("f(2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", "3", "3", &globalContext, &store); assertCalculationIs("f(2)", DisplayOutput::ApproximateOnly, EqualSign::Equal, "3", "3", "3", &globalContext, &store);
assertCalculationIs("2→x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "2", nullptr, nullptr, &globalContext, &store); assertCalculationIs("2→x", DisplayOutput::ApproximateOnly, EqualSign::Equal, "2", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", nullptr, nullptr, &globalContext, &store); assertCalculationIs("f(x)", DisplayOutput::ApproximateOnly, 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::ExactAndApproximate, EqualSign::Approximation, "√(π)+8", nullptr, nullptr, &globalContext, &store);
Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); Ion::Storage::sharedStorage()->recordNamed("f.func").destroy();
Ion::Storage::sharedStorage()->recordNamed("x.exp").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) { QUIZ_CASE(calculation_symbolic_computation_and_parametered_expressions) {
Shared::GlobalContext globalContext; 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.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)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store); assertCalculationIs("int(x,x,0,2)", DisplayOutput::ApproximateOnly, 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("sum(x,x,0,2)", DisplayOutput::ApproximateOnly, 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("product(x,x,1,2)", DisplayOutput::ApproximateOnly, 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("diff(x^2,x,3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "6", "6", &globalContext, &store);
assertCalculationIs("2→x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); assertCalculationIs("2→x", DisplayOutput::ApproximateOnly, 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("int(x,x,0,2)", DisplayOutput::ApproximateOnly, 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("sum(x,x,0,2)", DisplayOutput::ApproximateOnly, 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("product(x,x,1,2)", DisplayOutput::ApproximateOnly, 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("diff(x^2,x,3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "6", "6", &globalContext, &store);
Ion::Storage::sharedStorage()->recordNamed("x.exp").destroy(); Ion::Storage::sharedStorage()->recordNamed("x.exp").destroy();
} }
@@ -150,34 +174,34 @@ QUIZ_CASE(calculation_symbolic_computation_and_parametered_expressions) {
QUIZ_CASE(calculation_complex_format) { QUIZ_CASE(calculation_complex_format) {
Shared::GlobalContext globalContext; Shared::GlobalContext globalContext;
CalculationStore store; CalculationStore store(calculationBuffer,calculationBufferSize);
Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Real); 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+𝐢", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "1+𝐢", "1+𝐢", &globalContext, &store);
assertCalculationIs("√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "unreal", nullptr, nullptr, &globalContext, &store); assertCalculationIs("√(-1)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "unreal", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("ln(-2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store); assertCalculationIs("ln(-2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store);
assertCalculationIs("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store); assertCalculationIs("√(-1)×√(-1)", DisplayOutput::ApproximateOnly, 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)^(1/3)", DisplayOutput::ApproximateOnly, 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("(-8)^(2/3)", DisplayOutput::ApproximateOnly, 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("(-2)^(1/4)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store);
Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Cartesian); 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+𝐢", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "1+𝐢", "1+𝐢", &globalContext, &store);
assertCalculationIs("√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "𝐢", "𝐢", &globalContext, &store); assertCalculationIs("√(-1)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "𝐢", "𝐢", &globalContext, &store);
assertCalculationIs("ln(-2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ln(-2)", nullptr, nullptr, &globalContext, &store); assertCalculationIs("ln(-2)", DisplayOutput::ExactAndApproximate, 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("√(-1)×√(-1)", DisplayOutput::ApproximateOnly, 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)^(1/3)", DisplayOutput::ExactAndApproximate, 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("(-8)^(2/3)", DisplayOutput::ExactAndApproximate, 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("(-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); 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+𝐢", DisplayOutput::ExactAndApproximate, 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("√(-1)", DisplayOutput::ExactAndApproximate, 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("ln(-2)", DisplayOutput::ExactAndApproximate, 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("√(-1)×√(-1)", DisplayOutput::ExactAndApproximate, 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)^(1/3)", DisplayOutput::ExactAndApproximate, 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("(-8)^(2/3)", DisplayOutput::ExactAndApproximate, 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("(-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); Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Cartesian);
} }

View File

@@ -89,16 +89,18 @@ App::App(Snapshot * snapshot) :
, snapshot->lockOnConsole() , snapshot->lockOnConsole()
#endif #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_menuController(&m_listFooter, this, snapshot->scriptStore(), &m_listFooter),
m_codeStackViewController(&m_modalViewController, &m_listFooter), m_codeStackViewController(&m_modalViewController, &m_listFooter),
m_variableBoxController(snapshot->scriptStore()) m_variableBoxController(snapshot->scriptStore())
{ {
Clipboard::sharedClipboard()->enterPython();
} }
App::~App() { App::~App() {
assert(!m_consoleController.inputRunLoopActive()); assert(!m_consoleController.inputRunLoopActive());
deinitPython(); deinitPython();
Clipboard::sharedClipboard()->exitPython();
} }
bool App::handleEvent(Ion::Events::Event event) { bool App::handleEvent(Ion::Events::Event event) {

View File

@@ -9,6 +9,7 @@
#include "script_store.h" #include "script_store.h"
#include "python_toolbox.h" #include "python_toolbox.h"
#include "variable_box_controller.h" #include "variable_box_controller.h"
#include "../shared/shared_app.h"
namespace Code { namespace Code {
@@ -21,7 +22,7 @@ public:
App::Descriptor::ExaminationLevel examinationLevel() override; App::Descriptor::ExaminationLevel examinationLevel() override;
const Image * icon() override; const Image * icon() override;
}; };
class Snapshot : public ::App::Snapshot { class Snapshot : public SharedApp::Snapshot {
public: public:
Snapshot(); Snapshot();
App * unpack(Container * container) override; App * unpack(Container * container) override;

View File

@@ -30,8 +30,8 @@ PythonColor = "Definiere eine RGB-Farbe"
PythonColorBlack = "Black color" PythonColorBlack = "Black color"
PythonColorBlue = "Blue color" PythonColorBlue = "Blue color"
PythonColorBrown = "Brown color" PythonColorBrown = "Brown color"
PythonColorGray = "Gray color"
PythonColorGreen = "Green color" PythonColorGreen = "Green color"
PythonColorGrey = "Grey color"
PythonColorOrange = "Orange color" PythonColorOrange = "Orange color"
PythonColorPink = "Pink color" PythonColorPink = "Pink color"
PythonColorPurple = "Purple color" PythonColorPurple = "Purple color"

View File

@@ -1,7 +1,7 @@
PythonPound = "Comment" PythonPound = "Comment"
PythonPercent = "Modulo" PythonPercent = "Modulo"
Python1J = "Imaginary i" Python1J = "Imaginary i"
PythonLF = "line feed" PythonLF = "Line feed"
PythonTab = "Tabulation" PythonTab = "Tabulation"
PythonAmpersand = "Bitwise and" PythonAmpersand = "Bitwise and"
PythonSymbolExp = "Bitwise exclusive or" PythonSymbolExp = "Bitwise exclusive or"
@@ -30,8 +30,8 @@ PythonColor = "Define a rgb color"
PythonColorBlack = "Black color" PythonColorBlack = "Black color"
PythonColorBlue = "Blue color" PythonColorBlue = "Blue color"
PythonColorBrown = "Brown color" PythonColorBrown = "Brown color"
PythonColorGray = "Gray color"
PythonColorGreen = "Green color" PythonColorGreen = "Green color"
PythonColorGrey = "Grey color"
PythonColorOrange = "Orange color" PythonColorOrange = "Orange color"
PythonColorPink = "Pink color" PythonColorPink = "Pink color"
PythonColorPurple = "Purple color" PythonColorPurple = "Purple color"

View File

@@ -30,8 +30,8 @@ PythonColor = "Define a rgb color"
PythonColorBlack = "Black color" PythonColorBlack = "Black color"
PythonColorBlue = "Blue color" PythonColorBlue = "Blue color"
PythonColorBrown = "Brown color" PythonColorBrown = "Brown color"
PythonColorGray = "Gray color"
PythonColorGreen = "Green color" PythonColorGreen = "Green color"
PythonColorGrey = "Grey color"
PythonColorOrange = "Orange color" PythonColorOrange = "Orange color"
PythonColorPink = "Pink color" PythonColorPink = "Pink color"
PythonColorPurple = "Purple color" PythonColorPurple = "Purple color"

View File

@@ -30,8 +30,8 @@ PythonColor = "Définit une couleur rvb"
PythonColorBlack = "Couleur noire" PythonColorBlack = "Couleur noire"
PythonColorBlue = "Couleur bleue" PythonColorBlue = "Couleur bleue"
PythonColorBrown = "Couleur marron" PythonColorBrown = "Couleur marron"
PythonColorGray = "Couleur grise"
PythonColorGreen = "Couleur verte" PythonColorGreen = "Couleur verte"
PythonColorGrey = "Couleur grise"
PythonColorOrange = "Couleur orange" PythonColorOrange = "Couleur orange"
PythonColorPink = "Couleur rose" PythonColorPink = "Couleur rose"
PythonColorPurple = "Couleur violette" PythonColorPurple = "Couleur violette"

View File

@@ -30,8 +30,8 @@ PythonColor = "Definisci un colore rvb"
PythonColorBlack = "Colore nero" PythonColorBlack = "Colore nero"
PythonColorBlue = "Colore blu" PythonColorBlue = "Colore blu"
PythonColorBrown = "Colore marrone" PythonColorBrown = "Colore marrone"
PythonColorGray = "Colore grigio"
PythonColorGreen = "Colore verde" PythonColorGreen = "Colore verde"
PythonColorGrey = "Colore grigio"
PythonColorOrange = "Colore arancione" PythonColorOrange = "Colore arancione"
PythonColorPink = "Colore rosa" PythonColorPink = "Colore rosa"
PythonColorPurple = "Colore viola" PythonColorPurple = "Colore viola"

View File

@@ -13,14 +13,14 @@ PythonAbs = "Absolute waarde"
PythonAcos = "Arccosinus" PythonAcos = "Arccosinus"
PythonAcosh = "Arccosinus hyperbolicus" PythonAcosh = "Arccosinus hyperbolicus"
PythonAppend = "Voeg x toe aan het eind van je lijst" 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" PythonAsin = "Arcsinus"
PythonAsinh = "Arcsinus hyperbolicus" PythonAsinh = "Arcsinus hyperbolicus"
PythonAtan = "Arctangens" PythonAtan = "Arctangens"
PythonAtan2 = "Geeft atan(y/x)" PythonAtan2 = "Geeft atan(y/x)"
PythonAtanh = "Arctangens hyperbolicus" PythonAtanh = "Arctangens hyperbolicus"
PythonAxis = "Set the axes to (xmin,xmax,ymin,ymax)" PythonAxis = "Stel de assen in (xmin,xmax,ymin,ymax)"
PythonBar = "Draw a bar plot with x values" PythonBar = "Teken staafdiagram met x-waarden"
PythonBin = "Zet integer om in een binair getal" PythonBin = "Zet integer om in een binair getal"
PythonCeil = "Plafond" PythonCeil = "Plafond"
PythonChoice = "Geeft willek. getal van de lijst" PythonChoice = "Geeft willek. getal van de lijst"
@@ -30,8 +30,8 @@ PythonColor = "Definieer een rgb kleur"
PythonColorBlack = "Zwarte kleur" PythonColorBlack = "Zwarte kleur"
PythonColorBlue = "Blauwe kleur" PythonColorBlue = "Blauwe kleur"
PythonColorBrown = "Bruine kleur" PythonColorBrown = "Bruine kleur"
PythonColorGray = "Grijze kleur"
PythonColorGreen = "Groene kleur" PythonColorGreen = "Groene kleur"
PythonColorGrey = "Grijze kleur"
PythonColorOrange = "Oranje kleur" PythonColorOrange = "Oranje kleur"
PythonColorPink = "Roze kleur" PythonColorPink = "Roze kleur"
PythonColorPurple = "Paarse kleur" PythonColorPurple = "Paarse kleur"
@@ -60,15 +60,15 @@ PythonFrExp = "Mantisse en exponent van x: (m,e)"
PythonGamma = "Gammafunctie" PythonGamma = "Gammafunctie"
PythonGetPixel = "Geef pixel (x,y) kleur (rgb)" PythonGetPixel = "Geef pixel (x,y) kleur (rgb)"
PythonGetrandbits = "Integer met k willekeurige bits" PythonGetrandbits = "Integer met k willekeurige bits"
PythonGrid = "Toggle the visibility of the grid" PythonGrid = "Verander zichtbaarheid raster"
PythonHex = "Zet integer om in hexadecimaal" PythonHex = "Zet integer om in hexadecimaal"
PythonHist = "Draw the histogram of x" PythonHist = "Teken het histogram van x"
PythonImportCmath = "Importeer cmath module" PythonImportCmath = "Importeer cmath module"
PythonImportIon = "Importeer ion module" PythonImportIon = "Importeer ion module"
PythonImportKandinsky = "Importeer kandinsky module" PythonImportKandinsky = "Importeer kandinsky module"
PythonImportRandom = "Importeer random module" PythonImportRandom = "Importeer random module"
PythonImportMath = "Importeer math module" PythonImportMath = "Importeer math module"
PythonImportMatplotlibPyplot = "Import matplotlib.pyplot module" PythonImportMatplotlibPyplot = "Importeer matplotlib.pyplot module"
PythonImportTime = "Importeer time module" PythonImportTime = "Importeer time module"
PythonImportOs = "Importeer os module" PythonImportOs = "Importeer os module"
PythonOsUname = " Krijg systeeminfo" PythonOsUname = " Krijg systeeminfo"
@@ -84,7 +84,7 @@ PythonIonFunction = "ion module voorvoegsel"
PythonIsFinite = "Controleer of x eindig is" PythonIsFinite = "Controleer of x eindig is"
PythonIsInfinite = "Controleer of x oneindig is" PythonIsInfinite = "Controleer of x oneindig is"
PythonIsKeyDown = "Geef True als k toets omlaag 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" PythonKandinskyFunction = "kandinsky module voorvoegsel"
PythonKeyLeft = "PIJL NAAR LINKS toets" PythonKeyLeft = "PIJL NAAR LINKS toets"
PythonKeyUp = "PIJL OMHOOG toets" PythonKeyUp = "PIJL OMHOOG toets"
@@ -146,14 +146,14 @@ PythonModf = "Fractionele en gehele delen van x"
PythonMonotonic = "Waarde van een monotone klok" PythonMonotonic = "Waarde van een monotone klok"
PythonOct = "Integer omzetten naar octaal" PythonOct = "Integer omzetten naar octaal"
PythonPhase = "Fase van z in radialen" 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" PythonPolar = "z in poolcoördinaten"
PythonPop = "Verwijder en breng het laatste item terug" PythonPop = "Verwijder en breng het laatste item terug"
PythonPower = "x tot de macht y" PythonPower = "x tot de macht y"
PythonPrint = "Print object" PythonPrint = "Print object"
PythonRadians = "Zet x om van graden naar radialen" PythonRadians = "Zet x om van graden naar radialen"
PythonRandint = "Geeft willek. integer in [a,b]" 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" PythonRandomFunction = "random module voorvoegsel"
PythonRandrange = "Willek. getal in range(start, stop)" PythonRandrange = "Willek. getal in range(start, stop)"
PythonRangeStartStop = "Lijst van start tot stop-1" PythonRangeStartStop = "Lijst van start tot stop-1"
@@ -162,10 +162,10 @@ PythonRect = "z in cartesiaanse coördinaten"
PythonRemove = "Verwijder het eerste voorkomen van x" PythonRemove = "Verwijder het eerste voorkomen van x"
PythonReverse = "Keer de elementen van de lijst om" PythonReverse = "Keer de elementen van de lijst om"
PythonRound = "Rond af op n cijfers" 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" PythonSeed = "Start willek. getallengenerator"
PythonSetPixel = "Kleur pixel (x,y)" PythonSetPixel = "Kleur pixel (x,y)"
PythonShow = "Display the figure" PythonShow = "Figuur weergeven"
PythonSin= "Sinus" PythonSin= "Sinus"
PythonSinh = "Sinus hyperbolicus" PythonSinh = "Sinus hyperbolicus"
PythonSleep = "Stel executie voor t seconden uit" PythonSleep = "Stel executie voor t seconden uit"
@@ -174,7 +174,7 @@ PythonSqrt = "Vierkantswortel"
PythonSum = "Sommeer de items van een lijst" PythonSum = "Sommeer de items van een lijst"
PythonTan = "Tangens" PythonTan = "Tangens"
PythonTanh = "Tangens hyperbolicus" PythonTanh = "Tangens hyperbolicus"
PythonText = "Display a text at (x,y) coordinates" PythonText = "Geef tekst weer op coördinaten (x,y)"
PythonTimeFunction = "time module voorvoegsel" PythonTimeFunction = "time module voorvoegsel"
PythonTrunc = "x afgeknot tot een integer" PythonTrunc = "x afgeknot tot een integer"
PythonTurtleBackward = "Ga achterwaarts met x pixels" PythonTurtleBackward = "Ga achterwaarts met x pixels"
@@ -199,7 +199,7 @@ PythonTurtleSetposition = "Plaats de schildpad"
PythonTurtleShowturtle = "Laat de schildpad zien" PythonTurtleShowturtle = "Laat de schildpad zien"
PythonTurtleSpeed = "Tekensnelheid tussen 0 and 10" PythonTurtleSpeed = "Tekensnelheid tussen 0 and 10"
PythonTurtleWrite = "Display a text" PythonTurtleWrite = "Display a text"
PythonUniform = "Zwevendekommagetal in [a,b]" PythonUniform = "Decimaal getal in [a,b]"
PythonImportTime = "Import time module" PythonImportTime = "Import time module"
PythonTimePrefix = "time module function prefix" PythonTimePrefix = "time module function prefix"
PythonTimeSleep = "Wait for n second" PythonTimeSleep = "Wait for n second"

View File

@@ -30,8 +30,8 @@ PythonColor = "Define uma cor rgb"
PythonColorBlack = "Cor preta" PythonColorBlack = "Cor preta"
PythonColorBlue = "Cor azul" PythonColorBlue = "Cor azul"
PythonColorBrown = "Cor castanha" PythonColorBrown = "Cor castanha"
PythonColorGray = "Cor cinzenta"
PythonColorGreen = "Cor verde" PythonColorGreen = "Cor verde"
PythonColorGrey = "Cor cinzenta"
PythonColorOrange = "Cor laranja" PythonColorOrange = "Cor laranja"
PythonColorPink = "Cor rosa" PythonColorPink = "Cor rosa"
PythonColorPurple = "Cor roxa" PythonColorPurple = "Cor roxa"

View File

@@ -32,8 +32,8 @@ PythonCommandColor = "color(r,g,b)"
PythonCommandColorBlack = "'black'" PythonCommandColorBlack = "'black'"
PythonCommandColorBlue = "'blue'" PythonCommandColorBlue = "'blue'"
PythonCommandColorBrown = "'brown'" PythonCommandColorBrown = "'brown'"
PythonCommandColorGray = "'gray'"
PythonCommandColorGreen = "'green'" PythonCommandColorGreen = "'green'"
PythonCommandColorGrey = "'grey'"
PythonCommandColorOrange = "'orange'" PythonCommandColorOrange = "'orange'"
PythonCommandColorPink = "'pink'" PythonCommandColorPink = "'pink'"
PythonCommandColorPurple = "'purple'" PythonCommandColorPurple = "'purple'"

View File

@@ -23,7 +23,7 @@ void EditorController::setScript(Script script, int scriptIndex) {
m_script = script; m_script = script;
m_scriptIndex = scriptIndex; 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 * storage available space at the end of the current edited script and we set
* its size. * its size.
* *
@@ -36,8 +36,8 @@ void EditorController::setScript(Script script, int scriptIndex) {
* *
* */ * */
size_t newScriptSize = Ion::Storage::sharedStorage()->putAvailableSpaceAtEndOfRecord(m_script); Ion::Storage::sharedStorage()->putAvailableSpaceAtEndOfRecord(m_script);
m_editorView.setText(const_cast<char *>(m_script.content()), newScriptSize - Script::StatusSize()); m_editorView.setText(const_cast<char *>(m_script.content()), m_script.contentSize());
} }
void EditorController::willExitApp() { void EditorController::willExitApp() {

View File

@@ -67,11 +67,19 @@ void EditorView::GutterView::drawRect(KDContext * ctx, KDRect rect) const {
KDCoordinate firstLine = m_offset / glyphSize.height(); KDCoordinate firstLine = m_offset / glyphSize.height();
KDCoordinate firstLinePixelOffset = m_offset - firstLine * glyphSize.height(); KDCoordinate firstLinePixelOffset = m_offset - firstLine * glyphSize.height();
char lineNumber[4]; char lineNumber[k_lineNumberCharLength];
int numberOfLines = bounds().height() / glyphSize.height() + 1; int numberOfLines = bounds().height() / glyphSize.height() + 1;
for (int i=0; i<numberOfLines; i++) { for (int i=0; i<numberOfLines; i++) {
Poincare::Integer line(i + firstLine + 1); // Only the first two digits are displayed
line.serialize(lineNumber, 4); 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(); KDCoordinate leftPadding = (2 - strlen(lineNumber)) * glyphSize.width();
ctx->drawString( ctx->drawString(
lineNumber, lineNumber,

View File

@@ -42,6 +42,7 @@ private:
KDSize minimalSizeForOptimalDisplay() const override; KDSize minimalSizeForOptimalDisplay() const override;
private: private:
static constexpr KDCoordinate k_margin = 2; static constexpr KDCoordinate k_margin = 2;
static constexpr int k_lineNumberCharLength = 3;
const KDFont * m_font; const KDFont * m_font;
KDCoordinate m_offset; KDCoordinate m_offset;
}; };

View File

@@ -1,44 +1,20 @@
#include "helpers.h" #include "helpers.h"
#include <string.h> #include <escher/clipboard.h>
#include <ion/unicode/code_point.h>
#include <ion.h>
namespace Code { namespace Code {
namespace Helpers { 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) { const char * PythonTextForEvent(Ion::Events::Event event) {
for (size_t i=0; i<sizeof(sEventTextMap)/sizeof(sEventTextMap[0]); i++) { for (size_t i=0; i<NumberOfPythonTextPairs; i++) {
if (event == sEventTextMap[i].event()) { UTF8Helper::TextPair pair = PythonTextPairs[i];
return sEventTextMap[i].text(); if (event.text() == pair.firstString()) {
return pair.secondString();
}
if (event == Ion::Events::XNT) {
return "x";
} }
} }
return nullptr; return nullptr;
} }
} }
} }

View File

@@ -440,9 +440,7 @@ void PythonTextArea::addAutocompletion() {
const int scriptIndex = m_contentView.pythonDelegate()->menuController()->editedScriptIndex(); const int scriptIndex = m_contentView.pythonDelegate()->menuController()->editedScriptIndex();
m_contentView.pythonDelegate()->variableBoxController()->loadFunctionsAndVariables(scriptIndex, autocompletionTokenBeginning, autocompletionLocation - autocompletionTokenBeginning); m_contentView.pythonDelegate()->variableBoxController()->loadFunctionsAndVariables(scriptIndex, autocompletionTokenBeginning, autocompletionLocation - autocompletionTokenBeginning);
if (addAutocompletionTextAtIndex(0)) { addAutocompletionTextAtIndex(0);
m_contentView.setAutocompleting(true);
}
} }
bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIndexToUpdate) { bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIndexToUpdate) {
@@ -467,6 +465,7 @@ bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIn
return false; return false;
} }
autocompletionLocation += textToInsertLength; autocompletionLocation += textToInsertLength;
m_contentView.setAutocompleting(true);
m_contentView.setAutocompletionEnd(autocompletionLocation); 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)) { if (addParentheses && m_contentView.insertTextAtLocation(parentheses, const_cast<char *>(autocompletionLocation), parenthesesLength)) {
m_contentView.setAutocompleting(true); m_contentView.setAutocompleting(true);
m_contentView.setAutocompletionEnd(autocompletionLocation + parenthesesLength); m_contentView.setAutocompletionEnd(autocompletionLocation + parenthesesLength);
return true;
} }
return true; return (textToInsertLength > 0);
} }
void PythonTextArea::cycleAutocompletion(bool downwards) { void PythonTextArea::cycleAutocompletion(bool downwards) {

View File

@@ -131,7 +131,7 @@ const ToolboxMessageTree MatplotlibPyplotModuleChildren[] = {
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPink, I18n::Message::PythonColorPink, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPink, I18n::Message::PythonColorPink, false),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorOrange, I18n::Message::PythonColorOrange, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorOrange, I18n::Message::PythonColorOrange, false),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPurple, I18n::Message::PythonColorPurple, 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[] = { const ToolboxMessageTree TurtleModuleChildren[] = {
@@ -168,7 +168,7 @@ const ToolboxMessageTree TurtleModuleChildren[] = {
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPink, I18n::Message::PythonColorPink, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPink, I18n::Message::PythonColorPink, false),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorOrange, I18n::Message::PythonColorOrange, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorOrange, I18n::Message::PythonColorOrange, false),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPurple, I18n::Message::PythonColorPurple, 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[] = { const ToolboxMessageTree RandomModuleChildren[] = {
@@ -341,8 +341,8 @@ const ToolboxMessageTree catalogChildren[] = {
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPixel, I18n::Message::PythonGetPixel), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPixel, I18n::Message::PythonGetPixel),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetrandbits, I18n::Message::PythonGetrandbits), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetrandbits, I18n::Message::PythonGetrandbits),
ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandGoto, I18n::Message::PythonTurtleGoto), 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::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::PythonCommandGrid, I18n::Message::PythonGrid),
ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandHeading, I18n::Message::PythonTurtleHeading, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandHeading, I18n::Message::PythonTurtleHeading, false),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandHex, I18n::Message::PythonHex), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandHex, I18n::Message::PythonHex),

View File

@@ -49,6 +49,7 @@ public:
bool autoImportationStatus() const; bool autoImportationStatus() const;
void toggleAutoimportationStatus(); void toggleAutoimportationStatus();
const char * content() const; const char * content() const;
size_t contentSize() { return value().size - k_statusSize; }
/* Fetched status */ /* Fetched status */
bool fetchedFromConsole() const; bool fetchedFromConsole() const;

View File

@@ -13,7 +13,7 @@ void ScriptNodeCell::ScriptNodeView::drawRect(KDContext * ctx, KDRect rect) cons
// If it exists, draw the description name. // If it exists, draw the description name.
const char * descriptionName = m_scriptNode->description(); const char * descriptionName = m_scriptNode->description();
if (descriptionName != nullptr) { 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 // Draw the node name

View File

@@ -1,6 +1,6 @@
Functions = "Funzioni" Functions = "Funzioni"
Catalog = "Catalogo" Catalog = "Catalogo"
Modules = "Moduli" Modules = "Moduli"
LoopsAndTests = "Loops e test" LoopsAndTests = "Cicli e test"
Files = "Files" Files = "Files"
Exceptions = "Exceptions" Exceptions = "Exceptions"

View File

@@ -1,6 +1,6 @@
Functions = "Functies" Functions = "Functies"
Catalog = "Catalogus" Catalog = "Catalogus"
Modules = "Modules" Modules = "Modules"
LoopsAndTests = "Loops and tests" LoopsAndTests = "Herhalingen en testen"
Files = "Files" Files = "Files"
Exceptions = "Exceptions" Exceptions = "Exceptions"

View File

@@ -45,7 +45,7 @@ VariableBoxController::VariableBoxController(ScriptStore * scriptStore) :
{ {
for (int i = 0; i < k_scriptOriginsCount; i++) { for (int i = 0; i < k_scriptOriginsCount; i++) {
m_subtitleCells[i].setBackgroundColor(Palette::WallScreen); m_subtitleCells[i].setBackgroundColor(Palette::WallScreen);
m_subtitleCells[i].setTextColor(Palette::BlueishGrey); m_subtitleCells[i].setTextColor(Palette::SecondaryText);
} }
} }

View 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
1 CountryCode CountryPreferences::AvailableExamModes CountryPreferences::MethodForQuartiles Poincare::Preferences::UnitFormat CountryPreferences::HomeAppsLayout
2 WW StandardOnly MedianOfSublist Metric Default
3 CA StandardOnly MedianOfSublist Metric Default
4 DE StandardOnly MedianOfSublist Metric Default
5 ES StandardOnly MedianOfSublist Metric Default
6 FR StandardOnly CumulatedFrequency Metric Default
7 GB StandardOnly MedianOfSublist Metric Default
8 IT StandardOnly CumulatedFrequency Metric Default
9 NL All MedianOfSublist Metric Default
10 PT StandardOnly MedianOfSublist Metric Default
11 US StandardOnly MedianOfSublist Imperial HidePython

View 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

View File

@@ -18,7 +18,7 @@ I18n::Message examModeActivationWarningMessage(GlobalPreferences::ExamMode mode,
// Exam mode behaviour // Exam mode behaviour
KDColor examModeColor(GlobalPreferences::ExamMode mode); 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); bool exactExpressionsAreForbidden(GlobalPreferences::ExamMode mode);
} }

View File

@@ -1,16 +1,13 @@
#include "exam_mode_configuration.h" #include "exam_mode_configuration.h"
using namespace Poincare; constexpr Shared::SettingsMessageTree ExamModeConfiguration::s_modelExamChildren[] = {Shared::SettingsMessageTree(I18n::Message::ActivateExamMode), Shared::SettingsMessageTree(I18n::Message::Default)};
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)};
int ExamModeConfiguration::numberOfAvailableExamMode() { int ExamModeConfiguration::numberOfAvailableExamMode() {
return 2; return 1;
} }
GlobalPreferences::ExamMode ExamModeConfiguration::examModeAtIndex(int index) { 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) { 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}; I18n::Message warnings[] = {I18n::Message::ExitExamMode1, I18n::Message::ExitExamMode2, I18n::Message::Default};
return warnings[line]; 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}; I18n::Message warnings[] = {I18n::Message::ActiveExamModeMessage1, I18n::Message::ActiveExamModeMessage2, I18n::Message::ActiveExamModeMessage3};
return warnings[line]; return warnings[line];
} }
KDColor ExamModeConfiguration::examModeColor(GlobalPreferences::ExamMode mode) { 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; return KDColorRed;
} }
bool ExamModeConfiguration::appIsForbiddenInExamMode(App::Descriptor::ExaminationLevel appExaminationLevel, GlobalPreferences::ExamMode mode) { bool ExamModeConfiguration::appIsForbiddenInExamMode(I18n::Message appName, GlobalPreferences::ExamMode mode) {
if (mode == GlobalPreferences::ExamMode::NoSymNoText) {
return appExaminationLevel == App::Descriptor::ExaminationLevel::Basic;
}
return false; return false;
} }
bool ExamModeConfiguration::exactExpressionsAreForbidden(GlobalPreferences::ExamMode mode) { bool ExamModeConfiguration::exactExpressionsAreForbidden(GlobalPreferences::ExamMode mode) {
return mode == GlobalPreferences::ExamMode::NoSymNoText ? true : false; return false;
} }

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

View File

@@ -1,16 +1,32 @@
#include "exam_pop_up_controller.h" #include "exam_pop_up_controller.h"
#include "apps_container.h" #include "apps_container.h"
#include "exam_mode_configuration.h" #include "exam_mode_configuration.h"
#include <apps/i18n.h>
#include "global_preferences.h"
#include <assert.h> #include <assert.h>
#include <poincare/preferences.h> #include <poincare/preferences.h>
using namespace Poincare; using namespace Poincare;
ExamPopUpController::ExamPopUpController(ExamPopUpControllerDelegate * delegate) : ExamPopUpController::ExamPopUpController(ExamPopUpControllerDelegate * delegate) :
ViewController(nullptr), PopUpController(
m_contentView(this), 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_targetExamMode(GlobalPreferences::ExamMode::Unknown),
m_delegate(delegate) m_delegate(delegate)
{ {
@@ -18,11 +34,9 @@ ExamPopUpController::ExamPopUpController(ExamPopUpControllerDelegate * delegate)
void ExamPopUpController::setTargetExamMode(GlobalPreferences::ExamMode mode) { void ExamPopUpController::setTargetExamMode(GlobalPreferences::ExamMode mode) {
m_targetExamMode = mode; m_targetExamMode = mode;
m_contentView.setMessagesForExamMode(mode); for (int i = 0; i < k_numberOfLines; i++) {
} m_contentView.setMessage(i, ExamModeConfiguration::examModeActivationWarningMessage(mode, i));
}
View * ExamPopUpController::view() {
return &m_contentView;
} }
void ExamPopUpController::viewDidDisappear() { void ExamPopUpController::viewDidDisappear() {
@@ -30,103 +44,3 @@ void ExamPopUpController::viewDidDisappear() {
m_delegate->examDeactivatingPopUpIsDismissed(); 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);
}

View File

@@ -1,53 +1,20 @@
#ifndef APPS_EXAM_POP_UP_CONTROLLER_H #ifndef APPS_EXAM_POP_UP_CONTROLLER_H
#define 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 "exam_pop_up_controller_delegate.h"
#include "global_preferences.h" #include "global_preferences.h"
class HighContrastButton : public Button { class ExamPopUpController : public PopUpController {
public:
using Button::Button;
KDColor highlightedBackgroundColor() const override { return Palette::ButtonBackgroundSelectedHighContrast; }
};
class ExamPopUpController : public ViewController {
public: public:
ExamPopUpController(ExamPopUpControllerDelegate * delegate); ExamPopUpController(ExamPopUpControllerDelegate * delegate);
void setTargetExamMode(GlobalPreferences::ExamMode mode); void setTargetExamMode(GlobalPreferences::ExamMode mode);
GlobalPreferences::ExamMode targetExamMode() const { return m_targetExamMode; } GlobalPreferences::ExamMode targetExamMode() const { return m_targetExamMode; }
// View Controller
View * view() override;
void viewDidDisappear() override; void viewDidDisappear() override;
// Responder
void didBecomeFirstResponder() override;
bool handleEvent(Ion::Events::Event event) override;
private: private:
class ContentView : public View { constexpr static int k_numberOfLines = 3;
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;
GlobalPreferences::ExamMode m_targetExamMode; GlobalPreferences::ExamMode m_targetExamMode;
ExamPopUpControllerDelegate * m_delegate; ExamPopUpControllerDelegate * m_delegate;
}; };
#endif #endif

View File

@@ -16,6 +16,12 @@ public:
static GlobalPreferences * sharedGlobalPreferences(); static GlobalPreferences * sharedGlobalPreferences();
I18n::Language language() const { return m_language; } I18n::Language language() const { return m_language; }
void setLanguage(I18n::Language language) { m_language = 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 isInExamMode() const { return (int8_t)examMode() > 0; }
bool isInExamModeSymbolic() const { return !((int8_t)examMode() > 1); } bool isInExamModeSymbolic() const { return !((int8_t)examMode() > 1); }
ExamMode examMode() const; ExamMode examMode() const;
@@ -30,15 +36,18 @@ public:
void setFont(const KDFont * font) { m_font = font; } void setFont(const KDFont * font) { m_font = font; }
constexpr static int NumberOfBrightnessStates = 15; constexpr static int NumberOfBrightnessStates = 15;
private: 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() : GlobalPreferences() :
m_language((I18n::Language)0), m_language((I18n::Language)0),
m_country((I18n::Country)0),
m_examMode(ExamMode::Unknown), m_examMode(ExamMode::Unknown),
m_tempExamMode(ExamMode::Standard), m_tempExamMode(ExamMode::Standard),
m_showPopUp(true), m_showPopUp(true),
m_brightnessLevel(Ion::Backlight::MaxBrightness), m_brightnessLevel(Ion::Backlight::MaxBrightness),
m_font(KDFont::LargeFont) {} m_font(KDFont::LargeFont) {}
I18n::Language m_language; 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::Off == 0, "GlobalPreferences::isInExamMode() is not right");
static_assert((int8_t)GlobalPreferences::ExamMode::Unknown < 0, "GlobalPreferences::isInExamMode() is not right"); static_assert((int8_t)GlobalPreferences::ExamMode::Unknown < 0, "GlobalPreferences::isInExamMode() is not right");
mutable ExamMode m_examMode; mutable ExamMode m_examMode;

View File

@@ -1,9 +1,12 @@
apps += Graph::App apps += Graph::App
app_headers += apps/graph/app.h app_headers += apps/graph/app.h
app_graph_test_src = $(addprefix apps/graph/,\
continuous_function_store.cpp \
)
app_graph_src = $(addprefix apps/graph/,\ app_graph_src = $(addprefix apps/graph/,\
app.cpp \ app.cpp \
continuous_function_store.cpp \
graph/banner_view.cpp \ graph/banner_view.cpp \
graph/calculation_graph_controller.cpp \ graph/calculation_graph_controller.cpp \
graph/calculation_parameter_controller.cpp \ graph/calculation_parameter_controller.cpp \
@@ -31,8 +34,15 @@ app_graph_src = $(addprefix apps/graph/,\
values/values_controller.cpp \ values/values_controller.cpp \
) )
app_graph_src += $(app_graph_test_src)
apps_src += $(app_graph_src) apps_src += $(app_graph_src)
i18n_files += $(call i18n_without_universal_for,graph/base) 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)) $(eval $(call depends_on_image,apps/graph/app.cpp,apps/graph/graph_icon.png))

View File

@@ -55,10 +55,10 @@ void App::Snapshot::tidy() {
App::App(Snapshot * snapshot) : App::App(Snapshot * snapshot) :
FunctionApp(snapshot, &m_inputViewController), FunctionApp(snapshot, &m_inputViewController),
m_listController(&m_listFooter, &m_listHeader, &m_listFooter, this), 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_listHeader(&m_listStackViewController, &m_listFooter, &m_listController),
m_listStackViewController(&m_tabViewController, &m_listHeader), 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_graphAlternateEmptyViewController(&m_graphHeader, &m_graphController, &m_graphController),
m_graphHeader(&m_graphStackViewController, &m_graphAlternateEmptyViewController, &m_graphController), m_graphHeader(&m_graphStackViewController, &m_graphAlternateEmptyViewController, &m_graphController),
m_graphStackViewController(&m_tabViewController, &m_graphHeader), m_graphStackViewController(&m_tabViewController, &m_graphHeader),

View File

@@ -12,22 +12,22 @@ IntervalTheta = "θ interval"
IntervalX = "x interval" IntervalX = "x interval"
FunctionDomain = "Plotbereik" FunctionDomain = "Plotbereik"
FunctionColor = "Functiekleur" FunctionColor = "Functiekleur"
NoFunction = "Geen functie" NoFunction = "Geen functie gedefinieerd"
NoActivatedFunction = "Geen functie is ingeschakelt" NoActivatedFunction = "Geen functie ingeschakeld"
PlotOptions = "Plot opties" PlotOptions = "Plotopties"
Compute = "Bereken" Compute = "Bereken"
Zeros = "Nulpunten" Zeros = "Nulpunten"
Tangent = "Tangens" Tangent = "Raaklijn"
Intersection = "Snijpunt" Intersection = "Snijpunt"
Preimage = "Inverse beeld" Preimage = "Origineel"
SelectLowerBound = "Selecteer ondergrens " SelectLowerBound = "Selecteer ondergrens "
SelectUpperBound = "Selecteer bovengrens " SelectUpperBound = "Selecteer bovengrens "
NoMaximumFound = "Geen maximum gevonden" NoMaximumFound = "Geen maximum gevonden"
NoMinimumFound = "Geen minimum gevonden" NoMinimumFound = "Geen minimum gevonden"
NoZeroFound = "Geen nulpunt gevonden" NoZeroFound = "Geen nulpunt gevonden"
NoIntersectionFound = "Geen snijpunt gevonden" NoIntersectionFound = "Geen snijpunt gevonden"
NoPreimageFound = "Geen inverse beeld gevonden" NoPreimageFound = "Geen origineel gevonden"
DerivativeFunctionColumn = "Afgeleide functie kolom" DerivativeFunctionColumn = "Afgeleide functie kolom"
HideDerivativeColumn = "Verberg de afgeleide functie" HideDerivativeColumn = "Verberg de afgeleide functie"
AllowedCharactersAZaz09 = "Toegestane tekens: A-Z, a-z, 0-9, _" AllowedCharactersAZaz09 = "Toegestane tekens: A-Z, a-z, 0-9, _"
ReservedName = "Voorbehouden naam" ReservedName = "Gereserveerde naam"

View File

@@ -16,8 +16,9 @@ public:
return recordSatisfyingTestAtIndex(i, &isFunctionActiveOfType, &plotType); 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))); } 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; Ion::Storage::Record::ErrorStatus addEmptyModel() override;
private:
const char * modelExtension() const override { return Ion::Storage::funcExtension; } const char * modelExtension() const override { return Ion::Storage::funcExtension; }
Shared::ExpressionModelHandle * setMemoizedModelAtIndex(int cacheIndex, Ion::Storage::Record record) const override; Shared::ExpressionModelHandle * setMemoizedModelAtIndex(int cacheIndex, Ion::Storage::Record record) const override;
Shared::ExpressionModelHandle * memoizedModelAtIndex(int cacheIndex) 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(); return isFunctionActive(model, context) && plotType == static_cast<Shared::ContinuousFunction *>(model)->plotType();
} }
mutable Shared::ContinuousFunction m_functions[k_maxNumberOfMemoizedModels]; mutable Shared::ContinuousFunction m_functions[k_maxNumberOfMemoizedModels];
mutable Shared::ContinuousFunctionCache m_functionCaches[Shared::ContinuousFunctionCache::k_numberOfAvailableCaches];
}; };
} }

View File

@@ -30,7 +30,7 @@ void CalculationGraphController::viewWillAppear() {
m_isActive = true; m_isActive = true;
assert(App::app()->functionStore()->modelForRecord(m_record)->plotType() == Shared::ContinuousFunction::PlotType::Cartesian); assert(App::app()->functionStore()->modelForRecord(m_record)->plotType() == Shared::ContinuousFunction::PlotType::Cartesian);
m_cursor->moveTo(pointOfInterest.x1(), pointOfInterest.x1(), pointOfInterest.x2()); 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); m_bannerView->setNumberOfSubviews(Shared::XYBannerView::k_numberOfSubviews);
reloadBannerView(); reloadBannerView();
} }
@@ -64,7 +64,7 @@ bool CalculationGraphController::handleEnter() {
return true; return true;
} }
bool CalculationGraphController::moveCursorHorizontally(int direction, bool fast) { bool CalculationGraphController::moveCursorHorizontally(int direction, int scrollspeed) {
if (!m_isActive) { if (!m_isActive) {
return false; return false;
} }

View File

@@ -31,7 +31,7 @@ protected:
bool m_isActive; bool m_isActive;
private: private:
bool handleEnter() override; 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::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; }
Shared::CurveView * curveView() override { return m_graphView; } Shared::CurveView * curveView() override { return m_graphView; }
}; };

View File

@@ -12,6 +12,9 @@ public:
TELEMETRY_ID("Minimum"); TELEMETRY_ID("Minimum");
private: private:
Poincare::Coordinate2D<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; Poincare::Coordinate2D<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override;
// Prevent horizontal panning to preserve search interval
float cursorRightMarginRatio() override { return 0.0f; }
float cursorLeftMarginRatio() override { return 0.0f; }
}; };
class MaximumGraphController : public CalculationGraphController { class MaximumGraphController : public CalculationGraphController {
@@ -21,6 +24,9 @@ public:
TELEMETRY_ID("Maximum"); TELEMETRY_ID("Maximum");
private: private:
Poincare::Coordinate2D<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; Poincare::Coordinate2D<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override;
// Prevent horizontal panning to preserve search interval
float cursorRightMarginRatio() override { return 0.0f; }
float cursorLeftMarginRatio() override { return 0.0f; }
}; };
} }

View File

@@ -7,8 +7,8 @@ using namespace Shared;
namespace Graph { 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) : 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, modelVersion, previousModelsVersions, rangeVersion, angleUnitVersion), FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, curveViewRange, &m_view, cursor, indexFunctionSelectedByCursor, rangeVersion),
m_bannerView(this, inputEventHandlerDelegate, this), m_bannerView(this, inputEventHandlerDelegate, this),
m_view(curveViewRange, m_cursor, &m_bannerView, &m_cursorView), m_view(curveViewRange, m_cursor, &m_bannerView, &m_cursorView),
m_graphRange(curveViewRange), m_graphRange(curveViewRange),
@@ -35,118 +35,10 @@ void GraphController::viewWillAppear() {
selectFunctionWithCursor(indexFunctionSelectedByCursor()); selectFunctionWithCursor(indexFunctionSelectedByCursor());
} }
bool GraphController::defautRangeIsNormalized() const { bool GraphController::defaultRangeIsNormalized() const {
return functionStore()->displaysNonCartesianFunctions(); 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) { void GraphController::selectFunctionWithCursor(int functionIndex) {
FunctionGraphController::selectFunctionWithCursor(functionIndex); FunctionGraphController::selectFunctionWithCursor(functionIndex);
ExpiringPointer<ContinuousFunction> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(functionIndex)); ExpiringPointer<ContinuousFunction> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(functionIndex));
@@ -165,9 +57,9 @@ void GraphController::reloadBannerView() {
reloadDerivativeInBannerViewForCursorOnFunction(m_cursor, record); 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()); 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 { 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(); return function->tMin();
} }
bool GraphController::shouldSetDefaultOnModelChange() const {
return functionStore()->displaysNonCartesianFunctions();
}
void GraphController::jumpToLeftRightCurve(double t, int direction, int functionsCount, Ion::Storage::Record record) { void GraphController::jumpToLeftRightCurve(double t, int direction, int functionsCount, Ion::Storage::Record record) {
if (functionsCount == 1) { if (functionsCount == 1) {
return; return;

View File

@@ -15,28 +15,25 @@ namespace Graph {
class GraphController : public Shared::FunctionGraphController, public GraphControllerHelper { class GraphController : public Shared::FunctionGraphController, public GraphControllerHelper {
public: 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; I18n::Message emptyMessage() override;
void viewWillAppear() override; void viewWillAppear() override;
bool displayDerivativeInBanner() const { return m_displayDerivativeInBanner; } bool displayDerivativeInBanner() const { return m_displayDerivativeInBanner; }
void setDisplayDerivativeInBanner(bool displayDerivative) { m_displayDerivativeInBanner = displayDerivative; } void setDisplayDerivativeInBanner(bool displayDerivative) { m_displayDerivativeInBanner = displayDerivative; }
float interestingXHalfRange() const override;
void interestingRanges(float * xm, float * xM, float * ym, float * yM) const override;
private: private:
int estimatedBannerNumberOfLines() const override { return 1 + m_displayDerivativeInBanner; } int estimatedBannerNumberOfLines() const override { return 1 + m_displayDerivativeInBanner; }
void selectFunctionWithCursor(int functionIndex) override; void selectFunctionWithCursor(int functionIndex) override;
BannerView * bannerView() override { return &m_bannerView; } BannerView * bannerView() override { return &m_bannerView; }
void reloadBannerView() override; 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; int nextCurveIndexVertically(bool goingUp, int currentSelectedCurve, Poincare::Context * context) const override;
double defaultCursorT(Ion::Storage::Record record) override; double defaultCursorT(Ion::Storage::Record record) override;
Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; } Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; }
GraphView * functionGraphView() override { return &m_view; } GraphView * functionGraphView() override { return &m_view; }
CurveParameterController * curveParameterController() override { return &m_curveParameterController; } CurveParameterController * curveParameterController() override { return &m_curveParameterController; }
ContinuousFunctionStore * functionStore() const override { return static_cast<ContinuousFunctionStore *>(Shared::FunctionGraphController::functionStore()); } 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; 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; void jumpToLeftRightCurve(double t, int direction, int functionsCount, Ion::Storage::Record record) override;
Shared::RoundCursorView m_cursorView; Shared::RoundCursorView m_cursorView;

View File

@@ -10,7 +10,7 @@ using namespace Poincare;
namespace Graph { 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); ExpiringPointer<ContinuousFunction> function = App::app()->functionStore()->modelForRecord(record);
double tCursorPosition = cursor->t(); double tCursorPosition = cursor->t();
double t = tCursorPosition; double t = tCursorPosition;
@@ -27,11 +27,11 @@ bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCurso
function = App::app()->functionStore()->modelForRecord(record); // Reload the expiring pointer function = App::app()->functionStore()->modelForRecord(record); // Reload the expiring pointer
double dir = (direction > 0 ? 1.0 : -1.0); double dir = (direction > 0 ? 1.0 : -1.0);
double step = function->plotType() == ContinuousFunction::PlotType::Cartesian ? range->xGridUnit()/numberOfStepsInGradUnit : (tMax-tMin)/k_definitionDomainDivisor; double step = function->plotType() == ContinuousFunction::PlotType::Cartesian ? range->xGridUnit()/numberOfStepsInGradUnit : (tMax-tMin)/k_definitionDomainDivisor;
if (fast) { t += dir * step * scrollSpeed;
// TODO Navigate quicker the longer the user presses? (slow start)
step *= 5.0; // 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 += dir * step;
t = std::max(tMin, std::min(tMax, t)); t = std::max(tMin, std::min(tMax, t));
Coordinate2D<double> xy = function->evaluateXYAtParameter(t, App::app()->localContext()); Coordinate2D<double> xy = function->evaluateXYAtParameter(t, App::app()->localContext());
cursor->moveTo(t, xy.x1(), xy.x2()); cursor->moveTo(t, xy.x1(), xy.x2());

View File

@@ -11,7 +11,7 @@ class App;
class GraphControllerHelper { class GraphControllerHelper {
protected: 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); void reloadDerivativeInBannerViewForCursorOnFunction(Shared::CurveViewCursor * cursor, Ion::Storage::Record record);
virtual BannerView * bannerView() = 0; virtual BannerView * bannerView() = 0;
private: private:

View File

@@ -1,6 +1,7 @@
#include "graph_view.h" #include "graph_view.h"
#include "../app.h" #include "../app.h"
#include <assert.h> #include <assert.h>
#include <algorithm>
using namespace Shared; using namespace Shared;
@@ -27,7 +28,8 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const {
const int activeFunctionsCount = functionStore->numberOfActiveFunctions(); const int activeFunctionsCount = functionStore->numberOfActiveFunctions();
for (int i = 0; i < activeFunctionsCount ; i++) { for (int i = 0; i < activeFunctionsCount ; i++) {
Ion::Storage::Record record = functionStore->activeRecordAtIndex(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(); Shared::ContinuousFunction::PlotType type = f->plotType();
Poincare::Expression e = f->expressionReduced(context()); Poincare::Expression e = f->expressionReduced(context());
if (e.isUndefined() || ( if (e.isUndefined() || (
@@ -38,21 +40,24 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const {
} }
float tmin = f->tMin(); float tmin = f->tMin();
float tmax = f->tMax(); 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) { if (type == Shared::ContinuousFunction::PlotType::Cartesian) {
// Cartesian
drawCartesianCurve(ctx, rect, tmin, tmax, [](float t, void * model, void * context) { drawCartesianCurve(ctx, rect, tmin, tmax, [](float t, void * model, void * context) {
ContinuousFunction * f = (ContinuousFunction *)model; ContinuousFunction * f = (ContinuousFunction *)model;
Poincare::Context * c = (Poincare::Context *)context; 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()); float maxAbscissa = pixelToFloat(Axis::Horizontal, rect.right());
drawSegment(ctx, rect, minAbscissa, tangentParameterA*minAbscissa+tangentParameterB, maxAbscissa, tangentParameterA*maxAbscissa+tangentParameterB, Palette::GraphTangent, false); 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());
} }
} }

View File

@@ -7,6 +7,19 @@ namespace Graph {
class GraphView : public Shared::FunctionGraphView { class GraphView : public Shared::FunctionGraphView {
public: 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, GraphView(Shared::InteractiveCurveViewRange * graphRange,
Shared::CurveViewCursor * cursor, Shared::BannerView * bannerView, Shared::CursorView * cursorView); Shared::CurveViewCursor * cursor, Shared::BannerView * bannerView, Shared::CursorView * cursorView);
void reload() override; void reload() override;

View File

@@ -13,6 +13,9 @@ private:
void reloadBannerView() override; void reloadBannerView() override;
Poincare::Coordinate2D<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; Poincare::Coordinate2D<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override;
Ion::Storage::Record m_intersectedRecord; Ion::Storage::Record m_intersectedRecord;
// Prevent horizontal panning to preserve search interval
float cursorRightMarginRatio() override { return 0.0f; }
float cursorLeftMarginRatio() override { return 0.0f; }
}; };
} }

View File

@@ -12,6 +12,9 @@ public:
TELEMETRY_ID("Root"); TELEMETRY_ID("Root");
private: private:
Poincare::Coordinate2D<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; Poincare::Coordinate2D<double> computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override;
// Prevent horizontal panning to preserve search interval
float cursorRightMarginRatio() override { return 0.0f; }
float cursorLeftMarginRatio() override { return 0.0f; }
}; };
} }

View File

@@ -24,7 +24,7 @@ const char * TangentGraphController::title() {
void TangentGraphController::viewWillAppear() { void TangentGraphController::viewWillAppear() {
Shared::SimpleInteractiveCurveViewController::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->drawTangent(true);
m_graphView->setOkView(nullptr); m_graphView->setOkView(nullptr);
m_graphView->selectMainView(true); m_graphView->selectMainView(true);
@@ -51,7 +51,7 @@ bool TangentGraphController::textFieldDidFinishEditing(TextField * textField, co
assert(function->plotType() == Shared::ContinuousFunction::PlotType::Cartesian); assert(function->plotType() == Shared::ContinuousFunction::PlotType::Cartesian);
double y = function->evaluate2DAtParameter(floatBody, myApp->localContext()).x2(); double y = function->evaluate2DAtParameter(floatBody, myApp->localContext()).x2();
m_cursor->moveTo(floatBody, floatBody, y); 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(); reloadBannerView();
curveView()->reload(); curveView()->reload();
return true; return true;
@@ -90,7 +90,7 @@ void TangentGraphController::reloadBannerView() {
m_bannerView->reload(); 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); return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, m_record);
} }

View File

@@ -24,7 +24,7 @@ private:
Shared::CurveView * curveView() override { return m_graphView; } Shared::CurveView * curveView() override { return m_graphView; }
BannerView * bannerView() override { return m_bannerView; }; BannerView * bannerView() override { return m_bannerView; };
void reloadBannerView() override; void reloadBannerView() override;
bool moveCursorHorizontally(int direction, bool fast = false) override; bool moveCursorHorizontally(int direction, int scrollSpeed = 1) override;
bool handleEnter() override; bool handleEnter() override;
GraphView * m_graphView; GraphView * m_graphView;
BannerView * m_bannerView; BannerView * m_bannerView;

View File

@@ -10,7 +10,13 @@ namespace Graph {
DomainParameterController::DomainParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) : DomainParameterController::DomainParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) :
FloatParameterController<float>(parentResponder), FloatParameterController<float>(parentResponder),
m_domainCells{}, 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++) { for (int i = 0; i < k_totalNumberOfCell; i++) {
m_domainCells[i].setParentResponder(&m_selectableTableView); m_domainCells[i].setParentResponder(&m_selectableTableView);
@@ -18,12 +24,10 @@ DomainParameterController::DomainParameterController(Responder * parentResponder
} }
} }
const char * DomainParameterController::title() { void DomainParameterController::viewWillAppear() {
return I18n::translate(I18n::Message::FunctionDomain); // Initialize m_tempParameters to the extracted value.
} extractParameters();
FloatParameterController::viewWillAppear();
int DomainParameterController::numberOfRows() const {
return k_totalNumberOfCell+1;
} }
void DomainParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { void DomainParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) {
@@ -56,10 +60,6 @@ void DomainParameterController::willDisplayCellForIndex(HighlightCell * cell, in
FloatParameterController::willDisplayCellForIndex(cell, index); FloatParameterController::willDisplayCellForIndex(cell, index);
} }
int DomainParameterController::reusableParameterCellCount(int type) {
return k_totalNumberOfCell;
}
HighlightCell * DomainParameterController::reusableParameterCell(int index, int type) { HighlightCell * DomainParameterController::reusableParameterCell(int index, int type) {
assert(index >= 0 && index < k_totalNumberOfCell); assert(index >= 0 && index < k_totalNumberOfCell);
return &m_domainCells[index]; return &m_domainCells[index];
@@ -70,20 +70,49 @@ bool DomainParameterController::handleEvent(Ion::Events::Event event) {
stackController()->pop(); stackController()->pop();
return true; 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; return false;
} }
float DomainParameterController::parameterAtIndex(int index) { 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) { bool DomainParameterController::setParameterAtIndex(int parameterIndex, float f) {
// TODO: what to do if the xmin > xmax? /* Setting Min (or Max) parameter can alter the previously set Max
parameterIndex == 0 ? function()->setTMin(f) : function()->setTMax(f); * (or Min) parameter if Max <= Min. */
parameterIndex == 0 ? m_tempDomain.setMin(f) : m_tempDomain.setMax(f);
return true; 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() { void DomainParameterController::buttonAction() {
confirmParameters();
StackViewController * stack = stackController(); StackViewController * stack = stackController();
stack->pop(); stack->pop();
stack->pop(); stack->pop();

View File

@@ -6,6 +6,8 @@
#include "../../shared/continuous_function.h" #include "../../shared/continuous_function.h"
#include "../../shared/expiring_pointer.h" #include "../../shared/expiring_pointer.h"
#include "../../shared/float_parameter_controller.h" #include "../../shared/float_parameter_controller.h"
#include "../../shared/range_1D.h"
#include "../../shared/discard_pop_up_controller.h"
namespace Graph { namespace Graph {
@@ -14,17 +16,18 @@ public:
DomainParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate); DomainParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate);
// ViewController // ViewController
const char * title() override; const char * title() override { return I18n::translate(I18n::Message::FunctionDomain); }
TELEMETRY_ID("DomainParameter"); TELEMETRY_ID("DomainParameter");
// ListViewDataSource // ListViewDataSource
int numberOfRows() const override; int numberOfRows() const override { return k_totalNumberOfCell+1; }
void willDisplayCellForIndex(HighlightCell * cell, int index) override; void willDisplayCellForIndex(HighlightCell * cell, int index) override;
void setRecord(Ion::Storage::Record record) { m_record = record; } void setRecord(Ion::Storage::Record record) { m_record = record; }
private: private:
constexpr static int k_totalNumberOfCell = 2; 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; HighlightCell * reusableParameterCell(int index, int type) override;
bool handleEvent(Ion::Events::Event event) override; bool handleEvent(Ion::Events::Event event) override;
bool setParameterAtIndex(int parameterIndex, float f) override; bool setParameterAtIndex(int parameterIndex, float f) override;
@@ -32,8 +35,16 @@ private:
void buttonAction() override; void buttonAction() override;
InfinityTolerance infinityAllowanceForRow(int row) const override; InfinityTolerance infinityAllowanceForRow(int row) const override;
Shared::ExpiringPointer<Shared::ContinuousFunction> function() const; 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]; MessageTableCellWithEditableText m_domainCells[k_totalNumberOfCell];
Ion::Storage::Record m_record; Ion::Storage::Record m_record;
Shared::Range1D m_tempDomain;
Shared::DiscardPopUpController m_confirmPopUpController;
}; };
} }

View File

@@ -8,7 +8,8 @@ namespace Graph {
TextFieldFunctionTitleCell::TextFieldFunctionTitleCell(ListController * listController, Orientation orientation, const KDFont * font) : TextFieldFunctionTitleCell::TextFieldFunctionTitleCell(ListController * listController, Orientation orientation, const KDFont * font) :
Shared::FunctionTitleCell(orientation), Shared::FunctionTitleCell(orientation),
Responder(listController), 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()); m_textField.setAlignment(horizontalAlignment, verticalAlignment());
} }
void TextFieldFunctionTitleCell::reloadCell() {
layoutSubviews();
FunctionTitleCell::reloadCell();
}
void TextFieldFunctionTitleCell::didBecomeFirstResponder() { void TextFieldFunctionTitleCell::didBecomeFirstResponder() {
if (isEditing()) { if (isEditing()) {
Container::activeApp()->setFirstResponder(&m_textField); Container::activeApp()->setFirstResponder(&m_textField);

View File

@@ -35,6 +35,7 @@ public:
return &m_textField; return &m_textField;
} }
void layoutSubviews(bool force = false) override; void layoutSubviews(bool force = false) override;
void reloadCell() override;
// Responder // Responder
void didBecomeFirstResponder() override; void didBecomeFirstResponder() override;

124
apps/graph/test/caching.cpp Normal file
View 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);
}
}

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

View File

@@ -11,12 +11,13 @@
#include "led_test_controller.h" #include "led_test_controller.h"
#include "serial_number_controller.h" #include "serial_number_controller.h"
#include "vblank_test_controller.h" #include "vblank_test_controller.h"
#include "../shared/shared_app.h"
namespace HardwareTest { namespace HardwareTest {
class App : public ::App { class App : public ::App {
public: public:
class Snapshot : public ::App::Snapshot { class Snapshot : public ::SharedApp::Snapshot {
public: public:
App * unpack(Container * container) override; App * unpack(Container * container) override;
Descriptor * descriptor() override; Descriptor * descriptor() override;

View File

@@ -1,116 +1,25 @@
#include "pop_up_controller.h" #include "pop_up_controller.h"
#include <apps/i18n.h>
#include "../apps_container.h" #include "../apps_container.h"
#include <assert.h>
#include <escher/app.h>
namespace HardwareTest { namespace HardwareTest {
PopUpController::PopUpController() : PopUpController::PopUpController() :
ViewController(nullptr), ::PopUpController(
m_contentView(this) 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)
)
{ {
} m_contentView.setMessage(0, I18n::Message::HardwareTestLaunch1);
m_contentView.setMessage(1, I18n::Message::HardwareTestLaunch2);
View * PopUpController::view() { m_contentView.setMessage(2, I18n::Message::HardwareTestLaunch3);
return &m_contentView; m_contentView.setMessage(3, I18n::Message::HardwareTestLaunch4);
}
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);
} }
} }

View File

@@ -1,43 +1,15 @@
#ifndef HARDWARE_TEST_POP_UP_CONTROLLER_H #ifndef POP_UP_CONTROLLER_H
#define HARDWARE_TEST_POP_UP_CONTROLLER_H #define POP_UP_CONTROLLER_H
#include <escher.h> #include <escher/pop_up_controller.h>
namespace HardwareTest { namespace HardwareTest {
class PopUpController : public ViewController { class PopUpController : public ::PopUpController {
public: public:
PopUpController(); 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 #endif

View File

@@ -1,9 +1,28 @@
app_home_src = $(addprefix apps/home/,\ app_home_src = $(addprefix apps/home/,\
app.cpp \ app.cpp \
app_cell.cpp \ app_cell.cpp \
apps_layout.py \
controller.cpp \ controller.cpp \
) )
apps_src += $(app_home_src) apps_src += $(app_home_src)
i18n_files += $(call i18n_without_universal_for,home/base) 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

View File

@@ -3,6 +3,7 @@
#include <escher.h> #include <escher.h>
#include "controller.h" #include "controller.h"
#include "../shared/shared_app.h"
namespace Home { namespace Home {
@@ -13,7 +14,7 @@ public:
I18n::Message name() override; I18n::Message name() override;
I18n::Message upperName() override; I18n::Message upperName() override;
}; };
class Snapshot : public ::App::Snapshot, public SelectableTableViewDataSource { class Snapshot : public ::SharedApp::Snapshot, public SelectableTableViewDataSource {
public: public:
App * unpack(Container * container) override; App * unpack(Container * container) override;
Descriptor * descriptor() override; Descriptor * descriptor() override;

View 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
1 Default calculation rpn graph code statistics probability solver atom sequence regression settings
2 HidePython calculation rpn graph code statistics probability solver atom sequence regression settings

90
apps/home/apps_layout.py Normal file
View 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)

View File

@@ -1,4 +1,4 @@
Apps = "Aplicaciones" Apps = "Aplicaciones"
AppsCapital = "OMEGA" AppsCapital = "OMEGA"
ForbidenAppInExamMode1 = "Esta aplicación está" ForbidenAppInExamMode1 = "Esta aplicación está prohibida"
ForbidenAppInExamMode2 = "prohibida en el modo examen" ForbidenAppInExamMode2 = "en el modo de examen"

View File

@@ -1,5 +1,6 @@
#include "controller.h" #include "controller.h"
#include "app.h" #include "app.h"
#include <apps/home/apps_layout.h>
#include "../apps_container.h" #include "../apps_container.h"
#include "../global_preferences.h" #include "../global_preferences.h"
#include "../exam_mode_configuration.h" #include "../exam_mode_configuration.h"
@@ -67,7 +68,6 @@ Controller::Controller(Responder * parentResponder, SelectableTableViewDataSourc
bool Controller::handleEvent(Ion::Events::Event event) { bool Controller::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::OK || event == Ion::Events::EXE) { if (event == Ion::Events::OK || event == Ion::Events::EXE) {
AppsContainer * container = AppsContainer::sharedAppsContainer(); AppsContainer * container = AppsContainer::sharedAppsContainer();
int index = selectionDataSource()->selectedRow()*k_numberOfColumns+selectionDataSource()->selectedColumn()+1;
#ifdef HOME_DISPLAY_EXTERNALS #ifdef HOME_DISPLAY_EXTERNALS
if (index >= container->numberOfApps()) { if (index >= container->numberOfApps()) {
@@ -96,9 +96,8 @@ bool Controller::handleEvent(Ion::Events::Event event) {
} }
} else { } else {
#endif #endif
::App::Snapshot * selectedSnapshot = container->appSnapshotAtIndex(index); ::App::Snapshot * selectedSnapshot = container->appSnapshotAtIndex(PermutedAppSnapshotIndex(selectionDataSource()->selectedRow() * k_numberOfColumns + selectionDataSource()->selectedColumn() + 1));
if (ExamModeConfiguration::appIsForbiddenInExamMode(selectedSnapshot->descriptor()->examinationLevel(), GlobalPreferences::sharedGlobalPreferences()->examMode())) { if (ExamModeConfiguration::appIsForbiddenInExamMode(selectedSnapshot->descriptor()->name(), GlobalPreferences::sharedGlobalPreferences()->examMode())) {
App::app()->displayWarning(I18n::Message::ForbidenAppInExamMode1, I18n::Message::ForbidenAppInExamMode2);
} else { } else {
bool switched = container->switchTo(selectedSnapshot); bool switched = container->switchTo(selectedSnapshot);
assert(switched); assert(switched);
@@ -111,14 +110,14 @@ bool Controller::handleEvent(Ion::Events::Event event) {
} }
if (event == Ion::Events::Home || event == Ion::Events::Back) { 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()) { if (event == Ion::Events::Right && selectionDataSource()->selectedRow() < numberOfRows() - 1) {
return m_view.selectableTableView()->selectCellAtLocation(0, selectionDataSource()->selectedRow()+1); return m_view.selectableTableView()->selectCellAtLocation(0, selectionDataSource()->selectedRow() + 1);
} }
if (event == Ion::Events::Left && selectionDataSource()->selectedRow() > 0) { 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; return false;
@@ -146,7 +145,7 @@ View * Controller::view() {
} }
int Controller::numberOfRows() const { int Controller::numberOfRows() const {
return ((numberOfIcons()-1)/k_numberOfColumns)+1; return ((numberOfIcons() - 1) / k_numberOfColumns) + 1;
} }
int Controller::numberOfColumns() const { int Controller::numberOfColumns() const {
@@ -172,7 +171,7 @@ int Controller::reusableCellCount() const {
void Controller::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { void Controller::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) {
AppCell * appCell = (AppCell *)cell; AppCell * appCell = (AppCell *)cell;
AppsContainer * container = AppsContainer::sharedAppsContainer(); AppsContainer * container = AppsContainer::sharedAppsContainer();
int appIndex = (j*k_numberOfColumns+i)+1; int appIndex = (j * k_numberOfColumns + i) + 1;
if (appIndex >= container->numberOfApps()) { if (appIndex >= container->numberOfApps()) {
#ifdef HOME_DISPLAY_EXTERNALS #ifdef HOME_DISPLAY_EXTERNALS
External::Archive::File app_file; External::Archive::File app_file;
@@ -207,7 +206,7 @@ void Controller::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) {
#endif #endif
} else { } else {
appCell->setVisible(true); appCell->setVisible(true);
::App::Descriptor * descriptor = container->appSnapshotAtIndex(appIndex)->descriptor(); ::App::Descriptor * descriptor = container->appSnapshotAtIndex(PermutedAppSnapshotIndex(appIndex))->descriptor();
appCell->setAppDescriptor(descriptor); appCell->setAppDescriptor(descriptor);
} }
} }

View File

@@ -6,18 +6,22 @@
# properly draw upper case letters with accents, we remove them here. # properly draw upper case letters with accents, we remove them here.
# It works with Python 2 and Python 3 # It works with Python 2 and Python 3
import sys
import re
import unicodedata
import argparse import argparse
import csv
import io import io
import re
import sys
import unicodedata
parser = argparse.ArgumentParser(description="Process some i18n files.") parser = argparse.ArgumentParser(description="Process some i18n files.")
parser.add_argument('--header', help='the .h file to generate') parser.add_argument('--header', help='the .h file to generate')
parser.add_argument('--implementation', help='the .cpp 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('--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('--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('--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)') 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): def locale_from_filename(filename):
return re.match(r".*\.([a-z]+)\.i18n", filename).group(1) 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): def parse_files(files):
data = {} data = {}
messages = set() messages = set()
@@ -91,6 +107,7 @@ def parse_files(files):
else: else:
messages.add(name) messages.add(name)
data[locale][name] = definition data[locale][name] = definition
#check_redundancy(messages, data, args.locales) # FIXME
return {"messages": sorted(messages), "universal_messages": sorted(universal_messages), "data": data} return {"messages": sorted(messages), "universal_messages": sorted(universal_messages), "data": data}
def parse_codepoints(file): def parse_codepoints(file):
@@ -114,46 +131,110 @@ def parse_codepoints(file):
codepoints = parse_codepoints(args.codepoints) 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 = open(path, "w")
f.write("#ifndef APPS_I18N_H\n") f.write("#ifndef APPS_I18N_H\n")
f.write("#define APPS_I18N_H\n\n") f.write("#define APPS_I18N_H\n\n")
f.write("// This file is auto-generated by i18n.py\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("namespace I18n {\n\n")
f.write("constexpr static int NumberOfLanguages = %d;\n\n" % len(locales)) f.write("constexpr static int NumberOfLanguages = %d;\n\n" % len(locales))
f.write("constexpr static int NumberOfCountries = %d;\n\n" % len(countries))
# Messages enumeration # Messages enumeration
f.write("enum class Message : uint16_t {\n") print_block_from_list(f,
f.write(" Default = 0,\n") "enum class Message : uint16_t {\n Default = 0,\n",
for message in data["universal_messages"]: data["universal_messages"],
f.write(" " + message + ",\n") footer="\n")
f.write("\n") print_block_from_list(f,
f.write(" LocalizedMessageMarker,\n\n") " LocalizedMessageMarker,\n\n",
for message in data["messages"]: data["messages"])
f.write(" " + message + ",\n")
f.write("};\n\n")
# Languages enumeration # Languages enumeration
f.write("enum class Language : uint16_t {\n") print_block_from_list(f,
index = 0 "enum class Language : uint8_t {\n",
for locale in locales: locales,
f.write(" " + locale.upper() + (" = 0" if (index < 1) else "") +",\n") lambda arg: arg.upper())
index = index + 1
f.write("};\n\n")
# Language names # Language names
f.write("constexpr const Message LanguageNames[NumberOfLanguages] = {\n"); print_block_from_list(f,
for locale in locales: "constexpr const Message LanguageNames[NumberOfLanguages] = {\n",
f.write(" Message::Language" + locale.upper() + ",\n") locales,
f.write("};\n\n") lambda arg: arg.upper(),
" Message::Language")
if generate_ISO6391(): if generate_ISO6391():
# Language ISO639-1 codes print_block_from_list(f,
f.write("constexpr const Message LanguageISO6391Names[NumberOfLanguages] = {\n"); "constexpr const Message LanguageISO6391Names[NumberOfLanguages] = {\n",
for locale in locales: locales,
f.write(" Message::LanguageISO6391" + locale.upper() + ",\n") lambda arg: arg.upper(),
f.write("};\n\n") " 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("}\n\n")
f.write("#endif\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 = open(path, "a") # Re-open the file as text
f.write(";\n") f.write(";\n")
f.write("\n") f.write("\n")
f.write("constexpr static const char * universalMessages[%d] = {\n" % (len(data["universal_messages"])+1)) print_block_from_list(f,
f.write(" universalDefault,\n") "constexpr static const char * universalMessages[%d] = {\n universalDefault,\n" % (len(data["universal_messages"])+1),
for message in data["universal_messages"]: data["universal_messages"],
f.write(" universal" + message + ",\n") prefix=" universal")
f.write("};\n\n")
# Write the localized messages # Write the localized messages
for message in data["messages"]: for message in data["messages"]:
@@ -226,6 +306,6 @@ def print_implementation(data, path, locales):
data = parse_files(args.files) data = parse_files(args.files)
if args.header: if args.header:
print_header(data, args.header, args.locales) print_header(data, args.header, args.locales, args.countries)
if args.implementation: if args.implementation:
print_implementation(data, args.implementation, args.locales) print_implementation(data, args.implementation, args.locales)

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