[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
on: [pull_request, push]
#on: [pull_request, push]
on:
pull_request:
workflow_dispatch:
inputs:
triggerIos:
description: 'Run iOS tests'
required: true
default: 'no'
triggerMacos:
description: 'Run macOS tests'
required: true
default: 'no'
jobs:
# nintendo_3ds:
@@ -11,7 +23,7 @@ jobs:
# - run: echo ::set-env name=DEVKITPRO::/opt/devkitpro
# - run: echo ::set-env name=DEVKITARM::/opt/devkitpro/devkitARM
# - run: echo ::set-env name=PATH::$DEVKITPRO/tools/bin:$DEVKITARM/bin:$PATH
# - uses: actions/checkout@v1
# with:
# submodules: true
@@ -94,6 +106,23 @@ jobs:
with:
name: epsilon-binpack-n0110.tgz
path: output/release/device/n0110/binpack-n0110.tgz
windows:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- uses: msys2/setup-msys2@v2
- uses: actions/checkout@v2
- run: pacman -S --noconfirm mingw-w64-x86_64-gcc mingw-w64-x86_64-freetype mingw-w64-x86_64-pkg-config make mingw-w64-x86_64-python3 mingw-w64-x86_64-libjpeg-turbo mingw-w64-x86_64-libpng
- run: make -j2 PLATFORM=simulator
- run: make -j2 PLATFORM=simulator epsilon.official.exe
- run: make -j2 PLATFORM=simulator test.headless.exe
- run: output/release/simulator/windows/test.headless.exe
- uses: actions/upload-artifact@master
with:
name: epsilon-windows.exe
path: output/release/simulator/windows/epsilon.exe
web:
runs-on: ubuntu-latest
steps:
@@ -121,3 +150,34 @@ jobs:
name: epsilon-linux.bin
path: output/release/simulator/linux/epsilon.bin
- run: make -j2 PLATFORM=simulator test.headless.bin
macos:
if: github.event.inputs.triggerMacos == 'yes'
runs-on: macOS-latest
steps:
- run: brew install numworks/tap/epsilon-sdk
- uses: actions/checkout@v2
- run: make -j2 PLATFORM=simulator
- run: make -j2 PLATFORM=simulator epsilon.official.app
- run: make -j2 PLATFORM=simulator ARCH=x86_64 test.headless.bin
- run: output/release/simulator/macos/x86_64/test.headless.bin
- uses: actions/upload-artifact@master
with:
name: epsilon-macos.zip
path: output/release/simulator/macos/epsilon.app
ios:
if: github.event.inputs.triggerIos == 'yes'
runs-on: macOS-latest
steps:
- run: brew install numworks/tap/epsilon-sdk
- uses: actions/checkout@v2
- run: make -j2 PLATFORM=simulator TARGET=ios EPSILON_TELEMETRY=0
- run: make -j2 PLATFORM=simulator TARGET=ios EPSILON_TELEMETRY=0 epsilon.official.ipa
- run: make -j2 PLATFORM=simulator TARGET=ios EPSILON_TELEMETRY=0 test.ipa
- run: make -j2 PLATFORM=simulator TARGET=ios EPSILON_TELEMETRY=0 APPLE_PLATFORM=ios-simulator
- uses: actions/upload-artifact@master
with:
name: epsilon-ios.ipa
path: output/release/simulator/ios/epsilon.ipa
env:
ACCEPT_OFFICIAL_TOS: 1

View File

@@ -42,7 +42,10 @@ apps_src += $(addprefix apps/,\
title_bar_view.cpp \
)
tests_src += apps/exam_mode_configuration_non_official.cpp
tests_src += $(addprefix apps/,\
exam_mode_configuration_official.cpp \
)
snapshots_declaration = $(foreach i,$(apps),$(i)::Snapshot m_snapshot$(subst :,,$(i))Snapshot;)
apps_declaration = $(foreach i,$(apps),$(i) m_$(subst :,,$(i));)
@@ -56,6 +59,9 @@ $(call object_for,apps/apps_container_storage.cpp apps/apps_container.cpp apps/m
# I18n file generation
country_preferences = apps/country_preferences.csv
language_preferences = apps/language_preferences.csv
# The header is refered to as <apps/i18n.h> so make sure it's findable this way
SFLAGS += -I$(BUILD_DIR)
@@ -65,14 +71,14 @@ i18n_files += $(addprefix apps/language_,$(addsuffix _iso6391.universal.i18n, $(
endif
i18n_files += $(call i18n_with_universal_for,shared)
i18n_files += $(call i18n_without_universal_for,toolbox)
i18n_files += $(call i18n_with_universal_for,toolbox)
i18n_files += $(call i18n_without_universal_for,variables)
$(eval $(call rule_for, \
I18N, \
apps/i18n.cpp, \
$(i18n_files), \
$$(PYTHON) apps/i18n.py --codepoints $(code_points) --header $$(subst .cpp,.h,$$@) --implementation $$@ --locales $$(EPSILON_I18N) --files $$^ --generateISO6391locales $$(EPSILON_GETOPT), \
$$(PYTHON) apps/i18n.py --codepoints $(code_points) --countrypreferences $(country_preferences) --languagepreferences $(language_preferences) --header $$(subst .cpp,.h,$$@) --implementation $$@ --locales $$(EPSILON_I18N) --countries $$(EPSILON_COUNTRIES) --files $$^ --generateISO6391locales $$(EPSILON_GETOPT), \
global \
))
@@ -87,7 +93,7 @@ $(eval $(call depends_on_image,apps/title_bar_view.cpp,apps/exam_icon.png))
$(call object_for,$(apps_src) $(tests_src)): $(BUILD_DIR)/apps/i18n.h
$(call object_for,$(apps_src) $(tests_src)): $(BUILD_DIR)/python/port/genhdr/qstrdefs.generated.h
apps_tests_src = $(app_calculation_test_src) $(app_code_test_src) $(app_probability_test_src) $(app_regression_test_src) $(app_sequence_test_src) $(app_shared_test_src) $(app_statistics_test_src) $(app_settings_test_src) $(app_solver_test_src)
apps_tests_src = $(app_calculation_test_src) $(app_code_test_src) $(app_graph_test_src) $(app_probability_test_src) $(app_regression_test_src) $(app_sequence_test_src) $(app_shared_test_src) $(app_statistics_test_src) $(app_settings_test_src) $(app_solver_test_src)
apps_tests_src += $(addprefix apps/,\
alternate_empty_nested_menu_controller.cpp \

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ BatteryTimer::BatteryTimer() :
bool BatteryTimer::fire() {
AppsContainer * container = AppsContainer::sharedAppsContainer();
bool needRedrawing = container->updateBatteryState();
if (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) {
if (Ion::Battery::level() == Ion::Battery::Charge::EMPTY && !Ion::USB::isPlugged()) {
container->shutdownDueToLowBattery();
}
return needRedrawing;

View File

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

View File

@@ -11,7 +11,7 @@ ExpressionsListController::ExpressionsListController(EditExpressionController *
ListController(editExpressionController),
m_cells{}
{
for (int i = 0; i < k_maxNumberOfCells; i++) {
for (int i = 0; i < k_maxNumberOfRows; i++) {
m_cells[i].setParentResponder(m_listController.selectableTableView());
}
}
@@ -21,15 +21,20 @@ void ExpressionsListController::didEnterResponderChain(Responder * previousFirst
}
int ExpressionsListController::reusableCellCount(int type) {
return k_maxNumberOfCells;
return k_maxNumberOfRows;
}
void ExpressionsListController::viewDidDisappear() {
ListController::viewDidDisappear();
// Reset cell memoization to avoid taking extra space in the pool
for (int i = 0; i < k_maxNumberOfCells; i++) {
// Reset layout and cell memoization to avoid taking extra space in the pool
for (int i = 0; i < k_maxNumberOfRows; i++) {
m_cells[i].setLayout(Layout());
/* By reseting m_layouts, numberOfRow will go down to 0, and the highlighted
* cells won't be unselected. Therefore we unselect them here. */
m_cells[i].setHighlighted(false);
m_layouts[i] = Layout();
}
m_expression = Expression();
}
HighlightCell * ExpressionsListController::reusableCell(int index, int type) {
@@ -43,24 +48,34 @@ KDCoordinate ExpressionsListController::rowHeight(int j) {
}
void ExpressionsListController::willDisplayCellForIndex(HighlightCell * cell, int index) {
/* Note : To further optimize memoization space in the pool, layout
* serialization could be memoized instead, and layout would be recomputed
* here, when setting cell's layout. */
ExpressionTableCellWithPointer * myCell = static_cast<ExpressionTableCellWithPointer *>(cell);
myCell->setLayout(layoutAtIndex(index));
myCell->setAccessoryMessage(messageAtIndex(index));
myCell->reloadScroll();
}
int ExpressionsListController::numberOfRows() const {
int nbOfRows = 0;
for (size_t i = 0; i < k_maxNumberOfRows; i++) {
if (!m_layouts[i].isUninitialized()) {
nbOfRows++;
}
}
return nbOfRows;
}
void ExpressionsListController::setExpression(Poincare::Expression e) {
// Reinitialize memoization
for (int i = 0; i < k_maxNumberOfCells; i++) {
for (int i = 0; i < k_maxNumberOfRows; i++) {
m_layouts[i] = Layout();
}
m_expression = e;
}
Poincare::Layout ExpressionsListController::layoutAtIndex(int index) {
if (m_layouts[index].isUninitialized()) {
computeLayoutAtIndex(index);
}
assert(!m_layouts[index].isUninitialized());
return m_layouts[index];
}

View File

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

View File

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

View File

@@ -39,7 +39,10 @@ protected:
private:
int textAtIndex(char * buffer, size_t bufferSize, int index) override;
virtual CodePoint expressionSymbol() const = 0;
// Set the size of the buffer needed to store the additional calculation
constexpr static int k_maxNumberOfAdditionalCalculations = 4;
constexpr static int k_calculationStoreBufferSize = k_maxNumberOfAdditionalCalculations * (sizeof(Calculation) + Calculation::k_numberOfExpressions * Constant::MaxSerializedExpressionSize + sizeof(Calculation *));
char m_calculationStoreBuffer[k_calculationStoreBufferSize];
// Cells
virtual HighlightCell * illustrationCell() = 0;
ScrollableThreeExpressionsCell m_additionalCalculationCells[k_maxNumberOfAdditionalCalculations];

View File

@@ -11,10 +11,6 @@ using namespace Shared;
namespace Calculation {
int IntegerListController::numberOfRows() const {
return 3 + factorExpressionIsComputable();
}
Integer::Base baseAtIndex(int index) {
switch (index) {
case 0:
@@ -27,12 +23,20 @@ Integer::Base baseAtIndex(int index) {
}
}
void IntegerListController::computeLayoutAtIndex(int index) {
assert(m_expression.type() == ExpressionNode::Type::BasedInteger);
// For index = k_indexOfFactorExpression, the layout is assumed to be alreday memoized because it is needed to compute the numberOfRows
assert(index < k_indexOfFactorExpression);
Integer i = static_cast<BasedInteger &>(m_expression).integer();
m_layouts[index] = i.createLayout(baseAtIndex(index));
void IntegerListController::setExpression(Poincare::Expression e) {
ExpressionsListController::setExpression(e);
static_assert(k_maxNumberOfRows >= k_indexOfFactorExpression + 1, "k_maxNumberOfRows must be greater than k_indexOfFactorExpression");
assert(!m_expression.isUninitialized() && m_expression.type() == ExpressionNode::Type::BasedInteger);
Integer integer = static_cast<BasedInteger &>(m_expression).integer();
for (int index = 0; index < k_indexOfFactorExpression; ++index) {
m_layouts[index] = integer.createLayout(baseAtIndex(index));
}
// Computing factorExpression
Expression factor = Factor::Builder(m_expression.clone());
PoincareHelpers::Simplify(&factor, App::app()->localContext(), ExpressionNode::ReductionTarget::User);
if (!factor.isUndefined()) {
m_layouts[k_indexOfFactorExpression] = PoincareHelpers::CreateLayout(factor);
}
}
I18n::Message IntegerListController::messageAtIndex(int index) {
@@ -48,20 +52,4 @@ I18n::Message IntegerListController::messageAtIndex(int index) {
}
}
bool IntegerListController::factorExpressionIsComputable() const {
if (!m_layouts[k_indexOfFactorExpression].isUninitialized()) {
// The factor expression is already memoized
return !m_layouts[k_indexOfFactorExpression].isEmpty();
}
Poincare::Context * context = App::app()->localContext();
Expression factor = Factor::Builder(m_expression.clone());
PoincareHelpers::Simplify(&factor, context, ExpressionNode::ReductionTarget::User);
if (!factor.isUndefined()) {
m_layouts[k_indexOfFactorExpression] = PoincareHelpers::CreateLayout(factor);
return true;
}
m_layouts[k_indexOfFactorExpression] = EmptyLayout::Builder();
return false;
}
}

View File

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

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 {
int RationalListController::numberOfRows() const {
return 2;
}
Integer extractInteger(const Expression e) {
assert(e.type() == ExpressionNode::Type::BasedInteger);
return static_cast<const BasedInteger &>(e).integer();
}
void RationalListController::computeLayoutAtIndex(int index) {
void RationalListController::setExpression(Poincare::Expression e) {
ExpressionsListController::setExpression(e);
assert(!m_expression.isUninitialized());
static_assert(k_maxNumberOfRows >= 2, "k_maxNumberOfRows must be greater than 2");
bool negative = false;
Expression div = m_expression;
if (m_expression.type() == ExpressionNode::Type::Opposite) {
negative = true;
div = m_expression.childAtIndex(0);
}
assert(div.type() == ExpressionNode::Type::Division);
Integer numerator = extractInteger(div.childAtIndex(0));
numerator.setNegative(negative);
Integer denominator = extractInteger(div.childAtIndex(1));
Expression e;
if (index == 0) {
e = Integer::CreateMixedFraction(numerator, denominator);
} else {
assert(index == 1);
e = Integer::CreateEuclideanDivision(numerator, denominator);
}
m_layouts[index] = PoincareHelpers::CreateLayout(e);
int index = 0;
m_layouts[index++] = PoincareHelpers::CreateLayout(Integer::CreateMixedFraction(numerator, denominator));
m_layouts[index++] = PoincareHelpers::CreateLayout(Integer::CreateEuclideanDivision(numerator, denominator));
}
I18n::Message RationalListController::messageAtIndex(int index) {

View File

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

View File

@@ -15,112 +15,62 @@ namespace Calculation {
void UnitListController::setExpression(Poincare::Expression e) {
ExpressionsListController::setExpression(e);
assert(!m_expression.isUninitialized());
// Reinitialize m_memoizedExpressions
for (size_t i = 0; i < k_maxNumberOfCells; i++) {
m_memoizedExpressions[i] = Expression();
static_assert(k_maxNumberOfRows >= 3, "k_maxNumberOfRows must be greater than 3");
Poincare::Expression expressions[k_maxNumberOfRows];
// Initialize expressions
for (size_t i = 0; i < k_maxNumberOfRows; i++) {
expressions[i] = Expression();
}
size_t numberOfMemoizedExpressions = 0;
// 1. First rows: miscellaneous classic units for some dimensions
/* 1. First rows: miscellaneous classic units for some dimensions, in both
* metric and imperial units. */
Expression copy = m_expression.clone();
Expression units;
// Reduce to be able to recognize units
PoincareHelpers::Reduce(&copy, App::app()->localContext(), ExpressionNode::ReductionTarget::User);
copy = copy.removeUnit(&units);
bool requireSimplification = false;
bool canChangeUnitPrefix = false;
PoincareHelpers::ReduceAndRemoveUnit(&copy, App::app()->localContext(), ExpressionNode::ReductionTarget::User, &units);
double value = Shared::PoincareHelpers::ApproximateToScalar<double>(copy, App::app()->localContext());
ExpressionNode::ReductionContext reductionContext(
App::app()->localContext(),
Preferences::sharedPreferences()->complexFormat(),
Preferences::sharedPreferences()->angleUnit(),
GlobalPreferences::sharedGlobalPreferences()->unitFormat(),
ExpressionNode::ReductionTarget::User,
ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined);
int numberOfExpressions = Unit::SetAdditionalExpressions(units, value, expressions, k_maxNumberOfRows, reductionContext);
if (Unit::IsSISpeed(units)) {
// 1.a. Turn speed into km/h
m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder(
m_expression.clone(),
Multiplication::Builder(
Unit::Kilometer(),
Power::Builder(
Unit::Hour(),
Rational::Builder(-1)
)
)
);
requireSimplification = true; // Simplify the conversion
} else if (Unit::IsSIVolume(units)) {
// 1.b. Turn volume into L
m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder(
m_expression.clone(),
Unit::Liter()
);
requireSimplification = true; // Simplify the conversion
canChangeUnitPrefix = true; // Pick best prefix (mL)
} else if (Unit::IsSIEnergy(units)) {
// 1.c. Turn energy into Wh
m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder(
m_expression.clone(),
Multiplication::Builder(
Unit::Watt(),
Unit::Hour()
)
);
m_memoizedExpressions[numberOfMemoizedExpressions++] = UnitConvert::Builder(
m_expression.clone(),
Unit::ElectronVolt()
);
requireSimplification = true; // Simplify the conversion
canChangeUnitPrefix = true; // Pick best prefix (kWh)
} else if (Unit::IsSITime(units)) {
// Turn time into ? year + ? month + ? day + ? h + ? min + ? s
double value = Shared::PoincareHelpers::ApproximateToScalar<double>(copy, App::app()->localContext());
m_memoizedExpressions[numberOfMemoizedExpressions++] = Unit::BuildTimeSplit(value, App::app()->localContext(), Preferences::sharedPreferences()->complexFormat(), Preferences::sharedPreferences()->angleUnit());
}
// 1.d. Simplify and tune prefix of all computed expressions
size_t currentExpressionIndex = 0;
while (currentExpressionIndex < numberOfMemoizedExpressions) {
assert(!m_memoizedExpressions[currentExpressionIndex].isUninitialized());
if (requireSimplification) {
Shared::PoincareHelpers::Simplify(&m_memoizedExpressions[currentExpressionIndex], App::app()->localContext(), ExpressionNode::ReductionTarget::User);
}
if (canChangeUnitPrefix) {
Expression newUnits;
// Reduce to be able to removeUnit
PoincareHelpers::Reduce(&m_memoizedExpressions[currentExpressionIndex], App::app()->localContext(), ExpressionNode::ReductionTarget::User);
m_memoizedExpressions[currentExpressionIndex] = m_memoizedExpressions[currentExpressionIndex].removeUnit(&newUnits);
double value = Shared::PoincareHelpers::ApproximateToScalar<double>(m_memoizedExpressions[currentExpressionIndex], App::app()->localContext());
ExpressionNode::ReductionContext reductionContext(
App::app()->localContext(),
Preferences::sharedPreferences()->complexFormat(),
Preferences::sharedPreferences()->angleUnit(),
ExpressionNode::ReductionTarget::User,
ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined);
Unit::ChooseBestPrefixForValue(&newUnits, &value, reductionContext);
m_memoizedExpressions[currentExpressionIndex] = Multiplication::Builder(Number::FloatNumber(value), newUnits);
}
currentExpressionIndex++;
}
// 2. SI units only
assert(numberOfExpressions < k_maxNumberOfRows - 1);
expressions[numberOfExpressions] = m_expression.clone();
Shared::PoincareHelpers::Simplify(&expressions[numberOfExpressions], App::app()->localContext(), ExpressionNode::ReductionTarget::User, Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::InternationalSystem);
numberOfExpressions++;
// 2. IS units only
assert(numberOfMemoizedExpressions < k_maxNumberOfCells - 1);
m_memoizedExpressions[numberOfMemoizedExpressions] = m_expression.clone();
Shared::PoincareHelpers::Simplify(&m_memoizedExpressions[numberOfMemoizedExpressions], App::app()->localContext(), ExpressionNode::ReductionTarget::User, Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::InternationalSystem);
numberOfMemoizedExpressions++;
// 3. Get rid of duplicates
/* 3. Get rid of duplicates
* We find duplicates by comparing the serializations, to eliminate
* expressions that only differ by the types of their number nodes. */
Expression reduceExpression = m_expression.clone();
// Make m_expression compareable to m_memoizedExpressions (turn BasedInteger into Rational for instance)
// Make m_expression comparable to expressions (turn BasedInteger into Rational for instance)
Shared::PoincareHelpers::Simplify(&reduceExpression, App::app()->localContext(), ExpressionNode::ReductionTarget::User, Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion::None);
currentExpressionIndex = 1;
while (currentExpressionIndex < numberOfMemoizedExpressions) {
int currentExpressionIndex = 0;
while (currentExpressionIndex < numberOfExpressions) {
bool duplicateFound = false;
for (size_t i = 0; i < currentExpressionIndex + 1; i++) {
// Compare the currentExpression to all previous memoized expressions and to m_expression
Expression comparedExpression = i == currentExpressionIndex ? reduceExpression : m_memoizedExpressions[i];
constexpr int buffersSize = Constant::MaxSerializedExpressionSize;
char buffer1[buffersSize];
int size1 = PoincareHelpers::Serialize(expressions[currentExpressionIndex], buffer1, buffersSize);
for (int i = 0; i < currentExpressionIndex + 1; i++) {
// Compare the currentExpression to all previous expressions and to m_expression
Expression comparedExpression = i == currentExpressionIndex ? reduceExpression : expressions[i];
assert(!comparedExpression.isUninitialized());
if (comparedExpression.isIdenticalTo(m_memoizedExpressions[currentExpressionIndex])) {
numberOfMemoizedExpressions--;
char buffer2[buffersSize];
int size2 = PoincareHelpers::Serialize(comparedExpression, buffer2, buffersSize);
if (size1 == size2 && strcmp(buffer1, buffer2) == 0) {
numberOfExpressions--;
// Shift next expressions
for (size_t j = currentExpressionIndex; j < numberOfMemoizedExpressions; j++) {
m_memoizedExpressions[j] = m_memoizedExpressions[j+1];
for (int j = currentExpressionIndex; j < numberOfExpressions; j++) {
expressions[j] = expressions[j+1];
}
// Remove last expression
m_memoizedExpressions[numberOfMemoizedExpressions] = Expression();
expressions[numberOfExpressions] = Expression();
// The current expression has been discarded, no need to increment the current index
duplicateFound = true;
break;
@@ -131,21 +81,12 @@ void UnitListController::setExpression(Poincare::Expression e) {
currentExpressionIndex++;
}
}
}
int UnitListController::numberOfRows() const {
int nbOfRows = 0;
for (size_t i = 0; i < k_maxNumberOfCells; i++) {
if (!m_memoizedExpressions[i].isUninitialized()) {
nbOfRows++;
// Memoize layouts
for (size_t i = 0; i < k_maxNumberOfRows; i++) {
if (!expressions[i].isUninitialized()) {
m_layouts[i] = Shared::PoincareHelpers::CreateLayout(expressions[i]);
}
}
return nbOfRows;
}
void UnitListController::computeLayoutAtIndex(int index) {
assert(!m_memoizedExpressions[index].isUninitialized());
m_layouts[index] = Shared::PoincareHelpers::CreateLayout(m_memoizedExpressions[index]);
}
I18n::Message UnitListController::messageAtIndex(int index) {

View File

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

View File

@@ -31,6 +31,8 @@ App * App::Snapshot::unpack(Container * container) {
void App::Snapshot::reset() {
m_calculationStore.deleteAll();
m_cacheBuffer[0] = 0;
m_cacheBufferInformation = 0;
}
App::Descriptor * App::Snapshot::descriptor() {
@@ -38,14 +40,14 @@ App::Descriptor * App::Snapshot::descriptor() {
return &descriptor;
}
void App::Snapshot::tidy() {
m_calculationStore.tidy();
App::Snapshot::Snapshot() : m_calculationStore(m_calculationBuffer, k_calculationBufferSize)
{
}
App::App(Snapshot * snapshot) :
ExpressionFieldDelegateApp(snapshot, &m_editExpressionController),
m_historyController(&m_editExpressionController, snapshot->calculationStore()),
m_editExpressionController(&m_modalViewController, this, &m_historyController, snapshot->calculationStore())
m_editExpressionController(&m_modalViewController, this, snapshot->cacheBuffer(), snapshot->cacheBufferInformationAddress(), &m_historyController, snapshot->calculationStore())
{
}
@@ -70,7 +72,17 @@ bool App::isAcceptableExpression(const Poincare::Expression expression) {
return false;
}
}
return !expression.isUninitialized();
return !(expression.isUninitialized() || expression.type() == ExpressionNode::Type::Equal);
}
void App::didBecomeActive(Window * window) {
m_editExpressionController.restoreInput();
Shared::ExpressionFieldDelegateApp::didBecomeActive(window);
}
void App::willBecomeInactive() {
m_editExpressionController.memoizeInput();
Shared::ExpressionFieldDelegateApp::willBecomeInactive();
}
}

View File

@@ -6,6 +6,7 @@
#include "history_controller.h"
#include "../shared/text_field_delegate_app.h"
#include <escher.h>
#include "../shared/shared_app.h"
namespace Calculation {
@@ -18,15 +19,22 @@ public:
App::Descriptor::ExaminationLevel examinationLevel() override;
const Image * icon() override;
};
class Snapshot : public ::App::Snapshot {
class Snapshot : public ::SharedApp::Snapshot {
public:
Snapshot();
App * unpack(Container * container) override;
void reset() override;
Descriptor * descriptor() override;
CalculationStore * calculationStore() { return &m_calculationStore; }
char * cacheBuffer() { return m_cacheBuffer; }
size_t * cacheBufferInformationAddress() { return &m_cacheBufferInformation; }
private:
void tidy() override;
CalculationStore m_calculationStore;
// Set the size of the buffer needed to store the calculations
static constexpr int k_calculationBufferSize = 10 * (sizeof(Calculation) + Calculation::k_numberOfExpressions * Constant::MaxSerializedExpressionSize + sizeof(Calculation *));
char m_calculationBuffer[k_calculationBufferSize];
char m_cacheBuffer[EditExpressionController::k_cacheBufferSize];
size_t m_cacheBufferInformation;
};
static App * app() {
return static_cast<App *>(Container::activeApp());
@@ -36,9 +44,12 @@ public:
bool layoutFieldDidReceiveEvent(::LayoutField * layoutField, Ion::Events::Event event) override;
// TextFieldDelegateApp
bool isAcceptableExpression(const Poincare::Expression expression) override;
private:
App(Snapshot * snapshot);
HistoryController m_historyController;
void didBecomeActive(Window * window) override;
void willBecomeInactive() override;
EditExpressionController m_editExpressionController;
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -169,7 +169,8 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) {
ExpressionNode::Type::Sum,
ExpressionNode::Type::Derivative,
ExpressionNode::Type::ConfidenceInterval,
ExpressionNode::Type::PredictionInterval
ExpressionNode::Type::PredictionInterval,
ExpressionNode::Type::Sequence
};
return e.isOfType(approximateOnlyTypes, sizeof(approximateOnlyTypes)/sizeof(ExpressionNode::Type));
}, context)
@@ -218,7 +219,7 @@ Calculation::EqualSign Calculation::exactAndApproximateDisplayedOutputsAreEqual(
Preferences * preferences = Preferences::sharedPreferences();
// TODO: complex format should not be needed here (as it is not used to create layouts)
Preferences::ComplexFormat complexFormat = Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), m_inputText);
m_equalSign = Expression::ParsedExpressionsAreEqual(exactOutputText(), approximateOutputText(NumberOfSignificantDigits::UserDefined), context, complexFormat, preferences->angleUnit()) ? EqualSign::Equal : EqualSign::Approximation;
m_equalSign = Expression::ParsedExpressionsAreEqual(exactOutputText(), approximateOutputText(NumberOfSignificantDigits::UserDefined), context, complexFormat, preferences->angleUnit(), GlobalPreferences::sharedGlobalPreferences()->unitFormat()) ? EqualSign::Equal : EqualSign::Approximation;
return m_equalSign;
} else {
/* Do not override m_equalSign in case there is enough room in the pool
@@ -254,32 +255,9 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co
}
if (o.hasUnit()) {
Expression unit;
PoincareHelpers::Reduce(&o,
App::app()->localContext(),
ExpressionNode::ReductionTarget::User,
ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined,
ExpressionNode::UnitConversion::None);
o = o.removeUnit(&unit);
// There might be no unit in the end, if the reduction was interrupted.
if (!unit.isUninitialized()) {
if (Unit::IsSI(unit)) {
if (Unit::IsSISpeed(unit) || Unit::IsSIVolume(unit) || Unit::IsSIEnergy(unit)) {
/* All these units will provide misc. classic representatives in
* addition to the SI unit in additional information. */
return AdditionalInformationType::Unit;
}
if (Unit::IsSITime(unit)) {
/* If the number of seconds is above 60s, we can write it in the form
* of an addition: 23_min + 12_s for instance. */
double value = Shared::PoincareHelpers::ApproximateToScalar<double>(o, App::app()->localContext());
if (value > Unit::SecondsPerMinute) {
return AdditionalInformationType::Unit;
}
}
return AdditionalInformationType::None;
}
return AdditionalInformationType::Unit;
}
PoincareHelpers::ReduceAndRemoveUnit(&o, App::app()->localContext(), ExpressionNode::ReductionTarget::User, &unit, ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined, ExpressionNode::UnitConversion::None);
double value = PoincareHelpers::ApproximateToScalar<double>(o, App::app()->localContext());
return (Unit::ShouldDisplayAdditionalOutputs(value, unit, GlobalPreferences::sharedGlobalPreferences()->unitFormat())) ? AdditionalInformationType::Unit : AdditionalInformationType::None;
}
if (o.isBasedIntegerCappedBy(k_maximalIntegerWithAdditionalInformation)) {
return AdditionalInformationType::Integer;
@@ -291,6 +269,9 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co
if (o.hasDefinedComplexApproximation(context, complexFormat, preferences->angleUnit())) {
return AdditionalInformationType::Complex;
}
if (o.type() == ExpressionNode::Type::Matrix) {
return AdditionalInformationType::Matrix;
}
return AdditionalInformationType::None;
}

View File

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

View File

@@ -12,58 +12,47 @@ using namespace Shared;
namespace Calculation {
CalculationStore::CalculationStore() :
m_bufferEnd(m_buffer),
m_numberOfCalculations(0),
m_slidedBuffer(false),
m_indexOfFirstMemoizedCalculationPointer(0)
CalculationStore::CalculationStore(char * buffer, int size) :
m_buffer(buffer),
m_bufferSize(size),
m_calculationAreaEnd(m_buffer),
m_numberOfCalculations(0)
{
resetMemoizedModelsAfterCalculationIndex(-1);
assert(m_buffer != nullptr);
assert(m_bufferSize > 0);
}
// Returns an expiring pointer to the calculation of index i
ExpiringPointer<Calculation> CalculationStore::calculationAtIndex(int i) {
assert(!m_slidedBuffer);
assert(i >= 0 && i < m_numberOfCalculations);
assert(m_indexOfFirstMemoizedCalculationPointer >= 0);
if (i >= m_indexOfFirstMemoizedCalculationPointer && i < m_indexOfFirstMemoizedCalculationPointer + k_numberOfMemoizedCalculationPointers) {
// The calculation is within the range of memoized calculations
Calculation * c = m_memoizedCalculationPointers[i-m_indexOfFirstMemoizedCalculationPointer];
if (c != nullptr) {
// The pointer was memoized
return ExpiringPointer<Calculation>(c);
}
c = bufferCalculationAtIndex(i);
m_memoizedCalculationPointers[i-m_indexOfFirstMemoizedCalculationPointer] = c;
return c;
// m_buffer is the adress of the oldest calculation in calculation store
Calculation * c = (Calculation *) m_buffer;
if (i != m_numberOfCalculations-1) {
// The calculation we want is not the oldest one so we get its pointer
c = *reinterpret_cast<Calculation**>(addressOfPointerToCalculationOfIndex(i+1));
}
// Slide the memoization buffer
if (i >= m_indexOfFirstMemoizedCalculationPointer) {
// Slide the memoization buffer to the left
memmove(m_memoizedCalculationPointers, m_memoizedCalculationPointers+1, (k_numberOfMemoizedCalculationPointers - 1) * sizeof(Calculation *));
m_memoizedCalculationPointers[k_numberOfMemoizedCalculationPointers - 1] = nullptr;
m_indexOfFirstMemoizedCalculationPointer++;
} else {
// Slide the memoization buffer to the right
memmove(m_memoizedCalculationPointers+1, m_memoizedCalculationPointers, (k_numberOfMemoizedCalculationPointers - 1) * sizeof(Calculation *));
m_memoizedCalculationPointers[0] = nullptr;
m_indexOfFirstMemoizedCalculationPointer--;
}
return calculationAtIndex(i);
return ExpiringPointer<Calculation>(c);
}
// Pushes an expression in the store
ExpiringPointer<Calculation> CalculationStore::push(const char * text, Context * context, HeightComputer heightComputer) {
/* Compute ans now, before the buffer is slided and before the calculation
/* Compute ans now, before the buffer is updated and before the calculation
* might be deleted */
Expression ans = ansExpression(context);
// Prepare the buffer for the new calculation
int minSize = Calculation::MinimalSize();
assert(k_bufferSize > minSize);
while (remainingBufferSize() < minSize || m_numberOfCalculations > k_maxNumberOfCalculations) {
deleteLastCalculation();
/* Prepare the buffer for the new calculation
*The minimal size to store the new calculation is the minimal size of a calculation plus the pointer to its end */
int minSize = Calculation::MinimalSize() + sizeof(Calculation *);
assert(m_bufferSize > minSize);
while (remainingBufferSize() < minSize) {
// If there is no more space to store a calculation, we delete the oldest one
deleteOldestCalculation();
}
char * newCalculationsLocation = slideCalculationsToEndOfBuffer();
char * nextSerializationLocation = m_buffer;
// Getting the adresses of the limits of the free space
char * beginingOfFreeSpace = (char *)m_calculationAreaEnd;
char * endOfFreeSpace = beginingOfMemoizationArea();
char * previousCalc = beginingOfFreeSpace;
// Add the beginning of the calculation
{
@@ -71,23 +60,23 @@ ExpiringPointer<Calculation> CalculationStore::push(const char * text, Context *
* available, so this memmove will not overide anything. */
Calculation newCalc = Calculation();
size_t calcSize = sizeof(newCalc);
memmove(nextSerializationLocation, &newCalc, calcSize);
nextSerializationLocation += calcSize;
memcpy(beginingOfFreeSpace, &newCalc, calcSize);
beginingOfFreeSpace += calcSize;
}
/* Add the input expression.
* We do not store directly the text entered by the user because we do not
* want to keep Ans symbol in the calculation store. */
const char * inputSerialization = nextSerializationLocation;
const char * inputSerialization = beginingOfFreeSpace;
{
Expression input = Expression::Parse(text, context).replaceSymbolWithExpression(Symbol::Ans(), ans);
if (!pushSerializeExpression(input, nextSerializationLocation, &newCalculationsLocation)) {
if (!pushSerializeExpression(input, beginingOfFreeSpace, &endOfFreeSpace)) {
/* If the input does not fit in the store (event if the current
* calculation is the only calculation), just replace the calculation with
* undef. */
return emptyStoreAndPushUndef(context, heightComputer);
}
nextSerializationLocation += strlen(nextSerializationLocation) + 1;
beginingOfFreeSpace += strlen(beginingOfFreeSpace) + 1;
}
// Compute and serialize the outputs
@@ -110,30 +99,27 @@ ExpiringPointer<Calculation> CalculationStore::push(const char * text, Context *
if (i == numberOfOutputs - 1) {
numberOfSignificantDigits = Poincare::Preferences::sharedPreferences()->numberOfSignificantDigits();
}
if (!pushSerializeExpression(outputs[i], nextSerializationLocation, &newCalculationsLocation, numberOfSignificantDigits)) {
if (!pushSerializeExpression(outputs[i], beginingOfFreeSpace, &endOfFreeSpace, numberOfSignificantDigits)) {
/* If the exat/approximate output does not fit in the store (event if the
* current calculation is the only calculation), replace the output with
* undef if it fits, else replace the whole calcualtion with undef. */
Expression undef = Undefined::Builder();
if (!pushSerializeExpression(undef, nextSerializationLocation, &newCalculationsLocation)) {
if (!pushSerializeExpression(undef, beginingOfFreeSpace, &endOfFreeSpace)) {
return emptyStoreAndPushUndef(context, heightComputer);
}
}
nextSerializationLocation += strlen(nextSerializationLocation) + 1;
beginingOfFreeSpace += strlen(beginingOfFreeSpace) + 1;
}
}
// Storing the pointer of the end of the new calculation
memcpy(endOfFreeSpace-sizeof(Calculation*),&beginingOfFreeSpace,sizeof(beginingOfFreeSpace));
// Restore the other calculations
size_t slideSize = m_buffer + k_bufferSize - newCalculationsLocation;
memcpy(nextSerializationLocation, newCalculationsLocation, slideSize);
m_slidedBuffer = false;
// The new calculation is now stored
m_numberOfCalculations++;
m_bufferEnd+= nextSerializationLocation - m_buffer;
// Clean the memoization
resetMemoizedModelsAfterCalculationIndex(-1);
ExpiringPointer<Calculation> calculation = ExpiringPointer<Calculation>(reinterpret_cast<Calculation *>(m_buffer));
// The end of the calculation storage area is updated
m_calculationAreaEnd += beginingOfFreeSpace - previousCalc;
ExpiringPointer<Calculation> calculation = ExpiringPointer<Calculation>(reinterpret_cast<Calculation *>(previousCalc));
/* Heights are computed now to make sure that the display output is decided
* accordingly to the remaining size in the Poincare pool. Once it is, it
* can't change anymore: the calculation heights are fixed which ensures that
@@ -144,36 +130,42 @@ ExpiringPointer<Calculation> CalculationStore::push(const char * text, Context *
return calculation;
}
// Delete the calculation of index i
void CalculationStore::deleteCalculationAtIndex(int i) {
assert(i >= 0 && i < m_numberOfCalculations);
assert(!m_slidedBuffer);
ExpiringPointer<Calculation> calcI = calculationAtIndex(i);
char * nextCalc = reinterpret_cast<char *>(calcI->next());
assert(m_bufferEnd >= nextCalc);
size_t slidingSize = m_bufferEnd - nextCalc;
memmove((char *)(calcI.pointer()), nextCalc, slidingSize);
m_bufferEnd -= (nextCalc - (char *)(calcI.pointer()));
m_numberOfCalculations--;
resetMemoizedModelsAfterCalculationIndex(i);
}
void CalculationStore::deleteAll() {
/* We might call deleteAll because the app closed due to a pool allocation
* failure, so we cannot assert that m_slidedBuffer is false. */
m_slidedBuffer = false;
m_bufferEnd = m_buffer;
m_numberOfCalculations = 0;
resetMemoizedModelsAfterCalculationIndex(-1);
}
void CalculationStore::tidy() {
if (m_slidedBuffer) {
deleteAll();
if (i == 0) {
ExpiringPointer<Calculation> lastCalculationPointer = calculationAtIndex(0);
m_calculationAreaEnd = (char *)(lastCalculationPointer.pointer());
m_numberOfCalculations--;
return;
}
resetMemoizedModelsAfterCalculationIndex(-1);
char * calcI = (char *)calculationAtIndex(i).pointer();
char * nextCalc = (char *) calculationAtIndex(i-1).pointer();
assert(m_calculationAreaEnd >= nextCalc);
size_t slidingSize = m_calculationAreaEnd - nextCalc;
// Slide the i-1 most recent calculations right after the i+1'th
memmove(calcI, nextCalc, slidingSize);
m_calculationAreaEnd -= nextCalc - calcI;
// Recompute pointer to calculations after the i'th
recomputeMemoizedPointersAfterCalculationIndex(i);
m_numberOfCalculations--;
}
// Delete the oldest calculation in the store and returns the amount of space freed by the operation
size_t CalculationStore::deleteOldestCalculation() {
char * oldBufferEnd = (char *) m_calculationAreaEnd;
deleteCalculationAtIndex(numberOfCalculations()-1);
char * newBufferEnd = (char *) m_calculationAreaEnd;
return oldBufferEnd - newBufferEnd;
}
// Delete all calculations
void CalculationStore::deleteAll() {
m_calculationAreaEnd = m_buffer;
m_numberOfCalculations = 0;
}
// Replace "Ans" by its expression
Expression CalculationStore::ansExpression(Context * context) {
if (numberOfCalculations() == 0) {
return Rational::Builder(0);
@@ -192,92 +184,42 @@ Expression CalculationStore::ansExpression(Context * context) {
return mostRecentCalculation->exactOutput();
}
Calculation * CalculationStore::bufferCalculationAtIndex(int i) {
int currentIndex = 0;
for (Calculation * c : *this) {
if (currentIndex == i) {
return c;
}
currentIndex++;
}
assert(false);
return nullptr;
}
// Push converted expression in the buffer
bool CalculationStore::pushSerializeExpression(Expression e, char * location, char * * newCalculationsLocation, int numberOfSignificantDigits) {
assert(m_slidedBuffer);
assert(*newCalculationsLocation <= m_buffer + k_bufferSize);
assert(*newCalculationsLocation <= m_buffer + m_bufferSize);
bool expressionIsPushed = false;
while (true) {
size_t locationSize = *newCalculationsLocation - location;
expressionIsPushed = (PoincareHelpers::Serialize(e, location, locationSize, numberOfSignificantDigits) < (int)locationSize-1);
if (expressionIsPushed || *newCalculationsLocation >= m_buffer + k_bufferSize) {
if (expressionIsPushed || *newCalculationsLocation >= m_buffer + m_bufferSize) {
break;
}
*newCalculationsLocation = *newCalculationsLocation + deleteLastCalculation();
assert(*newCalculationsLocation <= m_buffer + k_bufferSize);
*newCalculationsLocation = *newCalculationsLocation + deleteOldestCalculation();
assert(*newCalculationsLocation <= m_buffer + m_bufferSize);
}
return expressionIsPushed;
}
char * CalculationStore::slideCalculationsToEndOfBuffer() {
int calculationsSize = m_bufferEnd - m_buffer;
char * calculationsNewPosition = m_buffer + k_bufferSize - calculationsSize;
memmove(calculationsNewPosition, m_buffer, calculationsSize);
m_slidedBuffer = true;
return calculationsNewPosition;
}
size_t CalculationStore::deleteLastCalculation(const char * calculationsStart) {
assert(m_numberOfCalculations > 0);
size_t result;
if (!m_slidedBuffer) {
assert(calculationsStart == nullptr);
const char * previousBufferEnd = m_bufferEnd;
m_bufferEnd = lastCalculationPosition(m_buffer);
assert(previousBufferEnd > m_bufferEnd);
result = previousBufferEnd - m_bufferEnd;
} else {
assert(calculationsStart != nullptr);
const char * lastCalc = lastCalculationPosition(calculationsStart);
assert(*lastCalc == 0);
result = m_buffer + k_bufferSize - lastCalc;
memmove(const_cast<char *>(calculationsStart + result), calculationsStart, m_buffer + k_bufferSize - calculationsStart - result);
}
m_numberOfCalculations--;
resetMemoizedModelsAfterCalculationIndex(-1);
return result;
}
const char * CalculationStore::lastCalculationPosition(const char * calculationsStart) const {
assert(calculationsStart >= m_buffer && calculationsStart < m_buffer + k_bufferSize);
Calculation * c = reinterpret_cast<Calculation *>(const_cast<char *>(calculationsStart));
int calculationIndex = 0;
while (calculationIndex < m_numberOfCalculations - 1) {
c = c->next();
calculationIndex++;
}
return reinterpret_cast<const char *>(c);
}
Shared::ExpiringPointer<Calculation> CalculationStore::emptyStoreAndPushUndef(Context * context, HeightComputer heightComputer) {
/* We end up here as a result of a failed calculation push. The store
* attributes are not necessarily clean, so we need to reset them. */
m_slidedBuffer = false;
deleteAll();
return push(Undefined::Name(), context, heightComputer);
}
void CalculationStore::resetMemoizedModelsAfterCalculationIndex(int index) {
if (index < m_indexOfFirstMemoizedCalculationPointer) {
memset(&m_memoizedCalculationPointers, 0, k_numberOfMemoizedCalculationPointers * sizeof(Calculation *));
return;
}
if (index >= m_indexOfFirstMemoizedCalculationPointer + k_numberOfMemoizedCalculationPointers) {
return;
}
for (int i = index - m_indexOfFirstMemoizedCalculationPointer; i < k_numberOfMemoizedCalculationPointers; i++) {
m_memoizedCalculationPointers[i] = nullptr;
// Recompute memoized pointers to the calculations after index i
void CalculationStore::recomputeMemoizedPointersAfterCalculationIndex(int index) {
assert(index < m_numberOfCalculations);
// Clear pointer and recompute new ones
Calculation * c = calculationAtIndex(index).pointer();
Calculation * nextCalc;
while (index != 0) {
nextCalc = c->next();
memcpy(addressOfPointerToCalculationOfIndex(index), &nextCalc, sizeof(Calculation *));
c = nextCalc;
index--;
}
}

View File

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

View File

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

View File

@@ -7,7 +7,6 @@
#include "../shared/text_field_delegate.h"
#include "../shared/layout_field_delegate.h"
#include "history_controller.h"
#include "calculation_store.h"
#include "selectable_table_view.h"
namespace Calculation {
@@ -15,11 +14,23 @@ namespace Calculation {
/* TODO: implement a split view */
class EditExpressionController : public ViewController, public Shared::TextFieldDelegate, public Shared::LayoutFieldDelegate {
public:
EditExpressionController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, HistoryController * historyController, CalculationStore * calculationStore);
EditExpressionController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, char * cacheBuffer, size_t * cacheBufferInformation, HistoryController * historyController, CalculationStore * calculationStore);
/* k_layoutBufferMaxSize dictates the size under which the expression being
* edited can be remembered when the user leaves Calculation. */
static constexpr int k_layoutBufferMaxSize = 1024;
/* k_cacheBufferSize is the size of the array to which m_cacheBuffer points.
* It is used both as a way to buffer expression when pushing them the
* CalculationStore, and as a storage for the current input when leaving the
* application. */
static constexpr int k_cacheBufferSize = (k_layoutBufferMaxSize < Constant::MaxSerializedExpressionSize) ? Constant::MaxSerializedExpressionSize : k_layoutBufferMaxSize;
View * view() override { return &m_contentView; }
void didBecomeFirstResponder() override;
void viewWillAppear() override;
void insertTextBody(const char * text);
void restoreInput();
void memoizeInput();
/* TextFieldDelegate */
bool textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) override;
@@ -47,11 +58,12 @@ private:
ExpressionField m_expressionField;
};
void reloadView();
void clearCacheBuffer() { m_cacheBuffer[0] = 0; *m_cacheBufferInformation = 0; }
bool inputViewDidReceiveEvent(Ion::Events::Event event, bool shouldDuplicateLastCalculation);
bool inputViewDidFinishEditing(const char * text, Poincare::Layout layoutR);
bool inputViewDidAbortEditing(const char * text);
static constexpr int k_cacheBufferSize = Constant::MaxSerializedExpressionSize;
char m_cacheBuffer[k_cacheBufferSize];
char * m_cacheBuffer;
size_t * m_cacheBufferInformation;
HistoryController * m_historyController;
CalculationStore * m_calculationStore;
ContentView m_contentView;

View File

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

View File

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

View File

@@ -5,9 +5,17 @@
#include <assert.h>
#include "../calculation_store.h"
typedef ::Calculation::Calculation::DisplayOutput DisplayOutput;
typedef ::Calculation::Calculation::EqualSign EqualSign ;
typedef ::Calculation::Calculation::NumberOfSignificantDigits NumberOfSignificantDigits;
using namespace Poincare;
using namespace Calculation;
static constexpr int calculationBufferSize = 10 * (sizeof(::Calculation::Calculation) + ::Calculation::Calculation::k_numberOfExpressions * ::Constant::MaxSerializedExpressionSize + sizeof(::Calculation::Calculation *));
char calculationBuffer[calculationBufferSize];
void assert_store_is(CalculationStore * store, const char * * result) {
for (int i = 0; i < store->numberOfCalculations(); i++) {
quiz_assert(strcmp(store->calculationAtIndex(i)->inputText(), result[i]) == 0);
@@ -18,7 +26,7 @@ KDCoordinate dummyHeight(::Calculation::Calculation * c, bool expanded) { return
QUIZ_CASE(calculation_store) {
Shared::GlobalContext globalContext;
CalculationStore store;
CalculationStore store(calculationBuffer,calculationBufferSize);
// Store is now {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
const char * result[] = {"9", "8", "7", "6", "5", "4", "3", "2", "1", "0"};
for (int i = 0; i < 10; i++) {
@@ -29,101 +37,117 @@ QUIZ_CASE(calculation_store) {
assert_store_is(&store, result);
for (int i = 9; i > 0; i = i-2) {
store.deleteCalculationAtIndex(i);
store.deleteCalculationAtIndex(i);
}
// Store is now {9, 7, 5, 3, 1}
const char * result2[] = {"9", "7", "5", "3", "1"};
assert_store_is(&store, result2);
store.deleteAll();
// Checking if the store handles correctly the delete of the oldest calculation when full
static int minSize = ::Calculation::Calculation::MinimalSize();
char text[2] = {'0', 0};
while (store.remainingBufferSize() > minSize) {
store.push(text, &globalContext, dummyHeight);
}
int numberOfCalculations1 = store.numberOfCalculations();
/* The buffer is now to full to push a new calculation.
* Trying to push a new one should delete the oldest one*/
store.push(text, &globalContext, dummyHeight);
int numberOfCalculations2 = store.numberOfCalculations();
// The numberOfCalculations should be the same
quiz_assert(numberOfCalculations1 == numberOfCalculations2);
store.deleteAll();
quiz_assert(store.remainingBufferSize() == store.bufferSize());
}
QUIZ_CASE(calculation_ans) {
Shared::GlobalContext globalContext;
CalculationStore store;
CalculationStore store(calculationBuffer,calculationBufferSize);
store.push("1+3/4", &globalContext, dummyHeight);
store.push("ans+2/3", &globalContext, dummyHeight);
Shared::ExpiringPointer<::Calculation::Calculation> lastCalculation = store.calculationAtIndex(0);
quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximate);
quiz_assert(lastCalculation->displayOutput(&globalContext) == DisplayOutput::ExactAndApproximate);
quiz_assert(strcmp(lastCalculation->exactOutputText(),"29/12") == 0);
store.push("ans+0.22", &globalContext, dummyHeight);
lastCalculation = store.calculationAtIndex(0);
quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximateToggle);
quiz_assert(strcmp(lastCalculation->approximateOutputText(::Calculation::Calculation::NumberOfSignificantDigits::Maximal),"2.6366666666667") == 0);
quiz_assert(lastCalculation->displayOutput(&globalContext) == DisplayOutput::ExactAndApproximateToggle);
quiz_assert(strcmp(lastCalculation->approximateOutputText(NumberOfSignificantDigits::Maximal),"2.6366666666667") == 0);
store.deleteAll();
}
void assertCalculationIs(const char * input, ::Calculation::Calculation::DisplayOutput display, ::Calculation::Calculation::EqualSign sign, const char * exactOutput, const char * displayedApproximateOutput, const char * storedApproximateOutput, Context * context, CalculationStore * store) {
void assertCalculationIs(const char * input, DisplayOutput display, EqualSign sign, const char * exactOutput, const char * displayedApproximateOutput, const char * storedApproximateOutput, Context * context, CalculationStore * store) {
store->push(input, context, dummyHeight);
Shared::ExpiringPointer<::Calculation::Calculation> lastCalculation = store->calculationAtIndex(0);
quiz_assert(lastCalculation->displayOutput(context) == display);
if (sign != ::Calculation::Calculation::EqualSign::Unknown) {
if (sign != EqualSign::Unknown) {
quiz_assert(lastCalculation->exactAndApproximateDisplayedOutputsAreEqual(context) == sign);
}
if (exactOutput) {
quiz_assert_print_if_failure(strcmp(lastCalculation->exactOutputText(), exactOutput) == 0, input);
}
if (displayedApproximateOutput) {
quiz_assert_print_if_failure(strcmp(lastCalculation->approximateOutputText(::Calculation::Calculation::NumberOfSignificantDigits::UserDefined), displayedApproximateOutput) == 0, input);
quiz_assert_print_if_failure(strcmp(lastCalculation->approximateOutputText(NumberOfSignificantDigits::UserDefined), displayedApproximateOutput) == 0, input);
}
if (storedApproximateOutput) {
quiz_assert_print_if_failure(strcmp(lastCalculation->approximateOutputText(::Calculation::Calculation::NumberOfSignificantDigits::Maximal), storedApproximateOutput) == 0, input);
quiz_assert_print_if_failure(strcmp(lastCalculation->approximateOutputText(NumberOfSignificantDigits::Maximal), storedApproximateOutput) == 0, input);
}
store->deleteAll();
}
QUIZ_CASE(calculation_significant_digits) {
Shared::GlobalContext globalContext;
CalculationStore store;
CalculationStore store(calculationBuffer,calculationBufferSize);
assertCalculationIs("123456789", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "123456789", "1.234568ᴇ8", "123456789", &globalContext, &store);
assertCalculationIs("1234567", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "1234567", "1234567", "1234567", &globalContext, &store);
assertCalculationIs("123456789", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "123456789", "1.234568ᴇ8", "123456789", &globalContext, &store);
assertCalculationIs("1234567", DisplayOutput::ApproximateOnly, EqualSign::Equal, "1234567", "1234567", "1234567", &globalContext, &store);
}
QUIZ_CASE(calculation_display_exact_approximate) {
Shared::GlobalContext globalContext;
CalculationStore store;
CalculationStore store(calculationBuffer,calculationBufferSize);
assertCalculationIs("1/2", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Equal, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("1/3", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("1/0", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
assertCalculationIs("2x-x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
assertCalculationIs("[[1,2,3]]", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("[[1,x,3]]", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "undef", "undef", &globalContext, &store);
assertCalculationIs("28^7", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("3+√(2)→a", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(2)+3", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("1/2", DisplayOutput::ExactAndApproximate, EqualSign::Equal, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("1/3", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("1/0", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
assertCalculationIs("2x-x", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
assertCalculationIs("[[1,2,3]]", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("[[1,x,3]]", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "undef", "undef", &globalContext, &store);
assertCalculationIs("28^7", DisplayOutput::ExactAndApproximate, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("3+√(2)→a", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "√(2)+3", nullptr, nullptr, &globalContext, &store);
Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy();
assertCalculationIs("3+2→a", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "5", "5", "5", &globalContext, &store);
assertCalculationIs("3+2→a", DisplayOutput::ApproximateOnly, EqualSign::Equal, "5", "5", "5", &globalContext, &store);
Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy();
assertCalculationIs("3→a", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", "3", "3", &globalContext, &store);
assertCalculationIs("3→a", DisplayOutput::ApproximateOnly, EqualSign::Equal, "3", "3", "3", &globalContext, &store);
Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy();
assertCalculationIs("3+x→f(x)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "x+3", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("3+x→f(x)", DisplayOutput::ExactOnly, EqualSign::Unknown, "x+3", nullptr, nullptr, &globalContext, &store);
Ion::Storage::sharedStorage()->recordNamed("f.func").destroy();
assertCalculationIs("1+1+random()", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("1+1+round(1.343,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3.34", "3.34", &globalContext, &store);
assertCalculationIs("randint(2,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "5", "5", "5", &globalContext, &store);
assertCalculationIs("confidence(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("prediction(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("prediction95(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("1+1+random()", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("1+1+round(1.343,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "3.34", "3.34", &globalContext, &store);
assertCalculationIs("randint(2,2)+3", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "5", "5", "5", &globalContext, &store);
assertCalculationIs("confidence(0.5,2)+3", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("prediction(0.5,2)+3", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("prediction95(0.5,2)+3", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
}
QUIZ_CASE(calculation_symbolic_computation) {
Shared::GlobalContext globalContext;
CalculationStore store;
CalculationStore store(calculationBuffer,calculationBufferSize);
assertCalculationIs("x+x+1+3+√(π)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
assertCalculationIs("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
assertCalculationIs("1+x→f(x)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "x+1", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
assertCalculationIs("f(2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", "3", "3", &globalContext, &store);
assertCalculationIs("2→x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "2", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("x+x+1+3+√(π)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(π)+8", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("x+x+1+3+√(π)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
assertCalculationIs("f(x)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
assertCalculationIs("1+x→f(x)", DisplayOutput::ExactOnly, EqualSign::Unknown, "x+1", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("f(x)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store);
assertCalculationIs("f(2)", DisplayOutput::ApproximateOnly, EqualSign::Equal, "3", "3", "3", &globalContext, &store);
assertCalculationIs("2→x", DisplayOutput::ApproximateOnly, EqualSign::Equal, "2", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("f(x)", DisplayOutput::ApproximateOnly, EqualSign::Equal, "3", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("x+x+1+3+√(π)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "√(π)+8", nullptr, nullptr, &globalContext, &store);
Ion::Storage::sharedStorage()->recordNamed("f.func").destroy();
Ion::Storage::sharedStorage()->recordNamed("x.exp").destroy();
@@ -131,18 +155,18 @@ QUIZ_CASE(calculation_symbolic_computation) {
QUIZ_CASE(calculation_symbolic_computation_and_parametered_expressions) {
Shared::GlobalContext globalContext;
CalculationStore store;
CalculationStore store(calculationBuffer,calculationBufferSize);
assertCalculationIs("int((^(-x))-x^(0.5), x, 0, 3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); // Tests a bug with symbolic computation
assertCalculationIs("int(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store);
assertCalculationIs("sum(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3", "3", &globalContext, &store);
assertCalculationIs("product(x,x,1,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store);
assertCalculationIs("diff(x^2,x,3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "6", "6", &globalContext, &store);
assertCalculationIs("2→x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("int(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store);
assertCalculationIs("sum(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3", "3", &globalContext, &store);
assertCalculationIs("product(x,x,1,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store);
assertCalculationIs("diff(x^2,x,3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "6", "6", &globalContext, &store);
assertCalculationIs("int((^(-x))-x^(0.5), x, 0, 3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); // Tests a bug with symbolic computation
assertCalculationIs("int(x,x,0,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store);
assertCalculationIs("sum(x,x,0,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "3", "3", &globalContext, &store);
assertCalculationIs("product(x,x,1,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store);
assertCalculationIs("diff(x^2,x,3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "6", "6", &globalContext, &store);
assertCalculationIs("2→x", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store);
assertCalculationIs("int(x,x,0,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store);
assertCalculationIs("sum(x,x,0,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "3", "3", &globalContext, &store);
assertCalculationIs("product(x,x,1,2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store);
assertCalculationIs("diff(x^2,x,3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "6", "6", &globalContext, &store);
Ion::Storage::sharedStorage()->recordNamed("x.exp").destroy();
}
@@ -150,34 +174,34 @@ QUIZ_CASE(calculation_symbolic_computation_and_parametered_expressions) {
QUIZ_CASE(calculation_complex_format) {
Shared::GlobalContext globalContext;
CalculationStore store;
CalculationStore store(calculationBuffer,calculationBufferSize);
Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Real);
assertCalculationIs("1+𝐢", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "1+𝐢", "1+𝐢", &globalContext, &store);
assertCalculationIs("√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "unreal", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("ln(-2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store);
assertCalculationIs("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store);
assertCalculationIs("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "-2", "-2", &globalContext, &store);
assertCalculationIs("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "4", "4", &globalContext, &store);
assertCalculationIs("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store);
assertCalculationIs("1+𝐢", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "1+𝐢", "1+𝐢", &globalContext, &store);
assertCalculationIs("√(-1)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, "unreal", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("ln(-2)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store);
assertCalculationIs("√(-1)×√(-1)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store);
assertCalculationIs("(-8)^(1/3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "-2", "-2", &globalContext, &store);
assertCalculationIs("(-8)^(2/3)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "4", "4", &globalContext, &store);
assertCalculationIs("(-2)^(1/4)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store);
Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Cartesian);
assertCalculationIs("1+𝐢", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "1+𝐢", "1+𝐢", &globalContext, &store);
assertCalculationIs("√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "𝐢", "𝐢", &globalContext, &store);
assertCalculationIs("ln(-2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ln(-2)", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "-1", "-1", &globalContext, &store);
assertCalculationIs("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "1+√(3)×𝐢", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "-2+2×√(3)×𝐢", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "root(8,4)/2+root(8,4)/2×𝐢", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("1+𝐢", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "1+𝐢", "1+𝐢", &globalContext, &store);
assertCalculationIs("√(-1)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "𝐢", "𝐢", &globalContext, &store);
assertCalculationIs("ln(-2)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "ln(-2)", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("√(-1)×√(-1)", DisplayOutput::ApproximateOnly, EqualSign::Unknown, nullptr, "-1", "-1", &globalContext, &store);
assertCalculationIs("(-8)^(1/3)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "1+√(3)×𝐢", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("(-8)^(2/3)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "-2+2×√(3)×𝐢", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("(-2)^(1/4)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "root(8,4)/2+root(8,4)/2×𝐢", nullptr, nullptr, &globalContext, &store);
Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Polar);
assertCalculationIs("1+𝐢", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(2)×^\u0012π/4×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("√(-1)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "^\u0012π/2×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("ln(-2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ln(-2)", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "^\u00123.141593×𝐢\u0013", "^\u00123.1415926535898×𝐢\u0013", &globalContext, &store);
assertCalculationIs("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "2×^\u0012π/3×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "4×^\u0012\u00122×π\u0013/3×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "root(2,4)×^\u0012π/4×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("1+𝐢", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "√(2)×^\u0012π/4×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("√(-1)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "^\u0012π/2×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("ln(-2)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "ln(-2)", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("√(-1)×√(-1)", DisplayOutput::ExactAndApproximate, EqualSign::Unknown, nullptr, "^\u00123.141593×𝐢\u0013", "^\u00123.1415926535898×𝐢\u0013", &globalContext, &store);
assertCalculationIs("(-8)^(1/3)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "2×^\u0012π/3×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("(-8)^(2/3)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "4×^\u0012\u00122×π\u0013/3×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
assertCalculationIs("(-2)^(1/4)", DisplayOutput::ExactAndApproximate, EqualSign::Approximation, "root(2,4)×^\u0012π/4×𝐢\u0013", nullptr, nullptr, &globalContext, &store);
Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Cartesian);
}

View File

@@ -89,16 +89,18 @@ App::App(Snapshot * snapshot) :
, snapshot->lockOnConsole()
#endif
),
m_listFooter(&m_codeStackViewController, &m_menuController, &m_menuController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGrey, ButtonRowController::Size::Large),
m_listFooter(&m_codeStackViewController, &m_menuController, &m_menuController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGray, ButtonRowController::Size::Large),
m_menuController(&m_listFooter, this, snapshot->scriptStore(), &m_listFooter),
m_codeStackViewController(&m_modalViewController, &m_listFooter),
m_variableBoxController(snapshot->scriptStore())
{
Clipboard::sharedClipboard()->enterPython();
}
App::~App() {
assert(!m_consoleController.inputRunLoopActive());
deinitPython();
Clipboard::sharedClipboard()->exitPython();
}
bool App::handleEvent(Ion::Events::Event event) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -67,11 +67,19 @@ void EditorView::GutterView::drawRect(KDContext * ctx, KDRect rect) const {
KDCoordinate firstLine = m_offset / glyphSize.height();
KDCoordinate firstLinePixelOffset = m_offset - firstLine * glyphSize.height();
char lineNumber[4];
char lineNumber[k_lineNumberCharLength];
int numberOfLines = bounds().height() / glyphSize.height() + 1;
for (int i=0; i<numberOfLines; i++) {
Poincare::Integer line(i + firstLine + 1);
line.serialize(lineNumber, 4);
// Only the first two digits are displayed
int lineNumberValue = (i + firstLine + 1) % 100;
Poincare::Integer line(lineNumberValue);
if (firstLine < 10 || lineNumberValue >= 10) {
line.serialize(lineNumber, k_lineNumberCharLength);
} else {
// Add a leading "0"
lineNumber[0] = '0';
line.serialize(lineNumber + 1, k_lineNumberCharLength - 1);
}
KDCoordinate leftPadding = (2 - strlen(lineNumber)) * glyphSize.width();
ctx->drawString(
lineNumber,

View File

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

View File

@@ -1,44 +1,20 @@
#include "helpers.h"
#include <string.h>
#include <ion/unicode/code_point.h>
#include <ion.h>
#include <escher/clipboard.h>
namespace Code {
namespace Helpers {
class EventTextPair {
public:
constexpr EventTextPair(Ion::Events::Event event, const char * text) : m_event(event), m_text(text) {}
Ion::Events::Event event() const { return m_event; }
const char * text() const { return m_text; }
private:
const Ion::Events::Event m_event;
const char * m_text;
};
static_assert('\x11' == UCodePointEmpty, "Unicode error");
static constexpr EventTextPair sEventTextMap[] = {
EventTextPair(Ion::Events::XNT, "x"),
EventTextPair(Ion::Events::Exp, "exp(\x11)"),
EventTextPair(Ion::Events::Ln, "log(\x11)"),
EventTextPair(Ion::Events::Log, "log10(\x11)"),
EventTextPair(Ion::Events::Imaginary, "1j"),
EventTextPair(Ion::Events::Power, "**"),
EventTextPair(Ion::Events::Pi, "pi"),
EventTextPair(Ion::Events::Sqrt, "sqrt(\x11)"),
EventTextPair(Ion::Events::Square, "**2"),
EventTextPair(Ion::Events::Multiplication, "*"),
EventTextPair(Ion::Events::EE, "e"),
};
const char * PythonTextForEvent(Ion::Events::Event event) {
for (size_t i=0; i<sizeof(sEventTextMap)/sizeof(sEventTextMap[0]); i++) {
if (event == sEventTextMap[i].event()) {
return sEventTextMap[i].text();
for (size_t i=0; i<NumberOfPythonTextPairs; i++) {
UTF8Helper::TextPair pair = PythonTextPairs[i];
if (event.text() == pair.firstString()) {
return pair.secondString();
}
if (event == Ion::Events::XNT) {
return "x";
}
}
return nullptr;
}
}
}

View File

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

View File

@@ -131,7 +131,7 @@ const ToolboxMessageTree MatplotlibPyplotModuleChildren[] = {
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPink, I18n::Message::PythonColorPink, false),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorOrange, I18n::Message::PythonColorOrange, false),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPurple, I18n::Message::PythonColorPurple, false),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGrey, I18n::Message::PythonColorGrey, false)
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGray, I18n::Message::PythonColorGray, false)
};
const ToolboxMessageTree TurtleModuleChildren[] = {
@@ -168,7 +168,7 @@ const ToolboxMessageTree TurtleModuleChildren[] = {
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPink, I18n::Message::PythonColorPink, false),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorOrange, I18n::Message::PythonColorOrange, false),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPurple, I18n::Message::PythonColorPurple, false),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGrey, I18n::Message::PythonColorGrey, false)
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGray, I18n::Message::PythonColorGray, false)
};
const ToolboxMessageTree RandomModuleChildren[] = {
@@ -341,8 +341,8 @@ const ToolboxMessageTree catalogChildren[] = {
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPixel, I18n::Message::PythonGetPixel),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetrandbits, I18n::Message::PythonGetrandbits),
ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandGoto, I18n::Message::PythonTurtleGoto),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGray, I18n::Message::PythonColorGray, false),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGreen, I18n::Message::PythonColorGreen, false),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGrey, I18n::Message::PythonColorGrey, false),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGrid, I18n::Message::PythonGrid),
ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandHeading, I18n::Message::PythonTurtleHeading, false),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandHex, I18n::Message::PythonHex),

View File

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

View File

@@ -13,7 +13,7 @@ void ScriptNodeCell::ScriptNodeView::drawRect(KDContext * ctx, KDRect rect) cons
// If it exists, draw the description name.
const char * descriptionName = m_scriptNode->description();
if (descriptionName != nullptr) {
ctx->drawString(descriptionName, KDPoint(0, m_frame.height() - k_bottomMargin - k_font->glyphSize().height()), k_font, Palette::GreyDark, backgroundColor);
ctx->drawString(descriptionName, KDPoint(0, m_frame.height() - k_bottomMargin - k_font->glyphSize().height()), k_font, Palette::GrayDark, backgroundColor);
}
// Draw the node name

View File

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

View File

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

View File

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

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

View File

@@ -1,16 +1,13 @@
#include "exam_mode_configuration.h"
using namespace Poincare;
constexpr Shared::SettingsMessageTree s_examModeMode[] = {Shared::SettingsMessageTree(I18n::Message::ExamModeModeStandard), Shared::SettingsMessageTree(I18n::Message::ExamModeModeNoSym), Shared::SettingsMessageTree(I18n::Message::ExamModeModeNoSymNoText)};
constexpr Shared::SettingsMessageTree ExamModeConfiguration::s_modelExamChildren[] = {Shared::SettingsMessageTree(I18n::Message::ExamModeMode, s_examModeMode), Shared::SettingsMessageTree(I18n::Message::ActivateExamMode)};
constexpr Shared::SettingsMessageTree ExamModeConfiguration::s_modelExamChildren[] = {Shared::SettingsMessageTree(I18n::Message::ActivateExamMode), Shared::SettingsMessageTree(I18n::Message::Default)};
int ExamModeConfiguration::numberOfAvailableExamMode() {
return 2;
return 1;
}
GlobalPreferences::ExamMode ExamModeConfiguration::examModeAtIndex(int index) {
return (s_modelExamChildren[index].label() == I18n::Message::ExamModeModeStandard) ? GlobalPreferences::ExamMode::Standard : GlobalPreferences::ExamMode::NoSym;
return GlobalPreferences::ExamMode::Standard;
}
I18n::Message ExamModeConfiguration::examModeActivationMessage(int index) {
@@ -22,23 +19,20 @@ I18n::Message ExamModeConfiguration::examModeActivationWarningMessage(GlobalPref
I18n::Message warnings[] = {I18n::Message::ExitExamMode1, I18n::Message::ExitExamMode2, I18n::Message::Default};
return warnings[line];
}
assert(mode == GlobalPreferences::ExamMode::Standard || mode == GlobalPreferences::ExamMode::NoSym || mode == GlobalPreferences::ExamMode::NoSymNoText);
assert(mode == GlobalPreferences::ExamMode::Standard);
I18n::Message warnings[] = {I18n::Message::ActiveExamModeMessage1, I18n::Message::ActiveExamModeMessage2, I18n::Message::ActiveExamModeMessage3};
return warnings[line];
}
KDColor ExamModeConfiguration::examModeColor(GlobalPreferences::ExamMode mode) {
assert(mode == GlobalPreferences::ExamMode::Standard || mode == GlobalPreferences::ExamMode::NoSym || mode == GlobalPreferences::ExamMode::NoSymNoText);
assert(mode == GlobalPreferences::ExamMode::Standard);
return KDColorRed;
}
bool ExamModeConfiguration::appIsForbiddenInExamMode(App::Descriptor::ExaminationLevel appExaminationLevel, GlobalPreferences::ExamMode mode) {
if (mode == GlobalPreferences::ExamMode::NoSymNoText) {
return appExaminationLevel == App::Descriptor::ExaminationLevel::Basic;
}
bool ExamModeConfiguration::appIsForbiddenInExamMode(I18n::Message appName, GlobalPreferences::ExamMode mode) {
return false;
}
bool ExamModeConfiguration::exactExpressionsAreForbidden(GlobalPreferences::ExamMode mode) {
return mode == GlobalPreferences::ExamMode::NoSymNoText ? true : false;
}
return false;
}

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 "apps_container.h"
#include "exam_mode_configuration.h"
#include <apps/i18n.h>
#include "global_preferences.h"
#include <assert.h>
#include <poincare/preferences.h>
using namespace Poincare;
ExamPopUpController::ExamPopUpController(ExamPopUpControllerDelegate * delegate) :
ViewController(nullptr),
m_contentView(this),
PopUpController(
k_numberOfLines,
Invocation(
[](void * context, void * sender) {
ExamPopUpController * controller = (ExamPopUpController *)context;
GlobalPreferences::ExamMode mode = controller->targetExamMode();
assert(mode != GlobalPreferences::ExamMode::Unknown);
GlobalPreferences::sharedGlobalPreferences()->setExamMode(mode);
AppsContainer * container = AppsContainer::sharedAppsContainer();
if (mode == GlobalPreferences::ExamMode::Off) {
Ion::LED::setColor(KDColorBlack);
Ion::LED::updateColorWithPlugAndCharge();
} else {
container->activateExamMode(mode);
}
container->refreshPreferences();
Container::activeApp()->dismissModalViewController();
return true;
}, this)
),
m_targetExamMode(GlobalPreferences::ExamMode::Unknown),
m_delegate(delegate)
{
@@ -18,11 +34,9 @@ ExamPopUpController::ExamPopUpController(ExamPopUpControllerDelegate * delegate)
void ExamPopUpController::setTargetExamMode(GlobalPreferences::ExamMode mode) {
m_targetExamMode = mode;
m_contentView.setMessagesForExamMode(mode);
}
View * ExamPopUpController::view() {
return &m_contentView;
for (int i = 0; i < k_numberOfLines; i++) {
m_contentView.setMessage(i, ExamModeConfiguration::examModeActivationWarningMessage(mode, i));
}
}
void ExamPopUpController::viewDidDisappear() {
@@ -30,103 +44,3 @@ void ExamPopUpController::viewDidDisappear() {
m_delegate->examDeactivatingPopUpIsDismissed();
}
}
void ExamPopUpController::didBecomeFirstResponder() {
m_contentView.setSelectedButton(0);
}
bool ExamPopUpController::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::Left && m_contentView.selectedButton() == 1) {
m_contentView.setSelectedButton(0);
return true;
}
if (event == Ion::Events::Right && m_contentView.selectedButton() == 0) {
m_contentView.setSelectedButton(1);
return true;
}
return false;
}
ExamPopUpController::ContentView::ContentView(Responder * parentResponder) :
m_cancelButton(parentResponder, I18n::Message::Cancel, Invocation([](void * context, void * sender) {
Container::activeApp()->dismissModalViewController();
return true;
}, parentResponder), KDFont::SmallFont),
m_okButton(parentResponder, I18n::Message::Ok, Invocation([](void * context, void * sender) {
ExamPopUpController * controller = (ExamPopUpController *)context;
GlobalPreferences::ExamMode mode = controller->targetExamMode();
assert(mode != GlobalPreferences::ExamMode::Unknown);
GlobalPreferences::sharedGlobalPreferences()->setExamMode(mode);
AppsContainer * container = AppsContainer::sharedAppsContainer();
if (mode == GlobalPreferences::ExamMode::Off) {
Ion::LED::setColor(KDColorBlack);
Ion::LED::updateColorWithPlugAndCharge();
} else {
container->activateExamMode(mode);
}
container->refreshPreferences();
Container::activeApp()->dismissModalViewController();
return true;
}, parentResponder), KDFont::SmallFont),
m_warningTextView(KDFont::SmallFont, I18n::Message::Warning, 0.5, 0.5, KDColorWhite, KDColorBlack),
m_messageTextViews{}
{
for (int i = 0; i < k_maxNumberOfLines; i++) {
m_messageTextViews[i].setFont(KDFont::SmallFont);
m_messageTextViews[i].setAlignment(0.5f, 0.5f);
m_messageTextViews[i].setBackgroundColor(KDColorBlack);
m_messageTextViews[i].setTextColor(KDColorWhite);
}
}
void ExamPopUpController::ContentView::drawRect(KDContext * ctx, KDRect rect) const {
ctx->fillRect(bounds(), KDColorBlack);
}
void ExamPopUpController::ContentView::setSelectedButton(int selectedButton) {
m_cancelButton.setHighlighted(selectedButton == 0);
m_okButton.setHighlighted(selectedButton == 1);
Container::activeApp()->setFirstResponder(selectedButton == 0 ? &m_cancelButton : &m_okButton);
}
int ExamPopUpController::ContentView::selectedButton() {
if (m_cancelButton.isHighlighted()) {
return 0;
}
return 1;
}
void ExamPopUpController::ContentView::setMessagesForExamMode(GlobalPreferences::ExamMode mode) {
for (int i = 0; i < k_maxNumberOfLines; i++) {
m_messageTextViews[i].setMessage(ExamModeConfiguration::examModeActivationWarningMessage(mode, i));
}
}
int ExamPopUpController::ContentView::numberOfSubviews() const {
return 6;
}
View * ExamPopUpController::ContentView::subviewAtIndex(int index) {
switch (index) {
case 0:
return &m_warningTextView;
case 4:
return &m_cancelButton;
case 5:
return &m_okButton;
default:
return &m_messageTextViews[index-1];
}
}
void ExamPopUpController::ContentView::layoutSubviews(bool force) {
KDCoordinate height = bounds().height();
KDCoordinate width = bounds().width();
KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height();
m_warningTextView.setFrame(KDRect(0, k_topMargin, width, textHeight), force);
for (int i = 0; i < k_maxNumberOfLines; i++) {
m_messageTextViews[i].setFrame(KDRect(0, k_topMargin+k_paragraphHeight+(i+1)*textHeight, width, textHeight), force);
}
m_cancelButton.setFrame(KDRect(k_buttonMargin, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight), force);
m_okButton.setFrame(KDRect(2*k_buttonMargin+(width-3*k_buttonMargin)/2, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight), force);
}

View File

@@ -1,53 +1,20 @@
#ifndef APPS_EXAM_POP_UP_CONTROLLER_H
#define APPS_EXAM_POP_UP_CONTROLLER_H
#include <escher.h>
#include <escher/pop_up_controller.h>
#include "exam_pop_up_controller_delegate.h"
#include "global_preferences.h"
class HighContrastButton : public Button {
public:
using Button::Button;
KDColor highlightedBackgroundColor() const override { return Palette::ButtonBackgroundSelectedHighContrast; }
};
class ExamPopUpController : public ViewController {
class ExamPopUpController : public PopUpController {
public:
ExamPopUpController(ExamPopUpControllerDelegate * delegate);
void setTargetExamMode(GlobalPreferences::ExamMode mode);
GlobalPreferences::ExamMode targetExamMode() const { return m_targetExamMode; }
// View Controller
View * view() override;
void viewDidDisappear() override;
// Responder
void didBecomeFirstResponder() override;
bool handleEvent(Ion::Events::Event event) override;
private:
class ContentView : public View {
public:
ContentView(Responder * parentResponder);
void drawRect(KDContext * ctx, KDRect rect) const override;
void setSelectedButton(int selectedButton);
int selectedButton();
void setMessagesForExamMode(GlobalPreferences::ExamMode mode);
private:
constexpr static KDCoordinate k_buttonMargin = 10;
constexpr static KDCoordinate k_buttonHeight = 20;
constexpr static KDCoordinate k_topMargin = 12;
constexpr static KDCoordinate k_paragraphHeight = 20;
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews(bool force = false) override;
HighContrastButton m_cancelButton;
HighContrastButton m_okButton;
MessageTextView m_warningTextView;
constexpr static int k_maxNumberOfLines = 3;
MessageTextView m_messageTextViews[k_maxNumberOfLines];
};
ContentView m_contentView;
constexpr static int k_numberOfLines = 3;
GlobalPreferences::ExamMode m_targetExamMode;
ExamPopUpControllerDelegate * m_delegate;
};
#endif

View File

@@ -16,6 +16,12 @@ public:
static GlobalPreferences * sharedGlobalPreferences();
I18n::Language language() const { return m_language; }
void setLanguage(I18n::Language language) { m_language = language; }
I18n::Country country() const { return m_country; }
void setCountry(I18n::Country country) { m_country = country; }
CountryPreferences::AvailableExamModes availableExamModes() const { return I18n::CountryPreferencesArray[static_cast<uint8_t>(m_country)].availableExamModes(); }
CountryPreferences::MethodForQuartiles methodForQuartiles() const { return I18n::CountryPreferencesArray[static_cast<uint8_t>(m_country)].methodForQuartiles(); }
Poincare::Preferences::UnitFormat unitFormat() const { return I18n::CountryPreferencesArray[static_cast<uint8_t>(m_country)].unitFormat(); }
CountryPreferences::HomeAppsLayout homeAppsLayout() const { return I18n::CountryPreferencesArray[static_cast<uint8_t>(m_country)].homeAppsLayout(); }
bool isInExamMode() const { return (int8_t)examMode() > 0; }
bool isInExamModeSymbolic() const { return !((int8_t)examMode() > 1); }
ExamMode examMode() const;
@@ -30,15 +36,18 @@ public:
void setFont(const KDFont * font) { m_font = font; }
constexpr static int NumberOfBrightnessStates = 15;
private:
static_assert(I18n::NumberOfLanguages > 0, "I18n::NumberOfLanguages is not superior to 0"); // There should already have be an error when processing an empty EPSILON_I18N flag
static_assert(I18n::NumberOfLanguages > 0, "I18n::NumberOfLanguages is not superior to 0"); // There should already have been an error when processing an empty EPSILON_I18N flag
static_assert(I18n::NumberOfCountries > 0, "I18n::NumberOfCountries is not superior to 0"); // There should already have been an error when processing an empty EPSILON_COUNTRIES flag
GlobalPreferences() :
m_language((I18n::Language)0),
m_country((I18n::Country)0),
m_examMode(ExamMode::Unknown),
m_tempExamMode(ExamMode::Standard),
m_showPopUp(true),
m_brightnessLevel(Ion::Backlight::MaxBrightness),
m_font(KDFont::LargeFont) {}
I18n::Language m_language;
I18n::Country m_country;
static_assert((int8_t)GlobalPreferences::ExamMode::Off == 0, "GlobalPreferences::isInExamMode() is not right");
static_assert((int8_t)GlobalPreferences::ExamMode::Unknown < 0, "GlobalPreferences::isInExamMode() is not right");
mutable ExamMode m_examMode;

View File

@@ -1,9 +1,12 @@
apps += Graph::App
app_headers += apps/graph/app.h
app_graph_test_src = $(addprefix apps/graph/,\
continuous_function_store.cpp \
)
app_graph_src = $(addprefix apps/graph/,\
app.cpp \
continuous_function_store.cpp \
graph/banner_view.cpp \
graph/calculation_graph_controller.cpp \
graph/calculation_parameter_controller.cpp \
@@ -31,8 +34,15 @@ app_graph_src = $(addprefix apps/graph/,\
values/values_controller.cpp \
)
app_graph_src += $(app_graph_test_src)
apps_src += $(app_graph_src)
i18n_files += $(call i18n_without_universal_for,graph/base)
tests_src += $(addprefix apps/graph/test/,\
caching.cpp \
helper.cpp \
ranges.cpp \
)
$(eval $(call depends_on_image,apps/graph/app.cpp,apps/graph/graph_icon.png))

View File

@@ -55,10 +55,10 @@ void App::Snapshot::tidy() {
App::App(Snapshot * snapshot) :
FunctionApp(snapshot, &m_inputViewController),
m_listController(&m_listFooter, &m_listHeader, &m_listFooter, this),
m_listFooter(&m_listHeader, &m_listController, &m_listController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGrey),
m_listFooter(&m_listHeader, &m_listController, &m_listController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGray),
m_listHeader(&m_listStackViewController, &m_listFooter, &m_listController),
m_listStackViewController(&m_tabViewController, &m_listHeader),
m_graphController(&m_graphAlternateEmptyViewController, this, snapshot->graphRange(), snapshot->cursor(), snapshot->indexFunctionSelectedByCursor(), snapshot->modelVersion(), snapshot->previousModelsVersions(), snapshot->rangeVersion(), snapshot->angleUnitVersion(), &m_graphHeader),
m_graphController(&m_graphAlternateEmptyViewController, this, snapshot->graphRange(), snapshot->cursor(), snapshot->indexFunctionSelectedByCursor(), snapshot->rangeVersion(), &m_graphHeader),
m_graphAlternateEmptyViewController(&m_graphHeader, &m_graphController, &m_graphController),
m_graphHeader(&m_graphStackViewController, &m_graphAlternateEmptyViewController, &m_graphController),
m_graphStackViewController(&m_tabViewController, &m_graphHeader),

View File

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

View File

@@ -16,8 +16,9 @@ public:
return recordSatisfyingTestAtIndex(i, &isFunctionActiveOfType, &plotType);
}
Shared::ExpiringPointer<Shared::ContinuousFunction> modelForRecord(Ion::Storage::Record record) const { return Shared::ExpiringPointer<Shared::ContinuousFunction>(static_cast<Shared::ContinuousFunction *>(privateModelForRecord(record))); }
private:
Shared::ContinuousFunctionCache * cacheAtIndex(int i) const { return (i < Shared::ContinuousFunctionCache::k_numberOfAvailableCaches) ? m_functionCaches + i : nullptr; }
Ion::Storage::Record::ErrorStatus addEmptyModel() override;
private:
const char * modelExtension() const override { return Ion::Storage::funcExtension; }
Shared::ExpressionModelHandle * setMemoizedModelAtIndex(int cacheIndex, Ion::Storage::Record record) const override;
Shared::ExpressionModelHandle * memoizedModelAtIndex(int cacheIndex) const override;
@@ -26,6 +27,8 @@ private:
return isFunctionActive(model, context) && plotType == static_cast<Shared::ContinuousFunction *>(model)->plotType();
}
mutable Shared::ContinuousFunction m_functions[k_maxNumberOfMemoizedModels];
mutable Shared::ContinuousFunctionCache m_functionCaches[Shared::ContinuousFunctionCache::k_numberOfAvailableCaches];
};
}

View File

@@ -30,7 +30,7 @@ void CalculationGraphController::viewWillAppear() {
m_isActive = true;
assert(App::app()->functionStore()->modelForRecord(m_record)->plotType() == Shared::ContinuousFunction::PlotType::Cartesian);
m_cursor->moveTo(pointOfInterest.x1(), pointOfInterest.x1(), pointOfInterest.x2());
m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth());
m_bannerView->setNumberOfSubviews(Shared::XYBannerView::k_numberOfSubviews);
reloadBannerView();
}
@@ -64,7 +64,7 @@ bool CalculationGraphController::handleEnter() {
return true;
}
bool CalculationGraphController::moveCursorHorizontally(int direction, bool fast) {
bool CalculationGraphController::moveCursorHorizontally(int direction, int scrollspeed) {
if (!m_isActive) {
return false;
}

View File

@@ -31,7 +31,7 @@ protected:
bool m_isActive;
private:
bool handleEnter() override;
bool moveCursorHorizontally(int direction, bool fast = false) override;
bool moveCursorHorizontally(int direction, int scrollSpeed = 1) override;
Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; }
Shared::CurveView * curveView() override { return m_graphView; }
};

View File

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

View File

@@ -7,8 +7,8 @@ using namespace Shared;
namespace Graph {
GraphController::GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * previousModelsVersions, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header) :
FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, curveViewRange, &m_view, cursor, indexFunctionSelectedByCursor, modelVersion, previousModelsVersions, rangeVersion, angleUnitVersion),
GraphController::GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, ButtonRowController * header) :
FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, curveViewRange, &m_view, cursor, indexFunctionSelectedByCursor, rangeVersion),
m_bannerView(this, inputEventHandlerDelegate, this),
m_view(curveViewRange, m_cursor, &m_bannerView, &m_cursorView),
m_graphRange(curveViewRange),
@@ -35,118 +35,10 @@ void GraphController::viewWillAppear() {
selectFunctionWithCursor(indexFunctionSelectedByCursor());
}
bool GraphController::defautRangeIsNormalized() const {
bool GraphController::defaultRangeIsNormalized() const {
return functionStore()->displaysNonCartesianFunctions();
}
void GraphController::interestingFunctionRange(ExpiringPointer<ContinuousFunction> f, float tMin, float tMax, float step, float * xm, float * xM, float * ym, float * yM) const {
Poincare::Context * context = textFieldDelegateApp()->localContext();
const int balancedBound = std::floor((tMax-tMin)/2/step);
for (int j = -balancedBound; j <= balancedBound ; j++) {
float t = (tMin+tMax)/2 + step * j;
Coordinate2D<float> xy = f->evaluateXYAtParameter(t, context);
float x = xy.x1();
float y = xy.x2();
if (!std::isnan(x) && !std::isinf(x) && !std::isnan(y) && !std::isinf(y)) {
*xm = std::min(*xm, x);
*xM = std::max(*xM, x);
*ym = std::min(*ym, y);
*yM = std::max(*yM, y);
}
}
}
void GraphController::interestingRanges(float * xm, float * xM, float * ym, float * yM) const {
float resultxMin = FLT_MAX;
float resultxMax = -FLT_MAX;
float resultyMin = FLT_MAX;
float resultyMax = -FLT_MAX;
assert(functionStore()->numberOfActiveFunctions() > 0);
int functionsCount = 0;
if (functionStore()->displaysNonCartesianFunctions(&functionsCount)) {
for (int i = 0; i < functionsCount; i++) {
ExpiringPointer<ContinuousFunction> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i));
if (f->plotType() == ContinuousFunction::PlotType::Cartesian) {
continue;
}
/* Scan x-range from the middle to the extrema in order to get balanced
* y-range for even functions (y = 1/x). */
double tMin = f->tMin();
double tMax = f->tMax();
assert(!std::isnan(tMin));
assert(!std::isnan(tMax));
assert(!std::isnan(f->rangeStep()));
interestingFunctionRange(f, tMin, tMax, f->rangeStep(), &resultxMin, &resultxMax, &resultyMin, &resultyMax);
}
if (resultxMin > resultxMax) {
resultxMin = - Range1D::k_default;
resultxMax = Range1D::k_default;
}
} else {
resultxMin = const_cast<GraphController *>(this)->interactiveCurveViewRange()->xMin();
resultxMax = const_cast<GraphController *>(this)->interactiveCurveViewRange()->xMax();
}
/* In practice, a step smaller than a pixel's width is needed for sampling
* the values of a function. Otherwise some relevant extremal values may be
* missed. */
for (int i = 0; i < functionsCount; i++) {
ExpiringPointer<ContinuousFunction> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i));
if (f->plotType() != ContinuousFunction::PlotType::Cartesian) {
continue;
}
/* Scan x-range from the middle to the extrema in order to get balanced
* y-range for even functions (y = 1/x). */
assert(!std::isnan(f->tMin()));
assert(!std::isnan(f->tMax()));
const double tMin = std::max(f->tMin(), resultxMin);
const double tMax = std::min(f->tMax(), resultxMax);
const double step = (tMax - tMin) / (2.0 * (m_view.bounds().width() - 1.0));
interestingFunctionRange(f, tMin, tMax, step, &resultxMin, &resultxMax, &resultyMin, &resultyMax);
}
if (resultyMin > resultyMax) {
resultyMin = - Range1D::k_default;
resultyMax = Range1D::k_default;
}
*xm = resultxMin;
*xM = resultxMax;
*ym = resultyMin;
*yM = resultyMax;
}
float GraphController::interestingXHalfRange() const {
float characteristicRange = 0.0f;
Poincare::Context * context = textFieldDelegateApp()->localContext();
ContinuousFunctionStore * store = functionStore();
int nbActiveFunctions = store->numberOfActiveFunctions();
double tMin = INFINITY;
double tMax = -INFINITY;
for (int i = 0; i < nbActiveFunctions; i++) {
ExpiringPointer<ContinuousFunction> f = store->modelForRecord(store->activeRecordAtIndex(i));
float fRange = f->expressionReduced(context).characteristicXRange(context, Poincare::Preferences::sharedPreferences()->angleUnit());
if (!std::isnan(fRange) && !std::isinf(fRange)) {
characteristicRange = std::max(fRange, characteristicRange);
}
// Compute the combined range of the functions
assert(f->plotType() == ContinuousFunction::PlotType::Cartesian); // So that tMin tMax represents xMin xMax
tMin = std::min<double>(tMin, f->tMin());
tMax = std::max<double>(tMax, f->tMax());
}
constexpr float rangeMultiplicator = 1.6f;
if (characteristicRange > 0.0f ) {
return rangeMultiplicator * characteristicRange;
}
float defaultXHalfRange = InteractiveCurveViewRangeDelegate::interestingXHalfRange();
assert(tMin <= tMax);
if (tMin >= -defaultXHalfRange && tMax <= defaultXHalfRange) {
/* If the combined Range of the functions is smaller than the default range,
* use it. */
float f = rangeMultiplicator * (float)std::max(std::fabs(tMin), std::fabs(tMax));
return (std::isnan(f) || std::isinf(f)) ? defaultXHalfRange : f;
}
return defaultXHalfRange;
}
void GraphController::selectFunctionWithCursor(int functionIndex) {
FunctionGraphController::selectFunctionWithCursor(functionIndex);
ExpiringPointer<ContinuousFunction> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(functionIndex));
@@ -165,9 +57,9 @@ void GraphController::reloadBannerView() {
reloadDerivativeInBannerViewForCursorOnFunction(m_cursor, record);
}
bool GraphController::moveCursorHorizontally(int direction, bool fast) {
bool GraphController::moveCursorHorizontally(int direction, int scrollSpeed) {
Ion::Storage::Record record = functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor());
return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, record, fast);
return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, record, scrollSpeed);
}
int GraphController::nextCurveIndexVertically(bool goingUp, int currentSelectedCurve, Poincare::Context * context) const {
@@ -187,10 +79,6 @@ double GraphController::defaultCursorT(Ion::Storage::Record record) {
return function->tMin();
}
bool GraphController::shouldSetDefaultOnModelChange() const {
return functionStore()->displaysNonCartesianFunctions();
}
void GraphController::jumpToLeftRightCurve(double t, int direction, int functionsCount, Ion::Storage::Record record) {
if (functionsCount == 1) {
return;

View File

@@ -15,28 +15,25 @@ namespace Graph {
class GraphController : public Shared::FunctionGraphController, public GraphControllerHelper {
public:
GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * previousModelsVersions, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header);
GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * rangeVersion, ButtonRowController * header);
I18n::Message emptyMessage() override;
void viewWillAppear() override;
bool displayDerivativeInBanner() const { return m_displayDerivativeInBanner; }
void setDisplayDerivativeInBanner(bool displayDerivative) { m_displayDerivativeInBanner = displayDerivative; }
float interestingXHalfRange() const override;
void interestingRanges(float * xm, float * xM, float * ym, float * yM) const override;
private:
int estimatedBannerNumberOfLines() const override { return 1 + m_displayDerivativeInBanner; }
void selectFunctionWithCursor(int functionIndex) override;
BannerView * bannerView() override { return &m_bannerView; }
void reloadBannerView() override;
bool moveCursorHorizontally(int direction, bool fast = false) override;
bool moveCursorHorizontally(int direction, int scrollSpeed = 1) override;
int nextCurveIndexVertically(bool goingUp, int currentSelectedCurve, Poincare::Context * context) const override;
double defaultCursorT(Ion::Storage::Record record) override;
Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; }
GraphView * functionGraphView() override { return &m_view; }
CurveParameterController * curveParameterController() override { return &m_curveParameterController; }
ContinuousFunctionStore * functionStore() const override { return static_cast<ContinuousFunctionStore *>(Shared::FunctionGraphController::functionStore()); }
bool defautRangeIsNormalized() const override;
bool defaultRangeIsNormalized() const override;
void interestingFunctionRange(Shared::ExpiringPointer<Shared::ContinuousFunction> f, float tMin, float tMax, float step, float * xm, float * xM, float * ym, float * yM) const;
bool shouldSetDefaultOnModelChange() const override;
void jumpToLeftRightCurve(double t, int direction, int functionsCount, Ion::Storage::Record record) override;
Shared::RoundCursorView m_cursorView;

View File

@@ -10,7 +10,7 @@ using namespace Poincare;
namespace Graph {
bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, bool fast) {
bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, int scrollSpeed) {
ExpiringPointer<ContinuousFunction> function = App::app()->functionStore()->modelForRecord(record);
double tCursorPosition = cursor->t();
double t = tCursorPosition;
@@ -27,11 +27,11 @@ bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCurso
function = App::app()->functionStore()->modelForRecord(record); // Reload the expiring pointer
double dir = (direction > 0 ? 1.0 : -1.0);
double step = function->plotType() == ContinuousFunction::PlotType::Cartesian ? range->xGridUnit()/numberOfStepsInGradUnit : (tMax-tMin)/k_definitionDomainDivisor;
if (fast) {
// TODO Navigate quicker the longer the user presses? (slow start)
step *= 5.0;
}
t += dir * step;
t += dir * step * scrollSpeed;
// If possible, round t so that f(x) matches f evaluated at displayed x
t = FunctionBannerDelegate::getValueDisplayedOnBanner(t, App::app()->localContext(), 0.05 * step, true);
t = std::max(tMin, std::min(tMax, t));
Coordinate2D<double> xy = function->evaluateXYAtParameter(t, App::app()->localContext());
cursor->moveTo(t, xy.x1(), xy.x2());

View File

@@ -11,7 +11,7 @@ class App;
class GraphControllerHelper {
protected:
bool privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, bool fast = false);
bool privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, int scrollSpeed = 1);
void reloadDerivativeInBannerViewForCursorOnFunction(Shared::CurveViewCursor * cursor, Ion::Storage::Record record);
virtual BannerView * bannerView() = 0;
private:

View File

@@ -1,6 +1,7 @@
#include "graph_view.h"
#include "../app.h"
#include <assert.h>
#include <algorithm>
using namespace Shared;
@@ -27,7 +28,8 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const {
const int activeFunctionsCount = functionStore->numberOfActiveFunctions();
for (int i = 0; i < activeFunctionsCount ; i++) {
Ion::Storage::Record record = functionStore->activeRecordAtIndex(i);
ExpiringPointer<ContinuousFunction> f = functionStore->modelForRecord(record);;
ExpiringPointer<ContinuousFunction> f = functionStore->modelForRecord(record);
ContinuousFunctionCache * cch = functionStore->cacheAtIndex(i);
Shared::ContinuousFunction::PlotType type = f->plotType();
Poincare::Expression e = f->expressionReduced(context());
if (e.isUndefined() || (
@@ -38,21 +40,24 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const {
}
float tmin = f->tMin();
float tmax = f->tMax();
/* The step is a fraction of tmax-tmin. We will evaluate the function at
* every step and if the consecutive dots are close enough, we won't
* evaluate any more dot within the step. We pick a very strange fraction
* denominator to avoid evaluating a periodic function periodically. For
* example, if tstep was (tmax - tmin)/10, the polar function r(θ) = sin(5θ)
* defined on 0..2π would be evaluated on r(0) = 0, r(π/5) = 0, r(2*π/5) = 0
* which would lead to no curve at all. With 10.0938275501223, the
* problematic functions are the functions whose period is proportionned to
* 10.0938275501223 which are hopefully rare enough.
* TODO: The drawCurve algorithm should use the derivative function to know
* how fast the function moves... */
float tstep = (tmax-tmin)/10.0938275501223f;
// Cartesian
float tCacheMin, tCacheStep, tStepNonCartesian;
if (type == ContinuousFunction::PlotType::Cartesian) {
float rectLeft = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin);
/* Here, tCacheMin can depend on rect (and change as the user move)
* because cache can be panned for cartesian curves, instead of being
* entirely invalidated. */
tCacheMin = std::isnan(rectLeft) ? tmin : std::max(tmin, rectLeft);
tCacheStep = pixelWidth();
} else {
tCacheMin = tmin;
// Compute tCacheStep and tStepNonCartesian
ContinuousFunctionCache::ComputeNonCartesianSteps(&tStepNonCartesian, &tCacheStep, tmax, tmin);
}
ContinuousFunctionCache::PrepareForCaching(f.operator->(), cch, tCacheMin, tCacheStep);
if (type == Shared::ContinuousFunction::PlotType::Cartesian) {
// Cartesian
drawCartesianCurve(ctx, rect, tmin, tmax, [](float t, void * model, void * context) {
ContinuousFunction * f = (ContinuousFunction *)model;
Poincare::Context * c = (Poincare::Context *)context;
@@ -72,18 +77,22 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const {
float maxAbscissa = pixelToFloat(Axis::Horizontal, rect.right());
drawSegment(ctx, rect, minAbscissa, tangentParameterA*minAbscissa+tangentParameterB, maxAbscissa, tangentParameterA*maxAbscissa+tangentParameterB, Palette::GraphTangent, false);
}
continue;
} else if (type == Shared::ContinuousFunction::PlotType::Polar) {
// Polar
drawPolarCurve(ctx, rect, tmin, tmax, tStepNonCartesian, [](float t, void * model, void * context) {
ContinuousFunction * f = (ContinuousFunction *)model;
Poincare::Context * c = (Poincare::Context *)context;
return f->evaluateXYAtParameter(t, c);
}, f.operator->(), context(), false, f->color());
} else {
// Parametric
assert(type == Shared::ContinuousFunction::PlotType::Parametric);
drawCurve(ctx, rect, tmin, tmax, tStepNonCartesian, [](float t, void * model, void * context) {
ContinuousFunction * f = (ContinuousFunction *)model;
Poincare::Context * c = (Poincare::Context *)context;
return f->evaluateXYAtParameter(t, c);
}, f.operator->(), context(), false, f->color());
}
// Polar or parametric
assert(
type == Shared::ContinuousFunction::PlotType::Polar ||
type == Shared::ContinuousFunction::PlotType::Parametric);
drawCurve(ctx, rect, tmin, tmax, tstep, [](float t, void * model, void * context) {
ContinuousFunction * f = (ContinuousFunction *)model;
Poincare::Context * c = (Poincare::Context *)context;
return f->evaluateXYAtParameter(t, c);
}, f.operator->(), context(), false, f->color());
}
}

View File

@@ -7,6 +7,19 @@ namespace Graph {
class GraphView : public Shared::FunctionGraphView {
public:
/* The step is a fraction of tmax-tmin. We will evaluate the function at
* every step and if the consecutive dots are close enough, we won't
* evaluate any more dot within the step. We pick a very strange fraction
* denominator to avoid evaluating a periodic function periodically. For
* example, if tstep was (tmax - tmin)/10, the polar function r(θ) = sin(5θ)
* defined on 0..2π would be evaluated on r(0) = 0, r(π/5) = 0, r(2*π/5) = 0
* which would lead to no curve at all. With 10.0938275501223, the
* problematic functions are the functions whose period is proportionned to
* 10.0938275501223 which are hopefully rare enough.
* TODO: The drawCurve algorithm should use the derivative function to know
* how fast the function moves... */
static constexpr float k_graphStepDenominator = 10.0938275501223f;
GraphView(Shared::InteractiveCurveViewRange * graphRange,
Shared::CurveViewCursor * cursor, Shared::BannerView * bannerView, Shared::CursorView * cursorView);
void reload() override;

View File

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

View File

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

View File

@@ -24,7 +24,7 @@ const char * TangentGraphController::title() {
void TangentGraphController::viewWillAppear() {
Shared::SimpleInteractiveCurveViewController::viewWillAppear();
m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth());
m_graphView->drawTangent(true);
m_graphView->setOkView(nullptr);
m_graphView->selectMainView(true);
@@ -51,7 +51,7 @@ bool TangentGraphController::textFieldDidFinishEditing(TextField * textField, co
assert(function->plotType() == Shared::ContinuousFunction::PlotType::Cartesian);
double y = function->evaluate2DAtParameter(floatBody, myApp->localContext()).x2();
m_cursor->moveTo(floatBody, floatBody, y);
interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), cursorRightMarginRatio(), cursorBottomMarginRatio(), cursorLeftMarginRatio(), curveView()->pixelWidth());
reloadBannerView();
curveView()->reload();
return true;
@@ -90,7 +90,7 @@ void TangentGraphController::reloadBannerView() {
m_bannerView->reload();
}
bool TangentGraphController::moveCursorHorizontally(int direction, bool fast) {
bool TangentGraphController::moveCursorHorizontally(int direction, int scrollSpeed) {
return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, m_record);
}

View File

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

View File

@@ -10,7 +10,13 @@ namespace Graph {
DomainParameterController::DomainParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) :
FloatParameterController<float>(parentResponder),
m_domainCells{},
m_record()
m_record(),
m_tempDomain(),
m_confirmPopUpController(Invocation([](void * context, void * sender) {
Container::activeApp()->dismissModalViewController();
((DomainParameterController *)context)->stackController()->pop();
return true;
}, this))
{
for (int i = 0; i < k_totalNumberOfCell; i++) {
m_domainCells[i].setParentResponder(&m_selectableTableView);
@@ -18,12 +24,10 @@ DomainParameterController::DomainParameterController(Responder * parentResponder
}
}
const char * DomainParameterController::title() {
return I18n::translate(I18n::Message::FunctionDomain);
}
int DomainParameterController::numberOfRows() const {
return k_totalNumberOfCell+1;
void DomainParameterController::viewWillAppear() {
// Initialize m_tempParameters to the extracted value.
extractParameters();
FloatParameterController::viewWillAppear();
}
void DomainParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) {
@@ -56,10 +60,6 @@ void DomainParameterController::willDisplayCellForIndex(HighlightCell * cell, in
FloatParameterController::willDisplayCellForIndex(cell, index);
}
int DomainParameterController::reusableParameterCellCount(int type) {
return k_totalNumberOfCell;
}
HighlightCell * DomainParameterController::reusableParameterCell(int index, int type) {
assert(index >= 0 && index < k_totalNumberOfCell);
return &m_domainCells[index];
@@ -70,20 +70,49 @@ bool DomainParameterController::handleEvent(Ion::Events::Event event) {
stackController()->pop();
return true;
}
if (event == Ion::Events::Back && !equalTempParameters()) {
// Open pop-up to confirm discarding values
Container::activeApp()->displayModalViewController(&m_confirmPopUpController, 0.f, 0.f, Metric::ExamPopUpTopMargin, Metric::PopUpRightMargin, Metric::ExamPopUpBottomMargin, Metric::PopUpLeftMargin);
return true;
}
return false;
}
float DomainParameterController::parameterAtIndex(int index) {
return index == 0 ? function()->tMin() : function()->tMax();
return index == 0 ? m_tempDomain.min() : m_tempDomain.max();
}
void DomainParameterController::extractParameters() {
setParameterAtIndex(0, function()->tMin());
setParameterAtIndex(1, function()->tMax());
/* Setting m_tempDomain tMin might affect m_tempDomain.max(), but setting tMax
* right after will not affect m_tempDomain.min() because Function's Range1D
* parameters are valid (tMax>tMin), and final tMin value is already set.
* Same happens in confirmParameters when setting function's parameters from
* valid m_tempDomain parameters. */
assert(equalTempParameters());
}
bool DomainParameterController::setParameterAtIndex(int parameterIndex, float f) {
// TODO: what to do if the xmin > xmax?
parameterIndex == 0 ? function()->setTMin(f) : function()->setTMax(f);
/* Setting Min (or Max) parameter can alter the previously set Max
* (or Min) parameter if Max <= Min. */
parameterIndex == 0 ? m_tempDomain.setMin(f) : m_tempDomain.setMax(f);
return true;
}
void DomainParameterController::confirmParameters() {
function()->setTMin(parameterAtIndex(0));
function()->setTMax(parameterAtIndex(1));
// See comment on Range1D initialization in extractParameters
assert(equalTempParameters());
}
bool DomainParameterController::equalTempParameters() {
return function()->tMin() == m_tempDomain.min() && function()->tMax() == m_tempDomain.max();
}
void DomainParameterController::buttonAction() {
confirmParameters();
StackViewController * stack = stackController();
stack->pop();
stack->pop();

View File

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

View File

@@ -8,7 +8,8 @@ namespace Graph {
TextFieldFunctionTitleCell::TextFieldFunctionTitleCell(ListController * listController, Orientation orientation, const KDFont * font) :
Shared::FunctionTitleCell(orientation),
Responder(listController),
m_textField(Shared::Function::k_parenthesedThetaArgumentByteLength, this, m_textFieldBuffer, k_textFieldBufferSize, k_textFieldBufferSize, nullptr, listController, font, 1.0f, 0.5f)
m_textField(Shared::Function::k_parenthesedThetaArgumentByteLength, this, m_textFieldBuffer, k_textFieldBufferSize, k_textFieldBufferSize, nullptr, listController, font, 1.0f, 0.5f),
m_textFieldBuffer("")
{
}
@@ -62,6 +63,11 @@ void TextFieldFunctionTitleCell::layoutSubviews(bool force) {
m_textField.setAlignment(horizontalAlignment, verticalAlignment());
}
void TextFieldFunctionTitleCell::reloadCell() {
layoutSubviews();
FunctionTitleCell::reloadCell();
}
void TextFieldFunctionTitleCell::didBecomeFirstResponder() {
if (isEditing()) {
Container::activeApp()->setFirstResponder(&m_textField);

View File

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

124
apps/graph/test/caching.cpp Normal file
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 "serial_number_controller.h"
#include "vblank_test_controller.h"
#include "../shared/shared_app.h"
namespace HardwareTest {
class App : public ::App {
public:
class Snapshot : public ::App::Snapshot {
class Snapshot : public ::SharedApp::Snapshot {
public:
App * unpack(Container * container) override;
Descriptor * descriptor() override;

View File

@@ -1,116 +1,25 @@
#include "pop_up_controller.h"
#include <apps/i18n.h>
#include "../apps_container.h"
#include <assert.h>
#include <escher/app.h>
namespace HardwareTest {
PopUpController::PopUpController() :
ViewController(nullptr),
m_contentView(this)
::PopUpController(
4,
Invocation(
[](void * context, void * sender) {
AppsContainer * appsContainer = AppsContainer::sharedAppsContainer();
bool switched = appsContainer->switchTo(appsContainer->hardwareTestAppSnapshot());
assert(switched);
(void) switched; // Silence compilation warning about unused variable.
return true;
}, this)
)
{
}
View * PopUpController::view() {
return &m_contentView;
}
void PopUpController::didBecomeFirstResponder() {
m_contentView.setSelectedButton(0);
}
bool PopUpController::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::Left && m_contentView.selectedButton() == 1) {
m_contentView.setSelectedButton(0);
return true;
}
if (event == Ion::Events::Right && m_contentView.selectedButton() == 0) {
m_contentView.setSelectedButton(1);
return true;
}
return false;
}
PopUpController::ContentView::ContentView(Responder * parentResponder) :
Responder(parentResponder),
m_cancelButton(this, I18n::Message::Cancel, Invocation([](void * context, void * sender) {
Container::activeApp()->dismissModalViewController();
return true;
}, this), KDFont::SmallFont),
m_okButton(this, I18n::Message::Ok, Invocation([](void * context, void * sender) {
AppsContainer * appsContainer = AppsContainer::sharedAppsContainer();
bool switched = appsContainer->switchTo(appsContainer->hardwareTestAppSnapshot());
assert(switched);
(void) switched; // Silence compilation warning about unused variable.
return true;
}, this), KDFont::SmallFont),
m_warningTextView(KDFont::SmallFont, I18n::Message::Warning, 0.5, 0.5, KDColorWhite, KDColorBlack),
m_messageTextView1(KDFont::SmallFont, I18n::Message::HardwareTestLaunch1, 0.5, 0.5, KDColorWhite, KDColorBlack),
m_messageTextView2(KDFont::SmallFont, I18n::Message::HardwareTestLaunch2, 0.5, 0.5, KDColorWhite, KDColorBlack),
m_messageTextView3(KDFont::SmallFont, I18n::Message::HardwareTestLaunch3, 0.5, 0.5, KDColorWhite, KDColorBlack),
m_messageTextView4(KDFont::SmallFont, I18n::Message::HardwareTestLaunch4, 0.5, 0.5, KDColorWhite, KDColorBlack)
{
}
void PopUpController::ContentView::drawRect(KDContext * ctx, KDRect rect) const {
ctx->fillRect(bounds(), KDColorBlack);
}
void PopUpController::ContentView::setSelectedButton(int selectedButton) {
m_cancelButton.setHighlighted(selectedButton == 0);
m_okButton.setHighlighted(selectedButton == 1);
if (selectedButton == 0) {
Container::activeApp()->setFirstResponder(&m_cancelButton);
} else {
Container::activeApp()->setFirstResponder(&m_okButton);
}
}
int PopUpController::ContentView::selectedButton() {
if (m_cancelButton.isHighlighted()) {
return 0;
}
return 1;
}
int PopUpController::ContentView::numberOfSubviews() const {
return 7;
}
View * PopUpController::ContentView::subviewAtIndex(int index) {
switch (index) {
case 0:
return &m_warningTextView;
case 1:
return &m_messageTextView1;
case 2:
return &m_messageTextView2;
case 3:
return &m_messageTextView3;
case 4:
return &m_messageTextView4;
case 5:
return &m_cancelButton;
case 6:
return &m_okButton;
default:
assert(false);
return nullptr;
}
}
void PopUpController::ContentView::layoutSubviews(bool force) {
KDCoordinate height = bounds().height();
KDCoordinate width = bounds().width();
KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height();
m_warningTextView.setFrame(KDRect(0, k_topMargin, width, textHeight), force);
m_messageTextView1.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+textHeight, width, textHeight), force);
m_messageTextView2.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+2*textHeight, width, textHeight), force);
m_messageTextView3.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+3*textHeight, width, textHeight), force);
m_messageTextView4.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+4*textHeight, width, textHeight), force);
m_cancelButton.setFrame(KDRect(k_buttonMargin, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight), force);
m_okButton.setFrame(KDRect(2*k_buttonMargin+(width-3*k_buttonMargin)/2, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight), force);
m_contentView.setMessage(0, I18n::Message::HardwareTestLaunch1);
m_contentView.setMessage(1, I18n::Message::HardwareTestLaunch2);
m_contentView.setMessage(2, I18n::Message::HardwareTestLaunch3);
m_contentView.setMessage(3, I18n::Message::HardwareTestLaunch4);
}
}

View File

@@ -1,43 +1,15 @@
#ifndef HARDWARE_TEST_POP_UP_CONTROLLER_H
#define HARDWARE_TEST_POP_UP_CONTROLLER_H
#ifndef POP_UP_CONTROLLER_H
#define POP_UP_CONTROLLER_H
#include <escher.h>
#include <escher/pop_up_controller.h>
namespace HardwareTest {
class PopUpController : public ViewController {
class PopUpController : public ::PopUpController {
public:
PopUpController();
View * view() override;
void didBecomeFirstResponder() override;
bool handleEvent(Ion::Events::Event event) override;
private:
class ContentView : public View, public Responder {
public:
ContentView(Responder * parentResponder);
void drawRect(KDContext * ctx, KDRect rect) const override;
void setSelectedButton(int selectedButton);
int selectedButton();
private:
constexpr static KDCoordinate k_buttonMargin = 10;
constexpr static KDCoordinate k_buttonHeight = 20;
constexpr static KDCoordinate k_topMargin = 8;
constexpr static KDCoordinate k_paragraphHeight = 20;
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
void layoutSubviews(bool force = false) override;
Button m_cancelButton;
Button m_okButton;
MessageTextView m_warningTextView;
MessageTextView m_messageTextView1;
MessageTextView m_messageTextView2;
MessageTextView m_messageTextView3;
MessageTextView m_messageTextView4;
};
ContentView m_contentView;
};
}
#endif

View File

@@ -1,9 +1,28 @@
app_home_src = $(addprefix apps/home/,\
app.cpp \
app_cell.cpp \
apps_layout.py \
controller.cpp \
)
apps_src += $(app_home_src)
i18n_files += $(call i18n_without_universal_for,home/base)
# Apps layout file generation
# The header is refered to as <apps/home/apps_layout.h> so make sure it's
# findable this way
SFLAGS += -I$(BUILD_DIR)
apps_layout = apps/home/apps_layout.csv
$(eval $(call rule_for, \
APPS_LAYOUT, \
apps/home/apps_layout.cpp, \
, \
$$(PYTHON) apps/home/apps_layout.py --layouts $(apps_layout) --header $$(subst .cpp,.h,$$@) --implementation $$@ --apps $$(EPSILON_APPS), \
global \
))
$(BUILD_DIR)/apps/home/apps_layout.h: $(BUILD_DIR)/apps/home/apps_layout.cpp

View File

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

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"
AppsCapital = "OMEGA"
ForbidenAppInExamMode1 = "Esta aplicación está"
ForbidenAppInExamMode2 = "prohibida en el modo examen"
ForbidenAppInExamMode1 = "Esta aplicación está prohibida"
ForbidenAppInExamMode2 = "en el modo de examen"

View File

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

View File

@@ -6,18 +6,22 @@
# properly draw upper case letters with accents, we remove them here.
# It works with Python 2 and Python 3
import sys
import re
import unicodedata
import argparse
import csv
import io
import re
import sys
import unicodedata
parser = argparse.ArgumentParser(description="Process some i18n files.")
parser.add_argument('--header', help='the .h file to generate')
parser.add_argument('--implementation', help='the .cpp file to generate')
parser.add_argument('--locales', nargs='+', help='locale to actually generate')
parser.add_argument('--countries', nargs='+', help='countries to actually generate')
parser.add_argument('--codepoints', help='the code_points.h file')
parser.add_argument('--countrypreferences', help='the country_preferences.csv file')
parser.add_argument('--languagepreferences', help='the language_preferences.csv file')
parser.add_argument('--files', nargs='+', help='an i18n file')
parser.add_argument('--generateISO6391locales', type=int, nargs='+', help='whether to generate the ISO6391 codes for the languages (for instance "en" for english)')
@@ -67,6 +71,18 @@ def split_line(line):
def locale_from_filename(filename):
return re.match(r".*\.([a-z]+)\.i18n", filename).group(1)
def check_redundancy(messages, data, locales):
redundant_names = set()
for name in messages:
redundancy = True
for i in range(1, len(locales)):
redundancy = redundancy and data[locales[i]][name] == data[locales[i-1]][name]
if redundancy:
redundant_names.add(name)
if (len(redundant_names) > 0):
sys.stderr.write("Some localized messages are redundant and can be made universal :\n\t" + "\n\t".join(sorted(redundant_names)) + "\n")
sys.exit(-1)
def parse_files(files):
data = {}
messages = set()
@@ -91,6 +107,7 @@ def parse_files(files):
else:
messages.add(name)
data[locale][name] = definition
#check_redundancy(messages, data, args.locales) # FIXME
return {"messages": sorted(messages), "universal_messages": sorted(universal_messages), "data": data}
def parse_codepoints(file):
@@ -114,46 +131,110 @@ def parse_codepoints(file):
codepoints = parse_codepoints(args.codepoints)
def print_header(data, path, locales):
def parse_csv_with_header(file):
res = []
with io.open(file, 'r', encoding='utf-8') as csvfile:
csvreader = csv.reader(csvfile, delimiter=',')
for row in csvreader:
res.append(row)
return (res[0], res[1:])
def parse_country_preferences(file):
countryPreferences = {}
header, records = parse_csv_with_header(file)
for record in records:
countryPreferences[record[0]] = [header[i] + "::" + record[i] for i in range(1, len(record))]
return countryPreferences
countryPreferences = parse_country_preferences(args.countrypreferences)
def parse_language_preferences(file):
languagePreferences = {}
header, records = parse_csv_with_header(file)
for record in records:
languagePreferences[record[0]] = (header[1], record[1])
return languagePreferences
languagePreferences = parse_language_preferences(args.languagepreferences)
def print_block_from_list(target, header, data, beautify=lambda arg: arg, prefix=" ", footer="};\n\n"):
target.write(header)
for i in range(len(data)):
target.write(prefix + beautify(data[i]) + ",\n")
target.write(footer)
def print_header(data, path, locales, countries):
f = open(path, "w")
f.write("#ifndef APPS_I18N_H\n")
f.write("#define APPS_I18N_H\n\n")
f.write("// This file is auto-generated by i18n.py\n\n")
f.write("#include <escher.h>\n\n")
f.write("#include <escher.h>\n")
f.write("#include <apps/country_preferences.h>\n\n")
f.write("namespace I18n {\n\n")
f.write("constexpr static int NumberOfLanguages = %d;\n\n" % len(locales))
f.write("constexpr static int NumberOfCountries = %d;\n\n" % len(countries))
# Messages enumeration
f.write("enum class Message : uint16_t {\n")
f.write(" Default = 0,\n")
for message in data["universal_messages"]:
f.write(" " + message + ",\n")
f.write("\n")
f.write(" LocalizedMessageMarker,\n\n")
for message in data["messages"]:
f.write(" " + message + ",\n")
f.write("};\n\n")
print_block_from_list(f,
"enum class Message : uint16_t {\n Default = 0,\n",
data["universal_messages"],
footer="\n")
print_block_from_list(f,
" LocalizedMessageMarker,\n\n",
data["messages"])
# Languages enumeration
f.write("enum class Language : uint16_t {\n")
index = 0
for locale in locales:
f.write(" " + locale.upper() + (" = 0" if (index < 1) else "") +",\n")
index = index + 1
f.write("};\n\n")
print_block_from_list(f,
"enum class Language : uint8_t {\n",
locales,
lambda arg: arg.upper())
# Language names
f.write("constexpr const Message LanguageNames[NumberOfLanguages] = {\n");
for locale in locales:
f.write(" Message::Language" + locale.upper() + ",\n")
f.write("};\n\n")
print_block_from_list(f,
"constexpr const Message LanguageNames[NumberOfLanguages] = {\n",
locales,
lambda arg: arg.upper(),
" Message::Language")
if generate_ISO6391():
# Language ISO639-1 codes
f.write("constexpr const Message LanguageISO6391Names[NumberOfLanguages] = {\n");
for locale in locales:
f.write(" Message::LanguageISO6391" + locale.upper() + ",\n")
f.write("};\n\n")
print_block_from_list(f,
"constexpr const Message LanguageISO6391Names[NumberOfLanguages] = {\n",
locales,
lambda arg: arg.upper(),
" Message::LanguageISO6391")
# Countries enumeration
print_block_from_list(f,
"enum class Country : uint8_t {\n",
countries,
lambda arg: arg.upper())
defaultCountry = countries[-1]
# Country names
print_block_from_list(f,
"constexpr const Message CountryNames[NumberOfCountries] = {\n",
countries,
lambda arg: arg.upper(),
" Message::Country")
# Language preferences
f.write("constexpr static Country DefaultCountryForLanguage[NumberOfLanguages] = {\n")
for language in locales:
key = language if (language in languagePreferences) else '??'
header, country = languagePreferences[key]
line = " " + header + "::" + (country if country in countries else defaultCountry)
f.write(line + ",\n")
f.write("};\n\n")
# Country preferences
f.write("constexpr static CountryPreferences CountryPreferencesArray[] = {\n")
for country in countries:
key = country if (country in countryPreferences) else defaultCountry
line = " CountryPreferences("
for param in countryPreferences[key]:
line += param + ", "
f.write(line[:-2] + "),\n")
f.write("};\n\n")
f.write("}\n\n")
f.write("#endif\n")
@@ -178,11 +259,10 @@ def print_implementation(data, path, locales):
f = open(path, "a") # Re-open the file as text
f.write(";\n")
f.write("\n")
f.write("constexpr static const char * universalMessages[%d] = {\n" % (len(data["universal_messages"])+1))
f.write(" universalDefault,\n")
for message in data["universal_messages"]:
f.write(" universal" + message + ",\n")
f.write("};\n\n")
print_block_from_list(f,
"constexpr static const char * universalMessages[%d] = {\n universalDefault,\n" % (len(data["universal_messages"])+1),
data["universal_messages"],
prefix=" universal")
# Write the localized messages
for message in data["messages"]:
@@ -226,6 +306,6 @@ def print_implementation(data, path, locales):
data = parse_files(args.files)
if args.header:
print_header(data, args.header, args.locales)
print_header(data, args.header, args.locales, args.countries)
if args.implementation:
print_implementation(data, args.implementation, args.locales)

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