diff --git a/apps/Makefile b/apps/Makefile index a7345678d..381c0d7d2 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -1,3 +1,4 @@ +include apps/helpers.mk include apps/shared/Makefile include apps/home/Makefile include apps/on_boarding/Makefile @@ -11,6 +12,7 @@ apps = $(foreach i,${apps_list},${eval include apps/$(i)/Makefile}) apps_src += $(addprefix apps/,\ + alternate_empty_nested_menu_controller.cpp \ apps_container.cpp \ apps_container_launch_default.cpp:-onboarding \ apps_container_launch_on_boarding.cpp:+onboarding \ @@ -32,11 +34,11 @@ apps_src += $(addprefix apps/,\ lock_view.cpp \ main.cpp \ math_toolbox.cpp \ + math_variable_box_controller.cpp \ + math_variable_box_empty_controller.cpp \ shift_alpha_lock_view.cpp \ suspend_timer.cpp \ title_bar_view.cpp \ - variable_box_controller.cpp \ - variable_box_empty_controller.cpp \ ) tests_src += apps/exam_mode_configuration_non_official.cpp @@ -60,27 +62,10 @@ i18n_files += $(addprefix apps/language_,$(addsuffix .universal.i18n, $(EPSILON_ ifeq ($(EPSILON_GETOPT),1) i18n_files += $(addprefix apps/language_,$(addsuffix _iso6391.universal.i18n, $(EPSILON_I18N))) endif -i18n_files += $(addprefix apps/,\ - shared.de.i18n\ - shared.en.i18n\ - shared.es.i18n\ - shared.fr.i18n\ - shared.pt.i18n\ - shared.hu.i18n\ - shared.universal.i18n\ - toolbox.de.i18n\ - toolbox.en.i18n\ - toolbox.es.i18n\ - toolbox.fr.i18n\ - toolbox.pt.i18n\ - toolbox.hu.i18n\ - variables.de.i18n\ - variables.en.i18n\ - variables.es.i18n\ - variables.fr.i18n\ - variables.pt.i18n\ - variables.hu.i18n\ -) + +i18n_files += $(call i18n_with_universal_for,shared) +i18n_files += $(call i18n_without_universal_for,toolbox) +i18n_files += $(call i18n_without_universal_for,variables) $(eval $(call rule_for, \ I18N, \ @@ -104,6 +89,7 @@ $(call object_for,$(apps_src) $(tests_src)): $(BUILD_DIR)/python/port/genhdr/qst 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 += $(addprefix apps/,\ + alternate_empty_nested_menu_controller.cpp \ global_preferences.cpp \ ) diff --git a/apps/alternate_empty_nested_menu_controller.cpp b/apps/alternate_empty_nested_menu_controller.cpp new file mode 100644 index 000000000..d3a89f0f9 --- /dev/null +++ b/apps/alternate_empty_nested_menu_controller.cpp @@ -0,0 +1,18 @@ +#include "alternate_empty_nested_menu_controller.h" + +void AlternateEmptyNestedMenuController::viewDidDisappear() { + if (isDisplayingEmptyController()) { + pop(); + } + NestedMenuController::viewDidDisappear(); +} + +bool AlternateEmptyNestedMenuController::displayEmptyControllerIfNeeded() { + assert(!isDisplayingEmptyController()); + // If the content is empty, we push an empty controller. + if (numberOfRows() == 0) { + push(emptyViewController()); + return true; + } + return false; +} diff --git a/apps/alternate_empty_nested_menu_controller.h b/apps/alternate_empty_nested_menu_controller.h new file mode 100644 index 000000000..d310b15b6 --- /dev/null +++ b/apps/alternate_empty_nested_menu_controller.h @@ -0,0 +1,19 @@ +#ifndef APPS_ALTERNATE_EMPTY_NESTED_MENU_CONTROLLER_H +#define APPS_ALTERNATE_EMPTY_NESTED_MENU_CONTROLLER_H + +#include + +class AlternateEmptyNestedMenuController : public NestedMenuController { +public: + AlternateEmptyNestedMenuController(I18n::Message title) : + NestedMenuController(nullptr, title) + {} + // View Controller + void viewDidDisappear() override; +protected: + virtual ViewController * emptyViewController() = 0; + bool isDisplayingEmptyController() { return StackViewController::depth() == 2; } + bool displayEmptyControllerIfNeeded(); +}; + +#endif diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index c3e627071..dd0b90207 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -92,7 +92,7 @@ MathToolbox * AppsContainer::mathToolbox() { return &m_mathToolbox; } -VariableBoxController * AppsContainer::variableBoxController() { +MathVariableBoxController * AppsContainer::variableBoxController() { return &m_variableBoxController; } diff --git a/apps/apps_container.h b/apps/apps_container.h index 99d219ee9..8af5abe7b 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -8,7 +8,7 @@ #include "apps_window.h" #include "empty_battery_window.h" #include "math_toolbox.h" -#include "variable_box_controller.h" +#include "math_variable_box_controller.h" #include "exam_pop_up_controller.h" #include "exam_pop_up_controller_delegate.h" #include "battery_timer.h" @@ -34,7 +34,7 @@ public: void reset(); Poincare::Context * globalContext(); MathToolbox * mathToolbox(); - VariableBoxController * variableBoxController(); + MathVariableBoxController * variableBoxController(); void suspend(bool checkIfOnOffKeyReleased = false); bool dispatchEvent(Ion::Events::Event event) override; bool switchTo(App::Snapshot * snapshot) override; @@ -70,7 +70,7 @@ private: EmptyBatteryWindow m_emptyBatteryWindow; Shared::GlobalContext m_globalContext; MathToolbox m_mathToolbox; - VariableBoxController m_variableBoxController; + MathVariableBoxController m_variableBoxController; ExamPopUpController m_examPopUpController; OnBoarding::PopUpController m_promptController; BatteryTimer m_batteryTimer; diff --git a/apps/calculation/Makefile b/apps/calculation/Makefile index a414283bc..453e46cf0 100644 --- a/apps/calculation/Makefile +++ b/apps/calculation/Makefile @@ -21,6 +21,7 @@ app_calculation_src = $(addprefix apps/calculation/,\ additional_outputs/trigonometry_graph_cell.cpp \ additional_outputs/trigonometry_list_controller.cpp \ additional_outputs/trigonometry_model.cpp \ + additional_outputs/unit_list_controller.cpp \ app.cpp \ edit_expression_controller.cpp \ expression_field.cpp \ @@ -32,14 +33,7 @@ app_calculation_src = $(addprefix apps/calculation/,\ app_calculation_src += $(app_calculation_test_src) apps_src += $(app_calculation_src) -i18n_files += $(addprefix apps/calculation/,\ - base.de.i18n\ - base.en.i18n\ - base.es.i18n\ - base.fr.i18n\ - base.pt.i18n\ - base.hu.i18n\ -) +i18n_files += $(call i18n_without_universal_for,calculation/base) tests_src += $(addprefix apps/calculation/test/,\ calculation_store.cpp\ diff --git a/apps/calculation/additional_outputs/complex_list_controller.h b/apps/calculation/additional_outputs/complex_list_controller.h index 1c7d2a654..addb7c342 100644 --- a/apps/calculation/additional_outputs/complex_list_controller.h +++ b/apps/calculation/additional_outputs/complex_list_controller.h @@ -10,7 +10,7 @@ namespace Calculation { class ComplexListController : public IllustratedListController { public: ComplexListController(EditExpressionController * editExpressionController) : - IllustratedListController(nullptr, editExpressionController), + IllustratedListController(editExpressionController), m_complexGraphCell(&m_model) {} // ViewController diff --git a/apps/calculation/additional_outputs/complex_model.cpp b/apps/calculation/additional_outputs/complex_model.cpp index 6d59f3810..5a2322415 100644 --- a/apps/calculation/additional_outputs/complex_model.cpp +++ b/apps/calculation/additional_outputs/complex_model.cpp @@ -17,10 +17,10 @@ float ComplexModel::rangeBound(float direction, bool horizontal) const { maxFactor = k_maxHorizontalMarginFactor; value = real(); } - if (std::isnan(value) || std::isinf(value) || value == 0.0f) { - return direction*maxFactor; - } float factor = direction*value >= 0.0f ? maxFactor : minFactor; + if (std::isnan(value) || std::isinf(value) || value == 0.0f) { + return direction*factor; + } return factor*value; } diff --git a/apps/calculation/additional_outputs/complex_model.h b/apps/calculation/additional_outputs/complex_model.h index 827418c1e..685d6bdaf 100644 --- a/apps/calculation/additional_outputs/complex_model.h +++ b/apps/calculation/additional_outputs/complex_model.h @@ -2,6 +2,7 @@ #define CALCULATION_ADDITIONAL_OUTPUTS_COMPLEX_MODEL_H #include "../../shared/curve_view_range.h" +#include "illustrated_list_controller.h" #include namespace Calculation { @@ -17,11 +18,53 @@ public: void setComplex(std::complex c) { *this = ComplexModel(c); } - - static constexpr float k_minVerticalMarginFactor = -0.5f; - static constexpr float k_maxVerticalMarginFactor = 1.2f; + /* The range is computed from these criteria: + * - The real part is centered horizontally + * - Both left and right margins are equal to the real length + * - The imaginary part is the same length as the real part + * - The remaining vertical margin are splitted as one third at the top, 2 + * thirds at the bottom + * + * | | 1/3 * vertical_margin + * +----------+ + * | / | | + * | / | | Imaginary + * | / | | + * | / | | + * ----------+----------+---------- + * | + * | 2/3 * vertical_margin + * ----------- + * Real + * + */ + // Horizontal range static constexpr float k_minHorizontalMarginFactor = -1.0f; static constexpr float k_maxHorizontalMarginFactor = 2.0f; + // Vertical range + static constexpr KDCoordinate k_width = Ion::Display::Width - Metric::PopUpRightMargin - Metric::PopUpLeftMargin; + static constexpr KDCoordinate k_height = IllustratedListController::k_illustrationHeight; + static constexpr KDCoordinate k_unit = k_width/3; + /* + * VerticalMaring = k_height - k_unit + * + * Values | Coordinates + * --------+---------------------------------- + * imag | k_unit + * Ymax | k_unit + (1/3)*VerticalMargin + * Ymin | -(2/3)*VerticalMargin + * + * Thus: + * Ymin = -(2/3)*k_verticalMargin*imag/k_unit + * = -(2/3)*(k_height/k_unit - 1)*imag + * = 2/3*(1 - k_height/k_unit)*imag + * Ymax = (k_unit + (1/3)*VerticalMargin)*imag/k_unit + * = (1 + (1/3)*(k_height/k_unit - 1))*imag + * = 1/3*(2 + k_height/k_unit)*imag + * + * */ + static constexpr float k_minVerticalMarginFactor = 2.0f/3.0f*(1.0f - (float)k_height/(float)k_unit); + static constexpr float k_maxVerticalMarginFactor = 1.0f/3.0f*(2.0f + (float)k_height/(float)k_unit); private: float rangeBound(float direction, bool horizontal) const; diff --git a/apps/calculation/additional_outputs/expressions_list_controller.cpp b/apps/calculation/additional_outputs/expressions_list_controller.cpp index 555de4433..2ea1b3ef3 100644 --- a/apps/calculation/additional_outputs/expressions_list_controller.cpp +++ b/apps/calculation/additional_outputs/expressions_list_controller.cpp @@ -7,8 +7,8 @@ namespace Calculation { /* Expressions list controller */ -ExpressionsListController::ExpressionsListController(Responder * parentResponder, EditExpressionController * editExpressionController) : - ListController(parentResponder, editExpressionController), +ExpressionsListController::ExpressionsListController(EditExpressionController * editExpressionController) : + ListController(editExpressionController), m_cells{} { for (int i = 0; i < k_maxNumberOfCells; i++) { @@ -38,9 +38,7 @@ HighlightCell * ExpressionsListController::reusableCell(int index, int type) { KDCoordinate ExpressionsListController::rowHeight(int j) { Layout l = layoutAtIndex(j); - if (l.isUninitialized()) { - return 0; - } + assert(!l.isUninitialized()); return l.layoutSize().height() + 2 * Metric::CommonSmallMargin + Metric::CellSeparatorThickness; } diff --git a/apps/calculation/additional_outputs/expressions_list_controller.h b/apps/calculation/additional_outputs/expressions_list_controller.h index df8f8e4ff..4d6ade149 100644 --- a/apps/calculation/additional_outputs/expressions_list_controller.h +++ b/apps/calculation/additional_outputs/expressions_list_controller.h @@ -10,7 +10,7 @@ namespace Calculation { class ExpressionsListController : public ListController { public: - ExpressionsListController(Responder * parentResponder, EditExpressionController * editExpressionController); + ExpressionsListController(EditExpressionController * editExpressionController); // Responder void viewDidDisappear() override; diff --git a/apps/calculation/additional_outputs/illustrated_list_controller.cpp b/apps/calculation/additional_outputs/illustrated_list_controller.cpp index 3fd481ee6..1b78bef20 100644 --- a/apps/calculation/additional_outputs/illustrated_list_controller.cpp +++ b/apps/calculation/additional_outputs/illustrated_list_controller.cpp @@ -1,4 +1,5 @@ #include "illustrated_list_controller.h" +#include #include #include "../app.h" @@ -8,8 +9,8 @@ namespace Calculation { /* Illustrated list controller */ -IllustratedListController::IllustratedListController(Responder * parentResponder, EditExpressionController * editExpressionController) : - ListController(parentResponder, editExpressionController, this), +IllustratedListController::IllustratedListController(EditExpressionController * editExpressionController) : + ListController(editExpressionController, this), m_additionalCalculationCells{} { for (int i = 0; i < k_maxNumberOfAdditionalCalculations; i++) { @@ -82,6 +83,10 @@ KDCoordinate IllustratedListController::rowHeight(int j) { KDCoordinate result = calculation->memoizedHeight(expanded); if (result < 0) { result = ScrollableThreeExpressionsCell::Height(calculation.pointer()); + if (result < 0) { + // Raise, because Height modified the calculation and failed. + Poincare::ExceptionCheckpoint::Raise(); + } calculation->setMemoizedHeight(expanded, result); } return result + Metric::CellSeparatorThickness; diff --git a/apps/calculation/additional_outputs/illustrated_list_controller.h b/apps/calculation/additional_outputs/illustrated_list_controller.h index fdac26ddd..970341821 100644 --- a/apps/calculation/additional_outputs/illustrated_list_controller.h +++ b/apps/calculation/additional_outputs/illustrated_list_controller.h @@ -10,8 +10,10 @@ namespace Calculation { class IllustratedListController : public ListController, public SelectableTableViewDelegate { +/* TODO There is factorizable code between this and + * Calculation::HistoryController (at least rowHeight). */ public: - IllustratedListController(Responder * parentResponder, EditExpressionController * editExpressionController); + IllustratedListController(EditExpressionController * editExpressionController); // Responder void viewDidDisappear() override; @@ -31,7 +33,7 @@ public: // IllustratedListController void setExpression(Poincare::Expression e) override; - constexpr static KDCoordinate k_illustrationHeight = 100; + constexpr static KDCoordinate k_illustrationHeight = 120; protected: Poincare::Expression m_savedExpression; CalculationStore m_calculationStore; diff --git a/apps/calculation/additional_outputs/integer_list_controller.cpp b/apps/calculation/additional_outputs/integer_list_controller.cpp index 1e5bb0cb8..98deb516d 100644 --- a/apps/calculation/additional_outputs/integer_list_controller.cpp +++ b/apps/calculation/additional_outputs/integer_list_controller.cpp @@ -28,9 +28,6 @@ Integer::Base baseAtIndex(int index) { } void IntegerListController::computeLayoutAtIndex(int index) { - if (!m_layouts[index].isUninitialized()) { - return; - } 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); @@ -65,6 +62,6 @@ bool IntegerListController::factorExpressionIsComputable() const { } m_layouts[k_indexOfFactorExpression] = EmptyLayout::Builder(); return false; +} } -} diff --git a/apps/calculation/additional_outputs/integer_list_controller.h b/apps/calculation/additional_outputs/integer_list_controller.h index a0208762d..b76f46d4d 100644 --- a/apps/calculation/additional_outputs/integer_list_controller.h +++ b/apps/calculation/additional_outputs/integer_list_controller.h @@ -8,7 +8,7 @@ namespace Calculation { class IntegerListController : public ExpressionsListController { public: IntegerListController(EditExpressionController * editExpressionController) : - ExpressionsListController(nullptr, editExpressionController) {} + ExpressionsListController(editExpressionController) {} //ListViewDataSource int numberOfRows() const override; diff --git a/apps/calculation/additional_outputs/list_controller.cpp b/apps/calculation/additional_outputs/list_controller.cpp index fe2836fed..ea2b17d3c 100644 --- a/apps/calculation/additional_outputs/list_controller.cpp +++ b/apps/calculation/additional_outputs/list_controller.cpp @@ -21,8 +21,8 @@ void ListController::InnerListController::didBecomeFirstResponder() { /* List Controller */ -ListController::ListController(Responder * parentResponder, EditExpressionController * editExpressionController, SelectableTableViewDelegate * delegate) : - StackViewController(parentResponder, &m_listController, Palette::ToolboxHeaderText, Palette::ToolboxHeaderBackground, Palette::ToolboxHeaderBorder), +ListController::ListController(EditExpressionController * editExpressionController, SelectableTableViewDelegate * delegate) : + StackViewController(nullptr, &m_listController, Palette::ToolboxHeaderText, Palette::ToolboxHeaderBackground, Palette::ToolboxHeaderBorder), m_listController(this, delegate), m_editExpressionController(editExpressionController) { @@ -38,7 +38,6 @@ bool ListController::handleEvent(Ion::Events::Event event) { * insertTextBody. */ Container::activeApp()->dismissModalViewController(); m_editExpressionController->insertTextBody(buffer); - Container::activeApp()->setFirstResponder(m_editExpressionController); return true; } return false; diff --git a/apps/calculation/additional_outputs/list_controller.h b/apps/calculation/additional_outputs/list_controller.h index 86f24b227..0378c90b6 100644 --- a/apps/calculation/additional_outputs/list_controller.h +++ b/apps/calculation/additional_outputs/list_controller.h @@ -10,7 +10,7 @@ class EditExpressionController; class ListController : public StackViewController, public ListViewDataSource, public SelectableTableViewDataSource { public: - ListController(Responder * parentResponder, EditExpressionController * editExpressionController, SelectableTableViewDelegate * delegate = nullptr); + ListController(EditExpressionController * editExpressionController, SelectableTableViewDelegate * delegate = nullptr); // Responder bool handleEvent(Ion::Events::Event event) override; diff --git a/apps/calculation/additional_outputs/rational_list_controller.h b/apps/calculation/additional_outputs/rational_list_controller.h index fac0f06eb..62d0de5a9 100644 --- a/apps/calculation/additional_outputs/rational_list_controller.h +++ b/apps/calculation/additional_outputs/rational_list_controller.h @@ -8,7 +8,7 @@ namespace Calculation { class RationalListController : public ExpressionsListController { public: RationalListController(EditExpressionController * editExpressionController) : - ExpressionsListController(nullptr, editExpressionController) {} + ExpressionsListController(editExpressionController) {} //ListViewDataSource int numberOfRows() const override; diff --git a/apps/calculation/additional_outputs/scrollable_three_expressions_cell.cpp b/apps/calculation/additional_outputs/scrollable_three_expressions_cell.cpp index 29fe03a9a..63cf7b17e 100644 --- a/apps/calculation/additional_outputs/scrollable_three_expressions_cell.cpp +++ b/apps/calculation/additional_outputs/scrollable_three_expressions_cell.cpp @@ -8,7 +8,8 @@ void ScrollableThreeExpressionsView::resetMemoization() { setLayouts(Poincare::Layout(), Poincare::Layout(), Poincare::Layout()); } -void ScrollableThreeExpressionsView::setCalculation(Calculation * calculation) { +void ScrollableThreeExpressionsView::setCalculation(Calculation * calculation, bool * didForceOutput) { + assert(!didForceOutput || *didForceOutput == false); Poincare::Context * context = App::app()->localContext(); // Clean the layouts to make room in the pool @@ -27,6 +28,9 @@ void ScrollableThreeExpressionsView::setCalculation(Calculation * calculation) { Poincare::ExceptionCheckpoint::Raise(); } else { calculation->forceDisplayOutput(::Calculation::Calculation::DisplayOutput::ApproximateOnly); + if (didForceOutput) { + *didForceOutput = true; + } } } } @@ -46,6 +50,9 @@ void ScrollableThreeExpressionsView::setCalculation(Calculation * calculation) { /* Set the display output to ApproximateOnly, make room in the pool by * erasing the exact layout, and retry to create the approximate layout */ calculation->forceDisplayOutput(::Calculation::Calculation::DisplayOutput::ApproximateOnly); + if (didForceOutput) { + *didForceOutput = true; + } exactOutputLayout = Poincare::Layout(); couldNotCreateApproximateLayout = false; approximateOutputLayout = calculation->createApproximateOutputLayout(context, &couldNotCreateApproximateLayout); @@ -67,7 +74,16 @@ void ScrollableThreeExpressionsView::setCalculation(Calculation * calculation) { KDCoordinate ScrollableThreeExpressionsCell::Height(Calculation * calculation) { ScrollableThreeExpressionsCell cell; - cell.setCalculation(calculation); + bool didForceOutput = false; + cell.setCalculation(calculation, &didForceOutput); + if (didForceOutput) { + /* We could not compute the height of the calculation as it is (the display + * output was forced to another value during the height computation). + * Warning: the display output of calculation was actually changed, so it + * will cause problems if we already did some computations with another + * display value. */ + return -1; + } KDRect leftFrame = KDRectZero; KDRect centerFrame = KDRectZero; KDRect approximateSignFrame = KDRectZero; @@ -87,8 +103,8 @@ void ScrollableThreeExpressionsCell::reinitSelection() { m_view.reloadScroll(); } -void ScrollableThreeExpressionsCell::setCalculation(Calculation * calculation) { - m_view.setCalculation(calculation); +void ScrollableThreeExpressionsCell::setCalculation(Calculation * calculation, bool * didForceOutput) { + m_view.setCalculation(calculation, didForceOutput); layoutSubviews(); } diff --git a/apps/calculation/additional_outputs/scrollable_three_expressions_cell.h b/apps/calculation/additional_outputs/scrollable_three_expressions_cell.h index b1b72a08a..4a8a015b4 100644 --- a/apps/calculation/additional_outputs/scrollable_three_expressions_cell.h +++ b/apps/calculation/additional_outputs/scrollable_three_expressions_cell.h @@ -9,6 +9,9 @@ namespace Calculation { +/* TODO There is factorizable code between this and Calculation::HistoryViewCell + * (at least setCalculation). */ + class ScrollableThreeExpressionsView : public Shared::AbstractScrollableMultipleExpressionsView { public: static constexpr KDCoordinate k_margin = Metric::CommonSmallMargin; @@ -17,7 +20,7 @@ public: setBackgroundColor(Palette::BackgroundApps); } void resetMemoization(); - void setCalculation(Calculation * calculation); + void setCalculation(Calculation * calculation, bool * didForceOutput = nullptr); void subviewFrames(KDRect * leftFrame, KDRect * centerFrame, KDRect * approximateSignFrame, KDRect * rightFrame) { return m_contentCell.subviewFrames(leftFrame, centerFrame, approximateSignFrame, rightFrame); } @@ -58,7 +61,7 @@ public: void setHighlighted(bool highlight) override { m_view.evenOddCell()->setHighlighted(highlight); } void resetMemoization() { m_view.resetMemoization(); } - void setCalculation(Calculation * calculation); + void setCalculation(Calculation * calculation, bool * didForceOutput = nullptr); void setDisplayCenter(bool display); ScrollableThreeExpressionsView::SubviewPosition selectedSubviewPosition() { return m_view.selectedSubviewPosition(); } void setSelectedSubviewPosition(ScrollableThreeExpressionsView::SubviewPosition subviewPosition) { m_view.setSelectedSubviewPosition(subviewPosition); } diff --git a/apps/calculation/additional_outputs/trigonometry_list_controller.h b/apps/calculation/additional_outputs/trigonometry_list_controller.h index dfec810d9..12880e1a0 100644 --- a/apps/calculation/additional_outputs/trigonometry_list_controller.h +++ b/apps/calculation/additional_outputs/trigonometry_list_controller.h @@ -10,7 +10,7 @@ namespace Calculation { class TrigonometryListController : public IllustratedListController { public: TrigonometryListController(EditExpressionController * editExpressionController) : - IllustratedListController(nullptr, editExpressionController), + IllustratedListController(editExpressionController), m_graphCell(&m_model) {} void setExpression(Poincare::Expression e) override; private: diff --git a/apps/calculation/additional_outputs/unit_list_controller.cpp b/apps/calculation/additional_outputs/unit_list_controller.cpp new file mode 100644 index 000000000..3c48787ad --- /dev/null +++ b/apps/calculation/additional_outputs/unit_list_controller.cpp @@ -0,0 +1,155 @@ +#include "unit_list_controller.h" +#include "../app.h" +#include "../../shared/poincare_helpers.h" +#include +#include +#include +#include +#include + +using namespace Poincare; +using namespace Shared; + +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(); + } + + size_t numberOfMemoizedExpressions = 0; + // 1. First rows: miscellaneous classic units for some dimensions + Expression copy = m_expression.clone(); + Expression units; + // Reduce to be able to recognize units + PoincareHelpers::Reduce(©, App::app()->localContext(), ExpressionNode::ReductionTarget::User); + copy = copy.removeUnit(&units); + bool requireSimplification = false; + bool canChangeUnitPrefix = false; + + if (Unit::IsISSpeed(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::IsISVolume(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::IsISEnergy(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::IsISTime(units)) { + // Turn time into ? year + ? month + ? day + ? h + ? min + ? s + double value = Shared::PoincareHelpers::ApproximateToScalar(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(m_memoizedExpressions[currentExpressionIndex], App::app()->localContext()); + ExpressionNode::ReductionContext reductionContext( + App::app()->localContext(), + Preferences::sharedPreferences()->complexFormat(), + Preferences::sharedPreferences()->angleUnit(), + ExpressionNode::ReductionTarget::User, + ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); + Unit::ChooseBestPrefixForValue(&newUnits, &value, reductionContext); + m_memoizedExpressions[currentExpressionIndex] = Multiplication::Builder(Number::FloatNumber(value), newUnits); + } + currentExpressionIndex++; + } + + // 2. IS units only + 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 + Expression reduceExpression = m_expression.clone(); + // Make m_expression compareable to m_memoizedExpressions (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) { + 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]; + assert(!comparedExpression.isUninitialized()); + if (comparedExpression.isIdenticalTo(m_memoizedExpressions[currentExpressionIndex])) { + numberOfMemoizedExpressions--; + // Shift next expressions + for (size_t j = currentExpressionIndex; j < numberOfMemoizedExpressions; j++) { + m_memoizedExpressions[j] = m_memoizedExpressions[j+1]; + } + // Remove last expression + m_memoizedExpressions[numberOfMemoizedExpressions] = Expression(); + // The current expression has been discarded, no need to increment the current index + duplicateFound = true; + break; + } + } + if (!duplicateFound) { + // The current expression is not a duplicate, check next expression + currentExpressionIndex++; + } + } +} + +int UnitListController::numberOfRows() const { + int nbOfRows = 0; + for (size_t i = 0; i < k_maxNumberOfCells; i++) { + if (!m_memoizedExpressions[i].isUninitialized()) { + nbOfRows++; + } + } + return nbOfRows; +} + +void UnitListController::computeLayoutAtIndex(int index) { + assert(!m_memoizedExpressions[index].isUninitialized()); + m_layouts[index] = Shared::PoincareHelpers::CreateLayout(m_memoizedExpressions[index]); +} + +I18n::Message UnitListController::messageAtIndex(int index) { + return (I18n::Message)0; +} + +} diff --git a/apps/calculation/additional_outputs/unit_list_controller.h b/apps/calculation/additional_outputs/unit_list_controller.h new file mode 100644 index 000000000..e3fdee036 --- /dev/null +++ b/apps/calculation/additional_outputs/unit_list_controller.h @@ -0,0 +1,26 @@ +#ifndef CALCULATION_ADDITIONAL_OUTPUTS_UNIT_LIST_CONTROLLER_H +#define CALCULATION_ADDITIONAL_OUTPUTS_UNIT_LIST_CONTROLLER_H + +#include "expressions_list_controller.h" + +namespace Calculation { + +class UnitListController : public ExpressionsListController { +public: + UnitListController(EditExpressionController * editExpressionController) : + ExpressionsListController(editExpressionController) {} + + 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]; +}; + +} + +#endif diff --git a/apps/calculation/base.es.i18n b/apps/calculation/base.es.i18n index 36d7a8bdb..25c3c5035 100644 --- a/apps/calculation/base.es.i18n +++ b/apps/calculation/base.es.i18n @@ -1,9 +1,9 @@ CalculApp = "Cálculo" CalculAppCapital = "CÁLCULO" -AdditionalResults = "????" -DecimalBase = "????" -HexadecimalBase = "????" -BinaryBase = "????" -PrimeFactors = "????" -MixedFraction = "????" -EuclideanDivision = "????" +AdditionalResults = "Resultados adicionales" +DecimalBase = "Decimal" +HexadecimalBase = "Hexadecimal" +BinaryBase = "Binario" +PrimeFactors = "Factores primos" +MixedFraction = "Fracción mixta" +EuclideanDivision = "División euclidiana" diff --git a/apps/calculation/base.it.i18n b/apps/calculation/base.it.i18n new file mode 100644 index 000000000..6544c6622 --- /dev/null +++ b/apps/calculation/base.it.i18n @@ -0,0 +1,9 @@ +CalculApp = "Calcolo" +CalculAppCapital = "CALCOLO" +AdditionalResults = "Risultati complementari" +DecimalBase = "Decimale" +HexadecimalBase = "Esadecimale" +BinaryBase = "Binario" +PrimeFactors = "Fattori primi" +MixedFraction = "Frazione mista" +EuclideanDivision = "Divisione euclidea" diff --git a/apps/calculation/base.nl.i18n b/apps/calculation/base.nl.i18n new file mode 100644 index 000000000..0ae59c24a --- /dev/null +++ b/apps/calculation/base.nl.i18n @@ -0,0 +1,9 @@ +CalculApp = "Calculatie" +CalculAppCapital = "CALCULATIE" +AdditionalResults = "Bijkomende resultaten" +DecimalBase = "Decimaal" +HexadecimalBase = "Hexadecimaal" +BinaryBase = "Binaire" +PrimeFactors = "Priemfactoren" +MixedFraction = "Gemengde breuk" +EuclideanDivision = "Geheeltallige deling" diff --git a/apps/calculation/base.pt.i18n b/apps/calculation/base.pt.i18n index 36d7a8bdb..b8e8717aa 100644 --- a/apps/calculation/base.pt.i18n +++ b/apps/calculation/base.pt.i18n @@ -1,9 +1,9 @@ CalculApp = "Cálculo" CalculAppCapital = "CÁLCULO" -AdditionalResults = "????" -DecimalBase = "????" -HexadecimalBase = "????" -BinaryBase = "????" -PrimeFactors = "????" -MixedFraction = "????" -EuclideanDivision = "????" +AdditionalResults = "Resultados adicionais" +DecimalBase = "Decimal" +HexadecimalBase = "Hexadecimal" +BinaryBase = "Binário" +PrimeFactors = "Fatores primos" +MixedFraction = "Fração mista" +EuclideanDivision = "Divisão euclidiana" diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index 646e1c05d..56b5978dc 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -3,8 +3,10 @@ #include "../shared/scrollable_multiple_expressions_view.h" #include "../global_preferences.h" #include "../exam_mode_configuration.h" +#include "app.h" #include #include +#include #include #include #include @@ -39,8 +41,7 @@ Calculation * Calculation::next() const { void Calculation::tidy() { /* Reset height memoization (the complex format could have changed when * re-entering Calculation app which would impact the heights). */ - m_height = -1; - m_expandedHeight = -1; + resetHeightMemoization(); } const char * Calculation::approximateOutputText(NumberOfSignificantDigits numberOfSignificantDigits) const { @@ -174,7 +175,7 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) { ExpressionNode::Type::PredictionInterval }; return e.isOfType(approximateOnlyTypes, sizeof(approximateOnlyTypes)/sizeof(ExpressionNode::Type)); - }, context, true) + }, context) ) { m_displayOutput = DisplayOutput::ApproximateOnly; @@ -194,9 +195,9 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) { void Calculation::forceDisplayOutput(DisplayOutput d) { m_displayOutput = d; // Reset heights memoization as it might have changed when we modify the display output - m_height = -1; - m_expandedHeight = -1; + resetHeightMemoization(); } + bool Calculation::shouldOnlyDisplayExactOutput() { /* If the input is a "store in a function", do not display the approximate * result. This prevents x->f(x) from displaying x = undef. */ @@ -232,6 +233,9 @@ Calculation::EqualSign Calculation::exactAndApproximateDisplayedOutputsAreEqual( } Calculation::AdditionalInformationType Calculation::additionalInformationType(Context * context) { + if (ExamModeConfiguration::exactExpressionsAreForbidden(GlobalPreferences::sharedGlobalPreferences()->examMode())) { + return AdditionalInformationType::None; + } Preferences * preferences = Preferences::sharedPreferences(); Preferences::ComplexFormat complexFormat = Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), m_inputText); Expression i = input(); @@ -253,8 +257,28 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co if (input().isDefinedCosineOrSine(context, complexFormat, preferences->angleUnit()) || o.isDefinedCosineOrSine(context, complexFormat, preferences->angleUnit())) { return AdditionalInformationType::Trigonometry; } - - // TODO: return AdditionalInformationType::Unit + if (o.hasUnit()) { + Expression unit; + PoincareHelpers::Reduce(&o, App::app()->localContext(), ExpressionNode::ReductionTarget::User,ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined, ExpressionNode::UnitConversion::None); + o = o.removeUnit(&unit); + if (Unit::IsIS(unit)) { + if (Unit::IsISSpeed(unit) || Unit::IsISVolume(unit) || Unit::IsISEnergy(unit)) { + /* All these units will provide misc. classic representatives in + * addition to the SI unit in additional information. */ + return AdditionalInformationType::Unit; + } + if (Unit::IsISTime(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(o, App::app()->localContext()); + if (value > Unit::SecondsPerMinute) { + return AdditionalInformationType::Unit; + } + } + return AdditionalInformationType::None; + } + return AdditionalInformationType::Unit; + } if (o.isBasedIntegerCappedBy(k_maximalIntegerWithAdditionalInformation)) { return AdditionalInformationType::Integer; } @@ -268,4 +292,9 @@ Calculation::AdditionalInformationType Calculation::additionalInformationType(Co return AdditionalInformationType::None; } +void Calculation::resetHeightMemoization() { + m_height = -1; + m_expandedHeight = -1; +} + } diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index 5c23cadee..f93c99aca 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -100,6 +100,7 @@ private: static constexpr int k_numberOfExpressions = 4; static constexpr KDCoordinate k_heightComputationFailureHeight = 50; static constexpr const char * k_maximalIntegerWithAdditionalInformation = "10000000000000000"; + void resetHeightMemoization(); /* Buffers holding text expressions have to be longer than the text written * by user (of maximum length TextField::maxBufferSize()) because when we * print an expression we add omitted signs (multiplications, parenthesis...) */ diff --git a/apps/calculation/calculation_store.cpp b/apps/calculation/calculation_store.cpp index cf9487c7b..e51ae3043 100644 --- a/apps/calculation/calculation_store.cpp +++ b/apps/calculation/calculation_store.cpp @@ -4,6 +4,7 @@ #include #include #include +#include "../exam_mode_configuration.h" #include using namespace Poincare; @@ -98,8 +99,11 @@ ExpiringPointer CalculationStore::push(const char * text, Context * // Outputs hold exact output, approximate output and its duplicate constexpr static int numberOfOutputs = Calculation::k_numberOfExpressions - 1; Expression outputs[numberOfOutputs] = {Expression(), Expression(), Expression()}; - // SYMBOLIC COMPUTATION <= E12: PoincareHelpers::ParseAndSimplifyAndApproximate(inputSerialization, &(outputs[0]), &(outputs[1]), context, GlobalPreferences::sharedGlobalPreferences()->isInExamModeSymbolic()); // Symbolic computation PoincareHelpers::ParseAndSimplifyAndApproximate(inputSerialization, &(outputs[0]), &(outputs[1]), context, GlobalPreferences::sharedGlobalPreferences()->isInExamModeSymbolic() ? Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition : Poincare::ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); + if (ExamModeConfiguration::exactExpressionsAreForbidden(GlobalPreferences::sharedGlobalPreferences()->examMode()) && outputs[1].hasUnit()) { + // Hide results with units on units if required by the exam mode configuration + outputs[1] = Undefined::Builder(); + } outputs[2] = outputs[1]; int numberOfSignificantDigits = Poincare::PrintFloat::k_numberOfStoredSignificantDigits; for (int i = 0; i < numberOfOutputs; i++) { diff --git a/apps/calculation/edit_expression_controller.cpp b/apps/calculation/edit_expression_controller.cpp index 5e69bfee8..cd7702e93 100644 --- a/apps/calculation/edit_expression_controller.cpp +++ b/apps/calculation/edit_expression_controller.cpp @@ -9,7 +9,7 @@ using namespace Poincare; namespace Calculation { -EditExpressionController::ContentView::ContentView(Responder * parentResponder, TableView * subview, InputEventHandlerDelegate * inputEventHandlerDelegate, TextFieldDelegate * textFieldDelegate, LayoutFieldDelegate * layoutFieldDelegate) : +EditExpressionController::ContentView::ContentView(Responder * parentResponder, CalculationSelectableTableView * subview, InputEventHandlerDelegate * inputEventHandlerDelegate, TextFieldDelegate * textFieldDelegate, LayoutFieldDelegate * layoutFieldDelegate) : View(), m_mainView(subview), m_expressionField(parentResponder, inputEventHandlerDelegate, textFieldDelegate, layoutFieldDelegate) @@ -42,18 +42,18 @@ EditExpressionController::EditExpressionController(Responder * parentResponder, ViewController(parentResponder), m_historyController(historyController), m_calculationStore(calculationStore), - m_contentView(this, (TableView *)m_historyController->view(), inputEventHandlerDelegate, this, this) + m_contentView(this, static_cast(m_historyController->view()), inputEventHandlerDelegate, this, this) { m_cacheBuffer[0] = 0; } void EditExpressionController::insertTextBody(const char * text) { + Container::activeApp()->setFirstResponder(this); m_contentView.expressionField()->handleEventWithText(text, false, true); } void EditExpressionController::didBecomeFirstResponder() { - int lastRow = m_calculationStore->numberOfCalculations() > 0 ? m_calculationStore->numberOfCalculations()-1 : 0; - m_historyController->scrollToCell(0, lastRow); + m_contentView.mainView()->scrollToBottom(); m_contentView.expressionField()->setEditing(true, false); Container::activeApp()->setFirstResponder(m_contentView.expressionField()); } diff --git a/apps/calculation/edit_expression_controller.h b/apps/calculation/edit_expression_controller.h index fe2d4c44c..eee1c4033 100644 --- a/apps/calculation/edit_expression_controller.h +++ b/apps/calculation/edit_expression_controller.h @@ -8,6 +8,7 @@ #include "../shared/layout_field_delegate.h" #include "history_controller.h" #include "calculation_store.h" +#include "selectable_table_view.h" namespace Calculation { @@ -34,15 +35,15 @@ public: private: class ContentView : public View { public: - ContentView(Responder * parentResponder, TableView * subview, InputEventHandlerDelegate * inputEventHandlerDelegate, TextFieldDelegate * textFieldDelegate, LayoutFieldDelegate * layoutFieldDelegate); + ContentView(Responder * parentResponder, CalculationSelectableTableView * subview, InputEventHandlerDelegate * inputEventHandlerDelegate, TextFieldDelegate * textFieldDelegate, LayoutFieldDelegate * layoutFieldDelegate); void reload(); - TableView * mainView() { return m_mainView; } + CalculationSelectableTableView * mainView() { return m_mainView; } ExpressionField * expressionField() { return &m_expressionField; } private: int numberOfSubviews() const override { return 2; } View * subviewAtIndex(int index) override; void layoutSubviews(bool force = false) override; - TableView * m_mainView; + CalculationSelectableTableView * m_mainView; ExpressionField m_expressionField; }; void reloadView(); diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index 251cc3a1f..c8c153b6a 100644 --- a/apps/calculation/history_controller.cpp +++ b/apps/calculation/history_controller.cpp @@ -1,5 +1,6 @@ #include "history_controller.h" #include "app.h" +#include #include using namespace Shared; @@ -15,7 +16,8 @@ HistoryController::HistoryController(EditExpressionController * editExpressionCo m_complexController(editExpressionController), m_integerController(editExpressionController), m_rationalController(editExpressionController), - m_trigonometryController(editExpressionController) + m_trigonometryController(editExpressionController), + m_unitController(editExpressionController) { for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) { m_calculationHistory[i].setParentResponder(&m_selectableTableView); @@ -37,9 +39,9 @@ void HistoryController::reload() { * the table view twice. */ if (numberOfRows() > 0) { - m_selectableTableView.scrollToCell(0, numberOfRows()-1); + m_selectableTableView.scrollToBottom(); // Force to reload last added cell (hide the burger and exact output if necessary) - tableViewDidChangeSelection(&m_selectableTableView, 0, numberOfRows()-1); + tableViewDidChangeSelectionAndDidScroll(&m_selectableTableView, 0, numberOfRows()-1); } } @@ -78,11 +80,9 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { EditExpressionController * editController = (EditExpressionController *)parentResponder(); if (subviewType == SubviewType::Input) { m_selectableTableView.deselectTable(); - Container::activeApp()->setFirstResponder(editController); editController->insertTextBody(calculationAtIndex(focusRow)->inputText()); } else if (subviewType == SubviewType::Output) { m_selectableTableView.deselectTable(); - Container::activeApp()->setFirstResponder(editController); Shared::ExpiringPointer calculation = calculationAtIndex(focusRow); ScrollableTwoExpressionsView::SubviewPosition outputSubviewPosition = selectedCell->outputView()->selectedSubviewPosition(); if (outputSubviewPosition == ScrollableTwoExpressionsView::SubviewPosition::Right @@ -108,6 +108,8 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { vc = &m_integerController; } else if (additionalInfoType == Calculation::AdditionalInformationType::Rational) { vc = &m_rationalController; + } else if (additionalInfoType == Calculation::AdditionalInformationType::Unit) { + vc = &m_unitController; } if (vc) { vc->setExpression(e); @@ -120,24 +122,14 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { int focusRow = selectedRow(); SubviewType subviewType = selectedSubviewType(); m_selectableTableView.deselectTable(); - EditExpressionController * editController = (EditExpressionController *)parentResponder(); m_calculationStore->deleteCalculationAtIndex(storeIndex(focusRow)); reload(); if (numberOfRows()== 0) { - Container::activeApp()->setFirstResponder(editController); + Container::activeApp()->setFirstResponder(parentResponder()); return true; } - if (focusRow > 0) { - m_selectableTableView.selectCellAtLocation(0, focusRow-1); - } else { - m_selectableTableView.selectCellAtLocation(0, 0); - } - if (subviewType == SubviewType::Input) { - tableViewDidChangeSelection(&m_selectableTableView, 0, selectedRow()); - } else { - tableViewDidChangeSelection(&m_selectableTableView, 0, -1); - } - m_selectableTableView.scrollToCell(0, selectedRow()); + m_selectableTableView.selectCellAtLocation(0, focusRow > 0 ? focusRow - 1 : 0); + setSelectedSubviewType(subviewType, true, 0, selectedRow()); return true; } if (event == Ion::Events::Clear) { @@ -148,9 +140,8 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { return true; } if (event == Ion::Events::Back) { - EditExpressionController * editController = (EditExpressionController *)parentResponder(); m_selectableTableView.deselectTable(); - Container::activeApp()->setFirstResponder(editController); + Container::activeApp()->setFirstResponder(parentResponder()); return true; } return false; @@ -160,7 +151,7 @@ Shared::ExpiringPointer HistoryController::calculationAtIndex(int i return m_calculationStore->calculationAtIndex(storeIndex(i)); } -void HistoryController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { +void HistoryController::tableViewDidChangeSelectionAndDidScroll(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { if (withinTemporarySelection || previousSelectedCellY == selectedRow()) { return; } @@ -171,7 +162,7 @@ void HistoryController::tableViewDidChangeSelection(SelectableTableView * t, int } else { HistoryViewCell * selectedCell = (HistoryViewCell *)(t->selectedCell()); SubviewType nextSelectedSubviewType = selectedSubviewType(); - if (!selectedCell->displaysSingleLine()) { + if (selectedCell && !selectedCell->displaysSingleLine()) { nextSelectedSubviewType = previousSelectedCellY < selectedRow() ? SubviewType::Input : SubviewType::Output; } setSelectedSubviewType(nextSelectedSubviewType, false, previousSelectedCellX, previousSelectedCellY); @@ -216,8 +207,16 @@ KDCoordinate HistoryController::rowHeight(int j) { KDCoordinate result = calculation->memoizedHeight(expanded); if (result < 0) { result = HistoryViewCell::Height(calculation.pointer(), expanded); + if (result < 0) { + // Raise, because Height modified the calculation and failed. + Poincare::ExceptionCheckpoint::Raise(); + } calculation->setMemoizedHeight(expanded, result); } + /* We might want to put an assertion here to check the memoization: + * assert(result == HistoryViewCell::Height(calculation.pointer(), expanded)); + * However, Height might fail due to pool memory exhaustion, in which case the + * assertion fails even if "result" had the right value. */ return result; } @@ -225,15 +224,21 @@ int HistoryController::typeAtLocation(int i, int j) { return 0; } -void HistoryController::scrollToCell(int i, int j) { - m_selectableTableView.scrollToCell(i, j); -} - bool HistoryController::calculationAtIndexToggles(int index) { Context * context = App::app()->localContext(); return index >= 0 && index < m_calculationStore->numberOfCalculations() && calculationAtIndex(index)->displayOutput(context) == Calculation::DisplayOutput::ExactAndApproximateToggle; } + +void HistoryController::setSelectedSubviewType(SubviewType subviewType, bool sameCell, int previousSelectedX, int previousSelectedY) { + // Avoid selecting non-displayed ellipsis + HistoryViewCell * selectedCell = static_cast(m_selectableTableView.selectedCell()); + if (subviewType == SubviewType::Ellipsis && selectedCell && selectedCell->additionalInformationType() == Calculation::AdditionalInformationType::None) { + subviewType = SubviewType::Output; + } + HistoryViewCellDataSource::setSelectedSubviewType(subviewType, sameCell, previousSelectedX, previousSelectedY); +} + void HistoryController::historyViewCellDidChangeSelection(HistoryViewCell ** cell, HistoryViewCell ** previousCell, int previousSelectedCellX, int previousSelectedCellY, SubviewType type, SubviewType previousType) { /* If the selection change triggers the toggling of the outputs, we update * the whole table as the height of the selected cell row might have changed. */ diff --git a/apps/calculation/history_controller.h b/apps/calculation/history_controller.h index 407e52805..021a0e876 100644 --- a/apps/calculation/history_controller.h +++ b/apps/calculation/history_controller.h @@ -9,6 +9,7 @@ #include "additional_outputs/integer_list_controller.h" #include "additional_outputs/rational_list_controller.h" #include "additional_outputs/trigonometry_list_controller.h" +#include "additional_outputs/unit_list_controller.h" namespace Calculation { @@ -30,8 +31,8 @@ public: void willDisplayCellForIndex(HighlightCell * cell, int index) override; KDCoordinate rowHeight(int j) override; int typeAtLocation(int i, int j) override; - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false) override; - void scrollToCell(int i, int j); + void setSelectedSubviewType(SubviewType subviewType, bool sameCell, int previousSelectedX = -1, int previousSelectedY = -1) override; + void tableViewDidChangeSelectionAndDidScroll(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false) override; private: int storeIndex(int i) { return numberOfRows() - i - 1; } Shared::ExpiringPointer calculationAtIndex(int i); @@ -46,6 +47,7 @@ private: IntegerListController m_integerController; RationalListController m_rationalController; TrigonometryListController m_trigonometryController; + UnitListController m_unitController; }; } diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index 46a93b987..1f47f7e0f 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -36,7 +36,16 @@ void HistoryViewCellDataSource::setSelectedSubviewType(SubviewType subviewType, KDCoordinate HistoryViewCell::Height(Calculation * calculation, bool expanded) { HistoryViewCell cell(nullptr); - cell.setCalculation(calculation, expanded); + bool didForceOutput = false; + cell.setCalculation(calculation, expanded, &didForceOutput); + if (didForceOutput) { + /* We could not compute the height of the calculation as it is (the display + * output was forced to another value during the height computation). + * Warning: the display output of calculation was actually changed, so it + * will cause problems if we already did some computations with another + * display value. */ + return -1; + } KDRect ellipsisFrame = KDRectZero; KDRect inputFrame = KDRectZero; KDRect outputFrame = KDRectZero; @@ -204,7 +213,7 @@ void HistoryViewCell::computeSubviewFrames(KDCoordinate frameWidth, KDCoordinate KDCoordinate inputY = k_margin; KDCoordinate outputY = k_margin; - if (m_calculationSingleLine) { + if (m_calculationSingleLine && !m_inputView.layout().isUninitialized()) { KDCoordinate inputBaseline = m_inputView.layout().baseline(); KDCoordinate outputBaseline = m_scrollableOutputView.baseline(); KDCoordinate baselineDifference = outputBaseline - inputBaseline; @@ -237,7 +246,8 @@ void HistoryViewCell::resetMemoization() { m_calculationCRC32 = 0; } -void HistoryViewCell::setCalculation(Calculation * calculation, bool expanded) { +void HistoryViewCell::setCalculation(Calculation * calculation, bool expanded, bool * didForceOutput) { + assert(!didForceOutput || *didForceOutput == false); uint32_t newCalculationCRC = Ion::crc32Byte((const uint8_t *)calculation, ((char *)calculation->next()) - ((char *) calculation)); if (newCalculationCRC == m_calculationCRC32 && m_calculationExpanded == expanded) { return; @@ -265,6 +275,9 @@ void HistoryViewCell::setCalculation(Calculation * calculation, bool expanded) { if (couldNotCreateExactLayout) { if (calculation->displayOutput(context) != ::Calculation::Calculation::DisplayOutput::ExactOnly) { calculation->forceDisplayOutput(::Calculation::Calculation::DisplayOutput::ApproximateOnly); + if (didForceOutput) { + *didForceOutput = true; + } } else { /* We should only display the exact result, but we cannot create it * -> raise an exception. */ @@ -287,6 +300,9 @@ void HistoryViewCell::setCalculation(Calculation * calculation, bool expanded) { /* Set the display output to ApproximateOnly, make room in the pool by * erasing the exact layout, and retry to create the approximate layout */ calculation->forceDisplayOutput(::Calculation::Calculation::DisplayOutput::ApproximateOnly); + if (didForceOutput) { + *didForceOutput = true; + } exactOutputLayout = Poincare::Layout(); couldNotCreateApproximateLayout = false; approximateOutputLayout = calculation->createApproximateOutputLayout(context, &couldNotCreateApproximateLayout); diff --git a/apps/calculation/history_view_cell.h b/apps/calculation/history_view_cell.h index 4fccbb42f..2932ee1a5 100644 --- a/apps/calculation/history_view_cell.h +++ b/apps/calculation/history_view_cell.h @@ -18,7 +18,7 @@ public: Ellipsis = 3 }; HistoryViewCellDataSource() : m_selectedSubviewType(SubviewType::Output) {} - void setSelectedSubviewType(SubviewType subviewType, bool sameCell, int previousSelectedX = -1, int previousSelectedY = -1); + virtual void setSelectedSubviewType(SubviewType subviewType, bool sameCell, int previousSelectedX = -1, int previousSelectedY = -1); SubviewType selectedSubviewType() const { return m_selectedSubviewType; } private: /* This method should belong to a delegate instead of a data source but as @@ -51,13 +51,14 @@ public: Poincare::Layout layout() const override; KDColor backgroundColor() const override { return m_even ? Palette::CalculationBackgroundEven : Palette::CalculationBackgroundOdd; } void resetMemoization(); - void setCalculation(Calculation * calculation, bool expanded); + void setCalculation(Calculation * calculation, bool expanded, bool * didForceOutput = nullptr); int numberOfSubviews() const override { return 2 + displayedEllipsis(); } View * subviewAtIndex(int index) override; void layoutSubviews(bool force = false) override; void didBecomeFirstResponder() override; bool handleEvent(Ion::Events::Event event) override; Shared::ScrollableTwoExpressionsView * outputView() { return &m_scrollableOutputView; } + ScrollableExpressionView * inputView() { return &m_inputView; } Calculation::AdditionalInformationType additionalInformationType() const { return m_calculationAdditionInformation; } private: constexpr static KDCoordinate k_resultWidth = 80; diff --git a/apps/calculation/selectable_table_view.cpp b/apps/calculation/selectable_table_view.cpp index 12599842b..8a2884ce1 100644 --- a/apps/calculation/selectable_table_view.cpp +++ b/apps/calculation/selectable_table_view.cpp @@ -1,4 +1,5 @@ #include "selectable_table_view.h" +#include namespace Calculation { @@ -11,6 +12,12 @@ CalculationSelectableTableView::CalculationSelectableTableView(Responder * paren setDecoratorType(ScrollView::Decorator::Type::None); } +void CalculationSelectableTableView::scrollToBottom() { + KDCoordinate contentOffsetX = contentOffset().x(); + KDCoordinate contentOffsetY = dataSource()->cumulatedHeightFromIndex(dataSource()->numberOfRows()) - maxContentHeightDisplayableWithoutScrolling(); + setContentOffset(KDPoint(contentOffsetX, contentOffsetY)); +} + void CalculationSelectableTableView::scrollToCell(int i, int j) { if (m_contentView.bounds().height() < bounds().height()) { setTopMargin(bounds().height() - m_contentView.bounds().height()); @@ -20,9 +27,8 @@ void CalculationSelectableTableView::scrollToCell(int i, int j) { ::SelectableTableView::scrollToCell(i, j); ScrollView::layoutSubviews(); if (m_contentView.bounds().height() - contentOffset().y() < bounds().height()) { - KDCoordinate contentOffsetX = contentOffset().x(); - KDCoordinate contentOffsetY = dataSource()->cumulatedHeightFromIndex(dataSource()->numberOfRows()) - maxContentHeightDisplayableWithoutScrolling(); - setContentOffset(KDPoint(contentOffsetX, contentOffsetY)); + // Avoid empty space at the end of the table + scrollToBottom(); } } @@ -31,23 +37,63 @@ void CalculationSelectableTableView::scrollToSubviewOfTypeOfCellAtLocation(Histo return; } /* As we scroll, the selected calculation does not use the same history view - * cell, thus, we want to deselect the previous used history view cell. */ + * cell, thus, we want to deselect the previous used history view cell. (*) */ unhighlightSelectedCell(); /* Main part of the scroll */ + HistoryViewCell * cell = static_cast(selectedCell()); + assert(cell); KDCoordinate contentOffsetX = contentOffset().x(); - KDCoordinate contentOffsetY = dataSource()->cumulatedHeightFromIndex(j+1) - maxContentHeightDisplayableWithoutScrolling(); - if (subviewType == HistoryViewCellDataSource::SubviewType::Input) { - if (j == 0) { - contentOffsetY = 0; - } else { - contentOffsetY = dataSource()->cumulatedHeightFromIndex(j); - } + + KDCoordinate contentOffsetY = dataSource()->cumulatedHeightFromIndex(j); + if (cell->displaysSingleLine() && dataSource()->rowHeight(j) > maxContentHeightDisplayableWithoutScrolling()) { + /* If we cannot display the full calculation, we display the selected + * layout as close as possible to the top of the screen without drawing + * empty space between the history and the input field. + * + * Below are some values we can assign to contentOffsetY, and the kinds of + * display they entail : + * (the selected cell is at index j) + * + * 1 - cumulatedHeightFromIndex(j) + * Aligns the top of the cell with the top of the zone in which the + * history can be drawn. + * + * 2 - (cumulatedHeightFromIndex(j+1) + * - maxContentHeightDisplayableWithoutScrolling()) + * Aligns the bottom of the cell with the top of the input field. + * + * 3 - cumulatedHeightFromIndex(j) + baseline1 - baseline2 + * Aligns the top of the selected layout with the top of the screen (only + * used when the selected layout is the smallest). + * + * The following drawing shows where the calculation would be aligned with + * each value of contentOffsetY, for the calculation (1/3)/(4/2) = 1/6. + * + * (1) (2) (3) + * +--------------+ +--------------+ +--------------+ + * | 1 | | --- - | | 3 1 | + * | - | | 4 6 | | --- - | + * | 3 1 | | - | | 4 6 | + * | --- - | | 2 | | - | + * +--------------+ +--------------+ +--------------+ + * | (1/3)/(4/2) | | (1/3)/(4/2) | | (1/3)/(4/2) | + * +--------------+ +--------------+ +--------------+ + * + * */ + contentOffsetY += std::min( + dataSource()->rowHeight(j) - maxContentHeightDisplayableWithoutScrolling(), + std::max(0, (cell->inputView()->layout().baseline() - cell->outputView()->baseline()) * (subviewType == HistoryViewCellDataSource::SubviewType::Input ? -1 : 1))); + } else if (subviewType != HistoryViewCellDataSource::SubviewType::Input) { + contentOffsetY += dataSource()->rowHeight(j) - maxContentHeightDisplayableWithoutScrolling(); } + setContentOffset(KDPoint(contentOffsetX, contentOffsetY)); - /* For the same reason, we have to rehighlight the new history view cell and - * reselect the first responder. */ - HistoryViewCell * cell = (HistoryViewCell *)(selectedCell()); + /* For the same reason as (*), we have to rehighlight the new history view + * cell and reselect the first responder. + * We have to recall "selectedCell" because when the table might have been + * relayouted in "setContentOffset".*/ + cell = static_cast(selectedCell()); assert(cell); cell->setHighlighted(true); Container::activeApp()->setFirstResponder(cell); diff --git a/apps/calculation/selectable_table_view.h b/apps/calculation/selectable_table_view.h index 07823d9f1..d1740ab78 100644 --- a/apps/calculation/selectable_table_view.h +++ b/apps/calculation/selectable_table_view.h @@ -9,6 +9,7 @@ class CalculationSelectableTableView : public ::SelectableTableView { public: CalculationSelectableTableView(Responder * parentResponder, TableViewDataSource * dataSource, SelectableTableViewDataSource * selectionDataSource, SelectableTableViewDelegate * delegate = nullptr); + void scrollToBottom(); void scrollToCell(int i, int j) override; void scrollToSubviewOfTypeOfCellAtLocation(HistoryViewCellDataSource::SubviewType subviewType, int i, int j); }; diff --git a/apps/code/Makefile b/apps/code/Makefile index 807dea98b..de11dcb4c 100644 --- a/apps/code/Makefile +++ b/apps/code/Makefile @@ -1,10 +1,6 @@ apps += Code::App app_headers += apps/code/app.h -app_code_test_src = $(addprefix apps/code/,\ - script_template.cpp \ -) - app_code_src = $(addprefix apps/code/,\ app.cpp \ console_controller.cpp \ @@ -15,42 +11,31 @@ app_code_src = $(addprefix apps/code/,\ editor_view.cpp \ helpers.cpp \ menu_controller.cpp \ - python_toolbox.cpp \ python_text_area.cpp \ sandbox_controller.cpp \ - script.cpp \ script_name_cell.cpp \ - script_node_cell.cpp \ script_parameter_controller.cpp \ +) + +app_code_test_src = $(addprefix apps/code/,\ + python_toolbox.cpp \ + script.cpp \ + script_node_cell.cpp \ script_store.cpp \ + script_template.cpp \ + variable_box_empty_controller.cpp \ variable_box_controller.cpp \ ) +tests_src += $(addprefix apps/code/test/,\ + variable_box_controller.cpp\ +) + app_code_src += $(app_code_test_src) apps_src += $(app_code_src) -i18n_files += $(addprefix apps/code/,\ - base.de.i18n\ - base.en.i18n\ - base.es.i18n\ - base.fr.i18n\ - base.pt.i18n\ - base.hu.i18n\ - base.universal.i18n\ - catalog.de.i18n\ - catalog.en.i18n\ - catalog.es.i18n\ - catalog.fr.i18n\ - catalog.pt.i18n\ - catalog.hu.i18n\ - catalog.universal.i18n\ - toolbox.de.i18n\ - toolbox.en.i18n\ - toolbox.es.i18n\ - toolbox.fr.i18n\ - toolbox.pt.i18n\ - toolbox.hu.i18n\ - toolbox.universal.i18n\ -) +i18n_files += $(call i18n_with_universal_for,code/base) +i18n_files += $(call i18n_with_universal_for,code/catalog) +i18n_files += $(call i18n_with_universal_for,code/toolbox) $(eval $(call depends_on_image,apps/code/app.cpp,apps/code/code_icon.png)) diff --git a/apps/code/app.cpp b/apps/code/app.cpp index 10d372183..26d276627 100644 --- a/apps/code/app.cpp +++ b/apps/code/app.cpp @@ -67,7 +67,7 @@ void App::Snapshot::setOpt(const char * name, const char * value) { const char * scriptContent = separator; Code::ScriptTemplate script(scriptName, scriptContent); m_scriptStore.addScriptFromTemplate(&script); - m_scriptStore.scriptNamed(scriptName).toggleImportationStatus(); // set Importation Status to 1 + ScriptStore::ScriptNamed(scriptName).toggleAutoimportationStatus(); // set Importation Status to 1 return; } if (strcmp(name, "lock-on-console") == 0) { diff --git a/apps/code/app.h b/apps/code/app.h index bdc3b9f05..10a82926f 100644 --- a/apps/code/app.h +++ b/apps/code/app.h @@ -51,6 +51,7 @@ public: } StackViewController * stackViewController() { return &m_codeStackViewController; } ConsoleController * consoleController() { return &m_consoleController; } + MenuController * menuController() { return &m_menuController; } /* Responder */ bool handleEvent(Ion::Events::Event event) override; diff --git a/apps/code/base.de.i18n b/apps/code/base.de.i18n index 39dc0ee0a..40cf856fa 100644 --- a/apps/code/base.de.i18n +++ b/apps/code/base.de.i18n @@ -1,10 +1,15 @@ -Console = "Interaktive Konsole" AddScript = "Skript hinzufügen" -ScriptOptions = "Skriptoptionen" -ExecuteScript = "Skript ausführen" +AllowedCharactersaz09 = "Erlaubte Zeichen: a-z, 0-9, _" +Autocomplete = "Autovervollständigung" AutoImportScript = "Automatischer Import in Konsole" +BuiltinsAndKeywords = "Native Funktionen und Schlüsselwörter" +Console = "Interaktive Konsole" DeleteScript = "Skript löschen" DuplicateScript = "Skript duplizieren" +ExecuteScript = "Skript ausführen" FunctionsAndVariables = "Funktionen und Variablen" -AllowedCharactersaz09 = "Erlaubte Zeichen: a-z, 0-9, _" -ScriptSize = "Skriptgrösse" +ImportedModulesAndScripts = "Importierte Module und Skripte" +NoWordAvailableHere = "Kein Wort ist hier verfübar." +ScriptInProgress = "Aktuelle Skript" +ScriptOptions = "Skriptoptionen" +ScriptSize = "Script size" diff --git a/apps/code/base.en.i18n b/apps/code/base.en.i18n index b584cc613..082bcfd79 100644 --- a/apps/code/base.en.i18n +++ b/apps/code/base.en.i18n @@ -1,10 +1,15 @@ -Console = "Python shell" AddScript = "Add a script" -ScriptOptions = "Script options" -ExecuteScript = "Execute script" +AllowedCharactersaz09 = "Allowed characters: a-z, 0-9, _" +Autocomplete = "Autocomplete" AutoImportScript = "Auto import in shell" +BuiltinsAndKeywords = "Builtins and keywords" +Console = "Python shell" DeleteScript = "Delete script" DuplicateScript = "Duplicate script" +ExecuteScript = "Execute script" FunctionsAndVariables = "Functions and variables" -AllowedCharactersaz09 = "Allowed characters: a-z, 0-9, _" +ImportedModulesAndScripts = "Imported modules and scripts" +NoWordAvailableHere = "No word available here." +ScriptInProgress = "Script in progress" +ScriptOptions = "Script options" ScriptSize = "Script size" diff --git a/apps/code/base.es.i18n b/apps/code/base.es.i18n index 05d69dc6e..5d9087b54 100644 --- a/apps/code/base.es.i18n +++ b/apps/code/base.es.i18n @@ -1,10 +1,15 @@ -Console = "Interprete de comandos" AddScript = "Agregar un archivo" -ScriptOptions = "Opciones del archivo" -ExecuteScript = "Ejecutar el archivo" +AllowedCharactersaz09 = "Caracteres permitidos : a-z, 0-9, _" +Autocomplete = "Autocompleción" AutoImportScript = "Importación auto en intérprete" +BuiltinsAndKeywords = "Funciones nativas y palabras clave" +Console = "Interprete de comandos" DeleteScript = "Eliminar el archivo" DuplicateScript = "Duplicar el guión" +ExecuteScript = "Ejecutar el archivo" FunctionsAndVariables = "Funciones y variables" -AllowedCharactersaz09 = "Caracteres permitidos : a-z, 0-9, _" -ScriptSize = "tamano del script" +ImportedModulesAndScripts = "Módulos y archivos importados" +NoWordAvailableHere = "No hay ninguna palabra disponible aquí." +ScriptInProgress = "Archivo en curso" +ScriptOptions = "Opciones del archivo" +ScriptSize = "Script size" diff --git a/apps/code/base.fr.i18n b/apps/code/base.fr.i18n index 812996a30..b9006a2d0 100644 --- a/apps/code/base.fr.i18n +++ b/apps/code/base.fr.i18n @@ -1,10 +1,15 @@ -Console = "Console d'exécution" AddScript = "Ajouter un script" -ScriptOptions = "Options de script" -ExecuteScript = "Exécuter le script" +AllowedCharactersaz09 = "Caractères autorisés : a-z, 0-9, _" +Autocomplete = "Auto-complétion" AutoImportScript = "Importation auto dans la console" +BuiltinsAndKeywords = "Fonctions natives et mots-clés" +Console = "Console d'exécution" DeleteScript = "Supprimer le script" DuplicateScript = "Dupliquer le script" +ExecuteScript = "Exécuter le script" FunctionsAndVariables = "Fonctions et variables" -AllowedCharactersaz09 = "Caractères autorisés : a-z, 0-9, _" -ScriptSize = "Taille du script" +ImportedModulesAndScripts = "Modules et scripts importés" +NoWordAvailableHere = "Aucun mot disponible à cet endroit." +ScriptInProgress = "Script en cours" +ScriptOptions = "Options de script" +ScriptSize = "Script size" diff --git a/apps/code/base.it.i18n b/apps/code/base.it.i18n new file mode 100644 index 000000000..be1e56b8c --- /dev/null +++ b/apps/code/base.it.i18n @@ -0,0 +1,13 @@ +AddScript = "Aggiungere script" +AllowedCharactersaz09 = "Caratteri consentiti : a-z, 0-9, _" +Autocomplete = "Autocompletamento" +AutoImportScript = "Importazione automatica dello script" +BuiltinsAndKeywords = "Funzioni native e parole chiave" +Console = "Console d'esecuzione" +DeleteScript = "Eliminare lo script" +ExecuteScript = "Eseguire lo script" +FunctionsAndVariables = "Funzioni e variabili" +ImportedModulesAndScripts = "Moduli e scripts importati" +NoWordAvailableHere = "Nessuna parola disponibile qui." +ScriptInProgress = "Script in corso" +ScriptOptions = "Opzioni dello script" diff --git a/apps/code/base.nl.i18n b/apps/code/base.nl.i18n new file mode 100644 index 000000000..e016172c2 --- /dev/null +++ b/apps/code/base.nl.i18n @@ -0,0 +1,13 @@ +AddScript = "Script toevoegen" +AllowedCharactersaz09 = "Toegestane tekens: a-z, 0-9, _" +Autocomplete = "Autocomplete" +AutoImportScript = "Automatisch importeren in shell" +BuiltinsAndKeywords = "Builtins and keywords" +Console = "Python shell" +DeleteScript = "Script verwijderen" +ExecuteScript = "Script uitvoeren" +FunctionsAndVariables = "Functies en variabelen" +ImportedModulesAndScripts = "Imported modules and scripts" +NoWordAvailableHere = "No word available here." +ScriptInProgress = "Script in progress" +ScriptOptions = "Script opties" diff --git a/apps/code/base.pt.i18n b/apps/code/base.pt.i18n index 8ae7025f3..eac448706 100644 --- a/apps/code/base.pt.i18n +++ b/apps/code/base.pt.i18n @@ -1,10 +1,14 @@ -Console = "Interpretador interativo" AddScript = "Adicionar um script" -ScriptOptions = "Opções de script" -ExecuteScript = "Executar o script" +AllowedCharactersaz09 = "Caracteres permitidos : a-z, 0-9, _" +Autocomplete = "Preenchimento automático" AutoImportScript = "Importação auto no interpretador" +BuiltinsAndKeywords = "Funções nativas e palavras-chave" +Console = "Interpretador interativo" DeleteScript = "Eliminar o script" DuplicateScript = "Duplicar o script" +ExecuteScript = "Executar o script" FunctionsAndVariables = "Funções e variáveis" -AllowedCharactersaz09 = "Caracteres permitidos : a-z, 0-9, _" -ScriptSize = "tamanho do script" +ImportedModulesAndScripts = "Módulos e scripts importados" +NoWordAvailableHere = "Nenhuma palavra disponível aqui." +ScriptInProgress = "Script em curso" +ScriptOptions = "Opções de script" diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index 4bd73ab07..fcfbd99be 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -26,7 +26,18 @@ PythonCeil = "Aufrundung" PythonChoice = "Zufallszahl aus der Liste" PythonClear = "Leere die Liste" PythonCmathFunction = "cmath-Modul-Funktionspräfix" -PythonColor = "Definiert eine RGB-Farbe" +PythonColor = "Definiere eine RGB-Farbe" +PythonColorBlack = "Black color" +PythonColorBlue = "Blue color" +PythonColorBrown = "Brown color" +PythonColorGreen = "Green color" +PythonColorGrey = "Grey color" +PythonColorOrange = "Orange color" +PythonColorPink = "Pink color" +PythonColorPurple = "Purple color" +PythonColorRed = "Red color" +PythonColorWhite = "White color" +PythonColorYellow = "Yellow color" PythonComplex = "a+ib zurückgeben" PythonCopySign = "x mit dem Vorzeichen von y" PythonCos = "Kosinus" @@ -45,10 +56,10 @@ PythonFillRect = "Malt ein Rechteck bei Pixel (x,y)" PythonFloat = "Wandelt x zu float um" PythonFloor = "Floor" PythonFmod = "a modulo b" -PythonFrExp = "Rest und Exponent von x" -PythonGamma = "Gammafunktion" -PythonGetPixel = "Farbe von Pixel (x,y)" -PythonGetrandbits = "Ganzzahl mit k zufälligen Bits" +PythonFrExp = "Mantissa and exponent of x: (m,e)" +PythonGamma = "Gamma function" +PythonGetPixel = "Return pixel (x,y) color" +PythonGetrandbits = "Integer with k random bits" PythonGrid = "Toggle the visibility of the grid" PythonHex = "Ganzzahl zu Hexadecimal" PythonHist = "Draw the histogram of x" @@ -162,43 +173,19 @@ PythonText = "Display a text at (x,y) coordinates" PythonTimeFunction = "time module function prefix" PythonTrunc = "x truncated to an integer" PythonTurtleBackward = "Move backward by x pixels" -PythonTurtleBlack = "Schwarze Farbe" -PythonTurtleBlue = "Blaue Farbe" -PythonTurtleBrown = "Braune Farbe" PythonTurtleCircle = "Circle of radius r pixels" PythonTurtleColor = "Stiftfarbe setzen" PythonTurtleColorMode = "Set the color mode to 1.0 or 255" PythonTurtleForward = "Move forward by x pixels" PythonTurtleFunction = "turtle module function prefix" PythonTurtleGoto = "Move to (x,y) coordinates" -PythonTurtleGreen = "Grüne Farbe" -PythonTurtleGrey = "Graue Farbe" PythonTurtleHeading = "Return the current heading" PythonTurtleHideturtle = "Hide the turtle" PythonTurtleIsdown = "Return True if the pen is down" PythonTurtleLeft = "Turn left by a degrees" -PythonTurtleOrange = "Orange color" PythonTurtlePendown = "Pull the pen down" PythonTurtlePensize = "Set the line thickness to x pixels" PythonTurtlePenup = "Pull the pen up" -PythonTurtlePink = "Pinke Farbe" -PythonTurtlePosition = "Return the current (x,y) location" -PythonTurtlePurple = "Purple color" -PythonTurtleRed = "Rote Farbe" -PythonTurtleReset = "Reset the drawing" -PythonTurtleRight = "Turn right by a degrees" -PythonTurtleSetheading = "Set the orientation to a degrees" -PythonTurtleSetposition = "Position des turtles" -PythonTurtleShowturtle = "Die turtle anzeigen" -PythonTurtleSpeed = "Zeichengeschwindigkeit zwischen 0 und 10" -PythonTurtleWhite = "Weiße Farbe" -PythonTurtleYellow = "Gelbe Farbe" -PythonUniform = "Fließkommazahl in [a,b]" -PythonTimeFromImport = "Import time module" -PythonTimeImport = "Import time module" -PythonTimePrefix = "time module function prefix" -PythonTimeSleep = "Warten Sie n Sekunden lang" -PythonTimeMonotonic = "Monotone Zeit zurückgeben" PythonFileOpen = "Öffnet eine Datei" PythonFileSeekable = "Ist eine Datei durchsuchbar?" PythonFileSeek = "Dateicursor verschieben" @@ -215,3 +202,12 @@ PythonFileName = "Dateiname" PythonFileMode = "Dateiöffnungsmodus" PythonFileReadable = "Ist die Datei lesbar?" PythonFileWritable = "Ist die Datei beschreibbar?" +PythonTurtlePosition = "Return the current (x,y) location" +PythonTurtleReset = "Reset the drawing" +PythonTurtleRight = "Turn right by a degrees" +PythonTurtleSetheading = "Set the orientation to a degrees" +PythonTurtleSetposition = "Positionne la tortue" +PythonTurtleShowturtle = "Show the turtle" +PythonTurtleSpeed = "Drawing speed between 0 and 10" +PythonTurtleWrite = "Display a text" +PythonUniform = "Floating point number in [a,b]" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 080d8462f..5f8657118 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -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" @@ -27,6 +27,17 @@ PythonChoice = "Random number in the list" PythonClear = "Empty the list" PythonCmathFunction = "cmath module function prefix" PythonColor = "Define a rgb color" +PythonColorBlack = "Black color" +PythonColorBlue = "Blue color" +PythonColorBrown = "Brown color" +PythonColorGreen = "Green color" +PythonColorGrey = "Grey color" +PythonColorOrange = "Orange color" +PythonColorPink = "Pink color" +PythonColorPurple = "Purple color" +PythonColorRed = "Red color" +PythonColorWhite = "White color" +PythonColorYellow = "Yellow color" PythonComplex = "Return a+ib" PythonCopySign = "Return x with the sign of y" PythonCos = "Cosine" @@ -45,7 +56,7 @@ PythonFillRect = "Fill a rectangle at pixel (x,y)" PythonFloat = "Convert x to a float" PythonFloor = "Floor" PythonFmod = "a modulo b" -PythonFrExp = "Mantissa and exponent of x" +PythonFrExp = "Mantissa and exponent of x: (m,e)" PythonGamma = "Gamma function" PythonGetPixel = "Return pixel (x,y) color" PythonGetrandbits = "Integer with k random bits" @@ -162,43 +173,32 @@ PythonText = "Display a text at (x,y) coordinates" PythonTimeFunction = "time module function prefix" PythonTrunc = "x truncated to an integer" PythonTurtleBackward = "Move backward by x pixels" -PythonTurtleBlack = "Black color" -PythonTurtleBlue = "Blue color" -PythonTurtleBrown = "Brown color" PythonTurtleCircle = "Circle of radius r pixels" PythonTurtleColor = "Set the pen color" PythonTurtleColorMode = "Set the color mode to 1.0 or 255" PythonTurtleForward = "Move forward by x pixels" PythonTurtleFunction = "turtle module function prefix" PythonTurtleGoto = "Move to (x,y) coordinates" -PythonTurtleGreen = "Green color" -PythonTurtleGrey = "Grey color" PythonTurtleHeading = "Return the current heading" PythonTurtleHideturtle = "Hide the turtle" PythonTurtleIsdown = "Return True if the pen is down" PythonTurtleLeft = "Turn left by a degrees" -PythonTurtleOrange = "Orange color" PythonTurtlePendown = "Pull the pen down" PythonTurtlePensize = "Set the line thickness to x pixels" PythonTurtlePenup = "Pull the pen up" -PythonTurtlePink = "Pink color" PythonTurtlePosition = "Return the current (x,y) location" -PythonTurtlePurple = "Purple color" -PythonTurtleRed = "Red color" PythonTurtleReset = "Reset the drawing" PythonTurtleRight = "Turn right by a degrees" PythonTurtleSetheading = "Set the orientation to a degrees" PythonTurtleSetposition = "Positionne la tortue" PythonTurtleShowturtle = "Show the turtle" PythonTurtleSpeed = "Drawing speed between 0 and 10" -PythonTurtleWhite = "White color" -PythonTurtleYellow = "Yellow color" +PythonTurtleWrite = "Display a text" PythonUniform = "Floating point number in [a,b]" -PythonTimeFromImport = "Import time module" -PythonTimeImport = "Import time module" +PythonImportTime = "Import time module" PythonTimePrefix = "time module function prefix" PythonTimeSleep = "Wait for n second" -PythonTimeMonotonic = "Return monotonic time" +PythonMonotonic = "Return monotonic time" PythonFileOpen = "Opens a file" PythonFileSeekable = "Tells if seek can be used on a file" PythonFileSeek = "Move file's cursor" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index e0bc6c137..35fa14d4e 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -27,6 +27,17 @@ PythonChoice = "Random number in the list" PythonClear = "Empty the list" PythonCmathFunction = "cmath module function prefix" PythonColor = "Define a rgb color" +PythonColorBlack = "Black color" +PythonColorBlue = "Blue color" +PythonColorBrown = "Brown color" +PythonColorGreen = "Green color" +PythonColorGrey = "Grey color" +PythonColorOrange = "Orange color" +PythonColorPink = "Pink color" +PythonColorPurple = "Purple color" +PythonColorRed = "Red color" +PythonColorWhite = "White color" +PythonColorYellow = "Yellow color" PythonComplex = "Return a+ib" PythonCopySign = "Return x with the sign of y" PythonCos = "Cosine" @@ -45,7 +56,7 @@ PythonFillRect = "Fill a rectangle at pixel (x,y)" PythonFloat = "Convert x to a float" PythonFloor = "Floor" PythonFmod = "a modulo b" -PythonFrExp = "Mantissa and exponent of x" +PythonFrExp = "Mantissa and exponent of x: (m,e)" PythonGamma = "Gamma function" PythonGetPixel = "Return pixel (x,y) color" PythonGetrandbits = "Integer with k random bits" @@ -162,43 +173,32 @@ PythonText = "Display a text at (x,y) coordinates" PythonTimeFunction = "time module function prefix" PythonTrunc = "x truncated to an integer" PythonTurtleBackward = "Move backward by x pixels" -PythonTurtleBlack = "Black color" -PythonTurtleBlue = "Blue color" -PythonTurtleBrown = "Brown color" PythonTurtleCircle = "Circle of radius r pixels" PythonTurtleColor = "Set the pen color" PythonTurtleColorMode = "Set the color mode to 1.0 or 255" PythonTurtleForward = "Move forward by x pixels" PythonTurtleFunction = "turtle module function prefix" PythonTurtleGoto = "Move to (x,y) coordinates" -PythonTurtleGreen = "Green color" -PythonTurtleGrey = "Grey color" PythonTurtleHeading = "Return the current heading" PythonTurtleHideturtle = "Hide the turtle" PythonTurtleIsdown = "Return True if the pen is down" PythonTurtleLeft = "Turn left by a degrees" -PythonTurtleOrange = "Orange color" PythonTurtlePendown = "Pull the pen down" PythonTurtlePensize = "Set the line thickness to x pixels" PythonTurtlePenup = "Pull the pen up" -PythonTurtlePink = "Pink color" PythonTurtlePosition = "Return the current (x,y) location" -PythonTurtlePurple = "Purple color" -PythonTurtleRed = "Red color" PythonTurtleReset = "Reset the drawing" PythonTurtleRight = "Turn right by a degrees" PythonTurtleSetheading = "Set the orientation to a degrees" PythonTurtleSetposition = "Positionne la tortue" PythonTurtleShowturtle = "Show the turtle" PythonTurtleSpeed = "Drawing speed between 0 and 10" -PythonTurtleWhite = "White color" -PythonTurtleYellow = "Yellow color" +PythonTurtleWrite = "Display a text" PythonUniform = "Floating point number in [a,b]" -PythonTimeFromImport = "Import time module" -PythonTimeImport = "Import time module" +PythonImportTime = "Import time module" PythonTimePrefix = "time module function prefix" PythonTimeSleep = "Esperar n segundos" -PythonTimeMonotonic = "Tiempo monótono de retorno" +PythonMonotonic = "Tiempo monótono de retorno" PythonFileOpen = "Opens a file" PythonFileSeekable = "Tells if seek can be used on a file" PythonFileSeek = "Move file's internal cursor" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index 2dfad76da..9b436400c 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -27,6 +27,17 @@ PythonChoice = "Nombre aléatoire dans la liste" PythonClear = "Vide la liste" PythonCmathFunction = "Préfixe fonction du module cmath" PythonColor = "Définit une couleur rvb" +PythonColorBlack = "Couleur noire" +PythonColorBlue = "Couleur bleue" +PythonColorBrown = "Couleur marron" +PythonColorGreen = "Couleur verte" +PythonColorGrey = "Couleur grise" +PythonColorOrange = "Couleur orange" +PythonColorPink = "Couleur rose" +PythonColorPurple = "Couleur violette" +PythonColorRed = "Couleur rouge" +PythonColorWhite = "Couleur blanche" +PythonColorYellow = "Couleur jaune" PythonComplex = "Renvoie a+ib" PythonCopySign = "Renvoie x avec le signe de y" PythonCos = "Cosinus" @@ -162,43 +173,32 @@ PythonText = "Affiche un texte en (x,y)" PythonTimeFunction = "Préfixe fonction module time" PythonTrunc = "Troncature entière" PythonTurtleBackward = "Recule de x pixels" -PythonTurtleBlack = "Couleur noire" -PythonTurtleBlue = "Couleur bleue" -PythonTurtleBrown = "Couleur marron" PythonTurtleCircle = "Cercle de rayon r pixels" PythonTurtleColor = "Modifie la couleur du tracé" PythonTurtleColorMode = "Met le mode de couleur à 1.0 ou 255" PythonTurtleForward = "Avance de x pixels" PythonTurtleFunction = "Préfixe fonction du module turtle" PythonTurtleGoto = "Va au point de coordonnées (x,y)" -PythonTurtleGreen = "Couleur verte" -PythonTurtleGrey = "Couleur grise" PythonTurtleHeading = "Renvoie l'orientation actuelle" PythonTurtleHideturtle = "Masque la tortue" PythonTurtleIsdown = "True si le crayon est abaissé" PythonTurtleLeft = "Pivote de a degrés vers la gauche" -PythonTurtleOrange = "Couleur orange" PythonTurtlePendown = "Abaisse le crayon" PythonTurtlePensize = "Taille du tracé en pixels" PythonTurtlePenup = "Relève le crayon" -PythonTurtlePink = "Couleur rose" PythonTurtlePosition = "Renvoie la position (x,y)" -PythonTurtlePurple = "Couleur violette" -PythonTurtleRed = "Couleur rouge" PythonTurtleReset = "Réinitialise le dessin" PythonTurtleRight = "Pivote de a degrés vers la droite" PythonTurtleSetheading = "Met un cap de a degrés" PythonTurtleSetposition = "Positionne la tortue" PythonTurtleShowturtle = "Affiche la tortue" PythonTurtleSpeed = "Vitesse du tracé entre 0 et 10" -PythonTurtleWhite = "Couleur blanche" -PythonTurtleYellow = "Couleur jaune" +PythonTurtleWrite = "Affiche un texte" PythonUniform = "Nombre décimal dans [a,b]" -PythonTimeFromImport = "Importation du module temps" -PythonTimeImport = "Importation du module temps" +PythonImportTime = "Importation du module temps" PythonTimePrefix = "Préfixe fonction du module temps" PythonTimeSleep = "Attendre n secondes" -PythonTimeMonotonic = "Retourne le temps monotonic" +PythonMonotonic = "Retourne le temps monotonic" PythonFileOpen = "Ouvre un fichier" PythonFileSeekable = "Indique si seek peut être utilisé" PythonFileSeek = "Déplace le curseur interne" diff --git a/apps/code/catalog.hu.i18n b/apps/code/catalog.hu.i18n index a82621ec8..e049c1e38 100644 --- a/apps/code/catalog.hu.i18n +++ b/apps/code/catalog.hu.i18n @@ -194,11 +194,10 @@ PythonTurtleSpeed = "Rajzolási sebesség 0 és 10 között" PythonTurtleWhite = "Fehér szín" PythonTurtleYellow = "Sárga szín" PythonUniform = "Lebegöpontos szám [a, b] -ben" -PythonTimeFromImport = "Idömodul importálása" -PythonTimeImport = "Idömodul importálása" +PythonImportTime = "Idömodul importálása" PythonTimePrefix = "idömodul funkció elötag" PythonTimeSleep = "Várj n másodpercet" -PythonTimeMonotonic = "Vissza a monoton idö" +PythonMonotonic = "Vissza a monoton idö" PythonFileOpen = "Fájl megnyitása" PythonFileSeekable = "A fájl kereshető?" PythonFileSeek = "A fájl kurzorának áthelyezése" diff --git a/apps/code/catalog.it.i18n b/apps/code/catalog.it.i18n new file mode 100644 index 000000000..893feb525 --- /dev/null +++ b/apps/code/catalog.it.i18n @@ -0,0 +1,197 @@ +PythonPound = "Commento" +PythonPercent = "Modulo" +Python1J = "Unità immaginaria" +PythonLF = "Nuova riga" +PythonTab = "Tabulazione" +PythonAmpersand = "Congiunzione" +PythonSymbolExp = "Disgiunzione esclusiva" +PythonVerticalBar = "Disgiunzione" +PythonImag = "Parte immaginaria di z" +PythonReal = "Parte reale di z" +PythonSingleQuote = "Apostrofo" +PythonAbs = "Valore assoluto/Modulo" +PythonAcos = "Coseno d'arco" +PythonAcosh = "Coseno iperbolico inverso" +PythonAppend = "Inserisce x alla fine della lista" +PythonArrow = "Freccia da (x,y) a (x+dx,y+dy)" +PythonAsin = "Arco sinusoidale" +PythonAsinh = "Arco sinusoidale iperbolico" +PythonAtan = "Arco tangente" +PythonAtan2 = "Calcolo di atan(y/x)" +PythonAtanh = "Arco tangente iperbolico" +PythonAxis = "Imposta assi (xmin,xmax,ymin,ymax)" +PythonBar = "Grafico a barre con x valori" +PythonBin = "Converte un intero in binario" +PythonCeil = "Parte intera superiore" +PythonChoice = "Numero aleatorio nella lista" +PythonClear = "Svuota la lista" +PythonCmathFunction = "Funz. prefissata modulo cmath" +PythonColor = "Definisci un colore rvb" +PythonColorBlack = "Colore nero" +PythonColorBlue = "Colore blu" +PythonColorBrown = "Colore marrone" +PythonColorGreen = "Colore verde" +PythonColorGrey = "Colore grigio" +PythonColorOrange = "Colore arancione" +PythonColorPink = "Colore rosa" +PythonColorPurple = "Colore viola" +PythonColorRed = "Colore rosso" +PythonColorWhite = "Colore bianco" +PythonColorYellow = "Colore giallo" +PythonComplex = "Restituisce a+ib" +PythonCopySign = "Restituisce x con segno di y" +PythonCos = "Coseno" +PythonCosh = "Coseno iperbolico" +PythonCount = "Conta le ricorrenze di x" +PythonDegrees = "Conversione di radianti in gradi" +PythonDivMod = "Quoziente e resto" +PythonDrawString = "Visualizza il testo dal pixel x,y" +PythonErf = "Funzione d'errore" +PythonErfc = "Funzione d'errore complementare" +PythonEval = "Valuta l'espressione nell'argomento " +PythonExp = "Funzione esponenziale" +PythonExpm1 = "Calcola exp(x)-1" +PythonFabs = "Valore assoluto" +PythonFillRect = "Riempie un rettangolo" +PythonFloat = "Conversione in flottanti" +PythonFloor = "Parte intera" +PythonFmod = "a modulo b" +PythonFrExp = "Mantissa ed esponente di x : (m,e)" +PythonGamma = "Funzione gamma" +PythonGetPixel = "Restituisce colore del pixel(x,y)" +PythonGetrandbits = "Numero aleatorio con k bit" +PythonGrid = "Attiva la visibilità della griglia" +PythonHex = "Conversione intero in esadecimale" +PythonHist = "Disegna l'istogramma di x" +PythonImportCmath = "Importa modulo cmath" +PythonImportIon = "Importa modulo ion" +PythonImportKandinsky = "Importa modulo kandinsky" +PythonImportRandom = "Importa modulo random" +PythonImportMath = "Importa modulo math" +PythonImportMatplotlibPyplot = "Importa modulo matplotlib.pyplot" +PythonImportTurtle = "Importa del modulo turtle" +PythonImportTime = "Importa del modulo time" +PythonIndex = "Indice prima occorrenza di x" +PythonInput = "Inserire un valore" +PythonInsert = "Inserire x in posizione i-esima" +PythonInt = "Conversione in intero" +PythonIonFunction = "Prefisso di funzione modulo ion" +PythonIsFinite = "Testa se x è finito" +PythonIsInfinite = "Testa se x est infinito" +PythonIsKeyDown = "Restituisce True premendo tasto k" +PythonIsNaN = "Testa se x è NaN" +PythonKandinskyFunction = "Prefisso funzione modulo kandinsky" +PythonKeyLeft = "Tasto FRECCIA SINISTRA" +PythonKeyUp = "Tasto FRECCIA ALTO" +PythonKeyDown = "Tasto FRECCIA BASSO" +PythonKeyRight = "Tasto FRECCIA DESTRA" +PythonKeyOk = "Tasto OK" +PythonKeyBack = "Tasto INDIETRO" +PythonKeyHome = "Tasto CASA" +PythonKeyOnOff = "Tasto ON/OFF" +PythonKeyShift = "Tasto SHIFT" +PythonKeyAlpha = "Tasto ALPHA" +PythonKeyXnt = "Tasto X,N,T" +PythonKeyVar = "Tasto VAR" +PythonKeyToolbox = "Tasto TOOLBOX" +PythonKeyBackspace = "Tasto CANCELLA" +PythonKeyExp = "Tasto ESPONENZIALE" +PythonKeyLn = "Tasto LOGARITMO NEPERIANO" +PythonKeyLog = "Tasto LOGARITMO DECIMALE" +PythonKeyImaginary = "Tasto I IMMAGINE" +PythonKeyComma = "Tasto VIRGOLA" +PythonKeyPower = "Tasto POTENZA" +PythonKeySine = "Tasto SENO" +PythonKeyCosine = "Tasto COSENO" +PythonKeyTangent = "Tasto TANGENTE" +PythonKeyPi = "Tasto PI" +PythonKeySqrt = "Tasto RADICE QUADRATA" +PythonKeySquare = "Tasto QUADRATO" +PythonKeySeven = "Tasto 7" +PythonKeyEight = "Tasto 8" +PythonKeyNine = "Tasto 9" +PythonKeyLeftParenthesis = "Tasto PARENTESI SINISTRA" +PythonKeyRightParenthesis = "Tasto PARENTESI DESTRA" +PythonKeyFour = "Tasto 4" +PythonKeyFive = "Tasto 5" +PythonKeySix = "Tasto 6" +PythonKeyMultiplication = "Tasto MOLTIPLICAZIONE" +PythonKeyDivision = "Tasto DIVISIONE" +PythonKeyOne = "Tasto 1" +PythonKeyTwo = "Tasto 2" +PythonKeyThree = "Tasto 3" +PythonKeyPlus = "Tasto PIÙ" +PythonKeyMinus = "Tasto MENO" +PythonKeyZero = "Tasto 0" +PythonKeyDot = "Tasto PUNTO" +PythonKeyEe = "Tasto 10 POTENZA X" +PythonKeyAns = "Tasto ANS" +PythonKeyExe = "Tasto EXE" +PythonLdexp = "Inversa di frexp : x*(2**i)" +PythonLength = "Longhezza di un oggetto" +PythonLgamma = "Logaritmo della funzione gamma" +PythonLog = "Logaritmo di base a" +PythonLog10 = "Logaritmo decimale" +PythonLog2 = "Logaritmo di base 2" +PythonMathFunction = "Prefisso funzione del modulo math" +PythonMatplotlibPyplotFunction = "Prefisso modulo matplotlib.pyplot" +PythonMax = "Massimo" +PythonMin = "Minimo" +PythonModf = "Parti frazionarie e intere" +PythonMonotonic = "Restituisce il valore dell'orologio" +PythonOct = "Conversione in ottale" +PythonPhase = "Argomento di z" +PythonPlot = "Disegna y in f. di x come linee" +PythonPolar = "Conversione in polare" +PythonPop = "Cancella l'ultimo elemento" +PythonPower = "x alla potenza y" +PythonPrint = "Visualizza l'oggetto" +PythonRadians = "Conversione da gradi a radianti" +PythonRandint = "Intero aleatorio in [a,b]" +PythonRandom = "Numero aleatorio in [0,1[" +PythonRandomFunction = "Prefisso funzione modulo casuale" +PythonRandrange = "Numero dentro il range(start, stop)" +PythonRangeStartStop = "Lista da start a stop-1" +PythonRangeStop = "Lista da 0 a stop-1" +PythonRect = "Converte in coordinate algebriche" +PythonRemove = "Cancella la prima x dalla lista" +PythonReverse = "Inverte gli elementi della lista" +PythonRound = "Arrotondato a n cifre decimali" +PythonScatter = "Diagramma dispersione y in f. di x" +PythonSeed = "Inizializza il generatore random" +PythonSetPixel = "Colora il pixel (x,y)" +PythonShow = "Mostra la figura" +PythonSin = "Seno" +PythonSinh = "Seno iperbolico" +PythonSleep = "Sospende l'esecuzione t secondi" +PythonSort = "Ordina l'elenco" +PythonSqrt = "Radice quadrata" +PythonSum = "Somma degli elementi della lista" +PythonTan = "Tangente" +PythonTanh = "Tangente iperbolica" +PythonText = "Mostra un testo in (x,y)" +PythonTimeFunction = "Prefisso funzione modulo time" +PythonTrunc = "Troncamento intero" +PythonTurtleBackward = "Indietreggia di x pixels" +PythonTurtleCircle = "Cerchio di raggio r pixel" +PythonTurtleColor = "Modifica il colore del tratto" +PythonTurtleColorMode = "Imposta modalità colore a 1.0 o 255" +PythonTurtleForward = "Avanza di x pixel" +PythonTurtleFunction = "Prefisso funzione modello turtle" +PythonTurtleGoto = "Spostati alle coordinate (x,y)" +PythonTurtleHeading = "Restituisce l'orientamento attuale" +PythonTurtleHideturtle = "Nascondi la tartaruga" +PythonTurtleIsdown = "True se la penna è abbassata" +PythonTurtleLeft = "Ruota di a gradi a sinistra" +PythonTurtlePendown = "Abbassa la penna" +PythonTurtlePensize = "Dimensione del tratto in pixel" +PythonTurtlePenup = "Solleva la penna" +PythonTurtlePosition = "Fornisce posizione corrente (x,y)" +PythonTurtleReset = "Azzera il disegno" +PythonTurtleRight = "Ruota di a gradi a destra" +PythonTurtleSetheading = "Imposta l'orientamento per a gradi" +PythonTurtleSetposition = "Posiziona la tartaruga" +PythonTurtleShowturtle = "Mostra la tartaruga" +PythonTurtleSpeed = "Velocità di disegno (x tra 0 e 10)" +PythonTurtleWrite = "Mostra un testo" +PythonUniform = "Numero decimale tra [a,b]" diff --git a/apps/code/catalog.nl.i18n b/apps/code/catalog.nl.i18n new file mode 100644 index 000000000..843d9b3ab --- /dev/null +++ b/apps/code/catalog.nl.i18n @@ -0,0 +1,197 @@ +PythonPound = "Opmerkingen" +PythonPercent = "Modulo" +Python1J = "Imaginaire i" +PythonLF = "Nieuwe regel" +PythonTab = "Tabulatie" +PythonAmpersand = "Bitsgewijze en" +PythonSymbolExp = "Bitsgewijze exclusieve of" +PythonVerticalBar = "Bitsgewijze of" +PythonImag = "Imaginair deel van z" +PythonReal = "Reëel deel van z" +PythonSingleQuote = "Enkele aanhalingstekens" +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)" +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" +PythonBin = "Zet integer om in een binair getal" +PythonCeil = "Plafond" +PythonChoice = "Geeft willek. getal van de lijst" +PythonClear = "Lijst leegmaken" +PythonCmathFunction = "cmath module voorvoegsel" +PythonColor = "Definieer een rgb kleur" +PythonColorBlack = "Zwarte kleur" +PythonColorBlue = "Blauwe kleur" +PythonColorBrown = "Bruine kleur" +PythonColorGreen = "Groene kleur" +PythonColorGrey = "Grijze kleur" +PythonColorOrange = "Oranje kleur" +PythonColorPink = "Roze kleur" +PythonColorPurple = "Paarse kleur" +PythonColorRed = "Rode kleur" +PythonColorWhite = "Witte kleur" +PythonColorYellow = "Gele kleur" +PythonComplex = "Geeft a+ib" +PythonCopySign = "Geeft x met het teken van y" +PythonCos = "Cosinus" +PythonCosh = "Cosinus hyperbolicus" +PythonCount = "Tel voorkomen van x" +PythonDegrees = "Zet x om van radialen naar graden" +PythonDivMod = "Quotiënt en rest" +PythonDrawString = "Geef een tekst weer van pixel (x,y)" +PythonErf = "Error functie" +PythonErfc = "Complementaire error functie" +PythonEval = "Geef de geëvalueerde uitdrukking" +PythonExp = "Exponentiële functie" +PythonExpm1 = "Bereken exp(x)-1" +PythonFabs = "Absolute waarde" +PythonFillRect = "Vul een rechthoek bij pixel (x,y)" +PythonFloat = "Zet x om in een float" +PythonFloor = "Vloer" +PythonFmod = "a modulo b" +PythonFrExp = "Mantisse en exponent van x: (m,e)" +PythonGamma = "Gammafunctie" +PythonGetPixel = "Geef pixel (x,y) kleur (rgb)" +PythonGetrandbits = "Integer met k willekeurige bits" +PythonGrid = "Toggle the visibility of the grid" +PythonHex = "Zet integer om in hexadecimaal" +PythonHist = "Draw the histogram of 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" +PythonImportTime = "Importeer time module" +PythonImportTurtle = "Importeer turtle module" +PythonIndex = "Index van de eerste x aanwezigheden" +PythonInput = "Wijs een waarde toe" +PythonInsert = "Voeg x toe aan index i in de lijst" +PythonInt = "Zet x om in een integer" +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" +PythonKandinskyFunction = "kandinsky module voorvoegsel" +PythonKeyLeft = "PIJL NAAR LINKS toets" +PythonKeyUp = "PIJL OMHOOG toets" +PythonKeyDown = "PIJL OMLAAG toets" +PythonKeyRight = "PIJL NAAR RECHTS toets" +PythonKeyOk = "OK toets" +PythonKeyBack = "TERUG toets" +PythonKeyHome = "HOME toets" +PythonKeyOnOff = "AAN/UIT toets" +PythonKeyShift = "SHIFT toets" +PythonKeyAlpha = "ALPHA toets" +PythonKeyXnt = "X,N,T toets" +PythonKeyVar = "VAR toets" +PythonKeyToolbox = "TOOLBOX toets" +PythonKeyBackspace = "BACKSPACE toets" +PythonKeyExp = "EXPONENTIEEL toets" +PythonKeyLn = "NATUURLIJKE LOGARITME toets" +PythonKeyLog = "BRIGGSE LOGARITME toets" +PythonKeyImaginary = "IMAGINAIRE I toets" +PythonKeyComma = "KOMMA toets" +PythonKeyPower = "MACHT toets" +PythonKeySine = "SINUS toets" +PythonKeyCosine = "COSINUS toets" +PythonKeyTangent = "TANGENS toets" +PythonKeyPi = "PI toets" +PythonKeySqrt = "VIERKANTSWORTEL toets" +PythonKeySquare = "KWADRAAT toets" +PythonKeySeven = "7 toets" +PythonKeyEight = "8 toets" +PythonKeyNine = "9 toets" +PythonKeyLeftParenthesis = "HAAKJE OPENEN toets" +PythonKeyRightParenthesis = "HAAKJE SLUITEN toets" +PythonKeyFour = "4 toets" +PythonKeyFive = "5 toets" +PythonKeySix = "6 toets" +PythonKeyMultiplication = "VERMENIGVULDIGEN toets" +PythonKeyDivision = "DELEN toets" +PythonKeyOne = "1 toets" +PythonKeyTwo = "2 toets" +PythonKeyThree = "3 toets" +PythonKeyPlus = "PLUS toets" +PythonKeyMinus = "MIN toets" +PythonKeyZero = "0 toets" +PythonKeyDot = "PUNT toets" +PythonKeyEe = "10 TOT DE MACHT X toets" +PythonKeyAns = "ANS toets" +PythonKeyExe = "EXE toets" +PythonLdexp = "Geeft x*(2**i), inversie van frexp" +PythonLength = "Lengte van een object" +PythonLgamma = "Log-gammafunctie" +PythonLog = "Logaritme met grondgetal a" +PythonLog10 = "Logaritme met grondgetal 10" +PythonLog2 = "Logaritme met grondgetal 2" +PythonMathFunction = "math module voorvoegsel" +PythonMatplotlibPyplotFunction = "matplotlib.pyplot module prefix" +PythonMax = "Maximum" +PythonMin = "Minimum" +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" +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[" +PythonRandomFunction = "random module voorvoegsel" +PythonRandrange = "Willek. getal in range(start, stop)" +PythonRangeStartStop = "Lijst van start tot stop-1" +PythonRangeStop = "Lijst van 0 tot stop-1" +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" +PythonSeed = "Start willek. getallengenerator" +PythonSetPixel = "Kleur pixel (x,y)" +PythonShow = "Display the figure" +PythonSin= "Sinus" +PythonSinh = "Sinus hyperbolicus" +PythonSleep = "Stel executie voor t seconden uit" +PythonSort = "Sorteer de lijst" +PythonSqrt = "Vierkantswortel" +PythonSum = "Sommeer de items van een lijst" +PythonTan = "Tangens" +PythonTanh = "Tangens hyperbolicus" +PythonText = "Display a text at (x,y) coordinates" +PythonTimeFunction = "time module voorvoegsel" +PythonTrunc = "x afgeknot tot een integer" +PythonTurtleBackward = "Ga achterwaarts met x pixels" +PythonTurtleCircle = "Cirkel van straal r pixels" +PythonTurtleColor = "Stel de kleur van de pen in" +PythonTurtleColorMode = "Stel de kleurmodus in op 1.0 of 255" +PythonTurtleForward = "Ga voorwaarts met x pixels" +PythonTurtleFunction = "turtle module voorvoegsel" +PythonTurtleGoto = "Verplaats naar (x,y) coordinaten" +PythonTurtleHeading = "Ga terug naar de huidige koers" +PythonTurtleHideturtle = "Verberg de schildpad" +PythonTurtleIsdown = "Geeft True als pen naar beneden is" +PythonTurtleLeft = "Ga linksaf met a graden" +PythonTurtlePendown = "Zet de pen naar beneden" +PythonTurtlePensize = "Stel de lijndikte in op x pixels" +PythonTurtlePenup = "Zet de pen omhoog" +PythonTurtlePosition = "Zet huidige (x,y) locatie terug" +PythonTurtleReset = "Reset de tekening" +PythonTurtleRight = "Ga rechtsaf met a graden" +PythonTurtleSetheading = "Zet de oriëntatie op a graden" +PythonTurtleSetposition = "Plaats de schildpad" +PythonTurtleShowturtle = "Laat de schildpad zien" +PythonTurtleSpeed = "Tekensnelheid tussen 0 and 10" +PythonTurtleWrite = "Display a text" +PythonUniform = "Zwevendekommagetal in [a,b]" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index 97ef2861e..6eaf08c00 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -1,208 +1,204 @@ -PythonPound = "Comment" -PythonPercent = "Modulo" -Python1J = "Imaginary i" -PythonLF = "Line feed" -PythonTab = "Tabulation" -PythonAmpersand = "Bitwise and" -PythonSymbolExp = "Bitwise exclusive or" -PythonVerticalBar = "Bitwise or" -PythonSingleQuote = "Single quote" -PythonImag = "Imaginary part of z" -PythonReal = "Real part of z" -PythonAbs = "Absolute value/Magnitude" -PythonAcos = "Arc cosine" -PythonAcosh = "Arc hyperbolic cosine" -PythonAppend = "Add x to the end of the list" -PythonArrow = "Arrow from (x,y) to (x+dx,y+dy)" -PythonAsin = "Arc sine" -PythonAsinh = "Arc hyperbolic sine" -PythonAtan = "Arc tangent" -PythonAtan2 = "Return atan(y/x)" -PythonAtanh = "Arc hyperbolic tangent" -PythonAxis = "Set axes to (xmin,xmax,ymin,ymax)" -PythonBar = "Draw a bar plot with x values" -PythonBin = "Convert integer to binary" -PythonCeil = "Ceiling" -PythonChoice = "Random number in the list" -PythonClear = "Empty the list" -PythonCmathFunction = "cmath module function prefix" -PythonColor = "Define a rgb color" -PythonComplex = "Return a+ib" -PythonCopySign = "Return x with the sign of y" -PythonCos = "Cosine" -PythonCosh = "Hyperbolic cosine" -PythonCount = "Count the occurrences of x" -PythonDegrees = "Convert x from radians to degrees" -PythonDivMod = "Quotient and remainder" -PythonDrawString = "Display a text from pixel (x,y)" -PythonErf = "Error function" -PythonErfc = "Complementary error function" -PythonEval = "Return the evaluated expression" -PythonExp = "Exponential function" -PythonExpm1 = "Compute exp(x)-1" -PythonFabs = "Absolute value" -PythonFillRect = "Fill a rectangle at pixel (x,y)" -PythonFloat = "Convert x to a float" -PythonFloor = "Floor" -PythonFmod = "a modulo b" -PythonFrExp = "Mantissa and exponent of x" -PythonGamma = "Gamma function" -PythonGetPixel = "Return pixel (x,y) color" -PythonGetrandbits = "Integer with k random bits" -PythonGrid = "Toggle the visibility of the grid" -PythonHex = "Convert integer to hexadecimal" -PythonHist = "Draw the histogram of x" -PythonImportCmath = "Import cmath module" -PythonImportIon = "Import ion module" -PythonImportKandinsky = "Import kandinsky module" -PythonImportRandom = "Import random module" -PythonImportMath = "Import math module" -PythonImportMatplotlibPyplot = "Import matplotlib.pyplot module" -PythonImportTime = "Import time module" -PythonImportTurtle = "Import turtle module" -PythonIndex = "Index of the first x occurrence" -PythonInput = "Prompt a value" -PythonInsert = "Insert x at index i in the list" -PythonInt = "Convert x to an integer" -PythonIonFunction = "ion module function prefix" -PythonIsFinite = "Check if x is finite" -PythonIsInfinite = "Check if x is infinity" -PythonIsKeyDown = "Return True if the k key is down" -PythonIsNaN = "Check if x is a NaN" -PythonKandinskyFunction = "kandinsky module function prefix" -PythonKeyLeft = "LEFT ARROW key" -PythonKeyUp = "UP ARROW key" -PythonKeyDown = "DOWN ARROW key" -PythonKeyRight = "RIGHT ARROW key" -PythonKeyOk = "OK key" -PythonKeyBack = "BACK key" -PythonKeyHome = "HOME key" -PythonKeyOnOff = "ON/OFF key" -PythonKeyShift = "SHIFT key" -PythonKeyAlpha = "ALPHA key" -PythonKeyXnt = "X,N,T key" -PythonKeyVar = "VAR key" -PythonKeyToolbox = "TOOLBOX key" -PythonKeyBackspace = "BACKSPACE key" -PythonKeyExp = "EXPONENTIAL key" -PythonKeyLn = "NATURAL LOGARITHM key" -PythonKeyLog = "DECIMAL LOGARITHM key" -PythonKeyImaginary = "IMAGINARY I key" -PythonKeyComma = "COMMA key" -PythonKeyPower = "POWER key" -PythonKeySine = "SINE key" -PythonKeyCosine = "COSINE key" -PythonKeyTangent = "TANGENT key" -PythonKeyPi = "PI key" -PythonKeySqrt = "SQUARE ROOT key" -PythonKeySquare = "SQUARE key" -PythonKeySeven = "7 key" -PythonKeyEight = "8 key" -PythonKeyNine = "9 key" -PythonKeyLeftParenthesis = "LEFT PARENTHESIS key" -PythonKeyRightParenthesis = "RIGHT PARENTHESIS key" -PythonKeyFour = "4 key" -PythonKeyFive = "5 key" -PythonKeySix = "6 key" -PythonKeyMultiplication = "MULTIPLICATION key" -PythonKeyDivision = "DIVISION key" -PythonKeyOne = "1 key" -PythonKeyTwo = "2 key" -PythonKeyThree = "3 key" -PythonKeyPlus = "PLUS key" -PythonKeyMinus = "MINUS key" -PythonKeyZero = "0 key" -PythonKeyDot = "DOT key" -PythonKeyEe = "10 POWER X key" -PythonKeyAns = "ANS key" -PythonKeyExe = "EXE key" -PythonLdexp = "Return x*(2**i), inverse of frexp" -PythonLength = "Length of an object" -PythonLgamma = "Log-gamma function" -PythonLog = "Logarithm to base a" -PythonLog10 = "Logarithm to base 10" -PythonLog2 = "Logarithm to base 2" -PythonMathFunction = "math module function prefix" -PythonMatplotlibPyplotFunction = "matplotlib.pyplot module prefix" -PythonMax = "Maximum" -PythonMin = "Minimum" -PythonModf = "Fractional and integer parts of x" -PythonMonotonic = "Value of a monotonic clock" -PythonOct = "Convert integer to octal" -PythonPhase = "Phase of z" -PythonPlot = "Plot y versus x as lines" -PythonPolar = "z in polar coordinates" -PythonPop = "Remove and return the last item" -PythonPower = "x raised to the power y" -PythonPrint = "Print object" -PythonRadians = "Convert x from degrees to radians" -PythonRandint = "Random integer in [a,b]" -PythonRandom = "Floating point number in [0,1[" -PythonRandomFunction = "random module function prefix" -PythonRandrange = "Random number in range(start,stop)" -PythonRangeStartStop = "List from start to stop-1" -PythonRangeStop = "List from 0 to stop-1" -PythonRect = "Convert to cartesian coordinates" -PythonRemove = "Remove the first occurrence of x" -PythonReverse = "Reverse the elements of the list" -PythonRound = "Round to n digits" -PythonScatter = "Draw a scatter plot of y versus x" -PythonSeed = "Initialize random number generator" -PythonSetPixel = "Color pixel (x,y)" -PythonShow = "Display the figure" -PythonSin = "Sine" -PythonSinh = "Hyperbolic sine" -PythonSleep = "Suspend the execution for t seconds" -PythonSort = "Sort the list" -PythonSqrt = "Square root" -PythonSum = "Sum the items of a list" -PythonTan = "Tangent" -PythonTanh = "Hyperbolic tangent" -PythonText = "Display a text at (x,y) coordinates" -PythonTimeFunction = "time module function prefix" -PythonTrunc = "x truncated to an integer" -PythonTurtleBackward = "Move backward by x pixels" -PythonTurtleBlack = "Black color" -PythonTurtleBlue = "Blue color" -PythonTurtleBrown = "Brown color" -PythonTurtleCircle = "Circle of radius r pixels" -PythonTurtleColor = "Set the pen color" -PythonTurtleColorMode = "Set the color mode to 1.0 or 255" -PythonTurtleForward = "Move forward by x pixels" -PythonTurtleFunction = "turtle module function prefix" -PythonTurtleGoto = "Move to (x,y) coordinates" -PythonTurtleGreen = "Green color" -PythonTurtleGrey = "Grey color" -PythonTurtleHeading = "Return the current heading" -PythonTurtleHideturtle = "Hide the turtle" -PythonTurtleIsdown = "Return True if the pen is down" -PythonTurtleLeft = "Turn left by a degrees" -PythonTurtleOrange = "Orange color" -PythonTurtlePendown = "Pull the pen down" -PythonTurtlePensize = "Set the line thickness to x pixels" -PythonTurtlePenup = "Pull the pen up" -PythonTurtlePink = "Pink color" -PythonTurtlePosition = "Return the current (x,y) location" -PythonTurtlePurple = "Purple color" -PythonTurtleRed = "Red color" -PythonTurtleReset = "Reset the drawing" -PythonTurtleRight = "Turn right by a degrees" -PythonTurtleSetheading = "Set the orientation to a degrees" +PythonPound = "Comentário" +PythonPercent = "Módulo" +Python1J = "i Complexo" +PythonLF = "Nova linha" +PythonTab = "Tabulação" +PythonAmpersand = "Operador binário and" +PythonSymbolExp = "Operador binário exclusivo or" +PythonVerticalBar = "Operador binário or" +PythonSingleQuote = "Apóstrofo" +PythonImag = "Parte imaginária de z" +PythonReal = "Parte real de z" +PythonAbs = "Valor absoluto/módulo" +PythonAcos = "Arco cosseno" +PythonAcosh = "Arco cosseno hiperbólico" +PythonAppend = "Adicionar x no fim da lista" +PythonArrow = "Seta de (x,y) para (x+dx,y+dy)" +PythonAsin = "Arco seno" +PythonAsinh = "Arco seno hiperbólico" +PythonAtan = "Arco tangente" +PythonAtan2 = "Cálculo de atan(y/x)" +PythonAtanh = "Arco tangente hiperbólica" +PythonAxis = "Definir eixos (xmin,xmax,ymin,ymax)" +PythonBar = "Gráfico de barras com valores de x" +PythonBin = "Converter número inteiro em binário" +PythonCeil = "Teto" +PythonChoice = "Número aleatório na lista" +PythonClear = "Esvaziar a lista" +PythonCmathFunction = "Prefixo da função do módulo cmath" +PythonColor = "Define uma cor rgb" +PythonColorBlack = "Cor preta" +PythonColorBlue = "Cor azul" +PythonColorBrown = "Cor castanha" +PythonColorGreen = "Cor verde" +PythonColorGrey = "Cor cinzenta" +PythonColorOrange = "Cor laranja" +PythonColorPink = "Cor rosa" +PythonColorPurple = "Cor roxa" +PythonColorRed = "Cor vermelha" +PythonColorWhite = "Cor branca" +PythonColorYellow = "Cor amarela" +PythonComplex = "Devolve a+ib" +PythonCopySign = "Devolve x com o sinal de y" +PythonCos = "Cosseno" +PythonCosh = "Cosseno hiperbólico" +PythonCount = "Contar as ocorrências de x" +PythonDegrees = "Converter x de radianos para graus" +PythonDivMod = "Quociente e resto" +PythonDrawString = "Mostrar o texto do pixel (x,y)" +PythonErf = "Função erro" +PythonErfc = "Função erro complementar" +PythonEval = "Devolve a expressão avaliada" +PythonExp = "Função exponencial" +PythonExpm1 = "Calcular exp(x)-1" +PythonFabs = "Valor absoluto" +PythonFillRect = "Preencher um retângulo em (x,y)" +PythonFloat = "Converter x num flutuante" +PythonFloor = "Parte inteira" +PythonFmod = "a módulo b" +PythonFrExp = "Coeficiente e expoente de x: (m, e)" +PythonGamma = "Função gama" +PythonGetPixel = "Devolve a cor do pixel (x,y)" +PythonGetrandbits = "Número inteiro aleatório com k bits" +PythonGrid = "Alterar visibilidade da grelha" +PythonHex = "Converter inteiro em hexadecimal" +PythonHist = "Desenhar o histograma de x" +PythonImportCmath = "Importar módulo cmath" +PythonImportIon = "Importar módulo ion" +PythonImportKandinsky = "Importar módulo kandinsky" +PythonImportRandom = "Importar módulo random" +PythonImportMath = "Importar módulo math" +PythonImportMatplotlibPyplot = "Importar módulo matplotlib.pyplot" +PythonImportTime = "Importar módulo time" +PythonImportTurtle = "Importar módulo turtle" +PythonIndex = "Índice da primeira ocorrência de x" +PythonInput = "Adicionar um valor" +PythonInsert = "Inserir x no índice i na lista" +PythonInt = "Converter x num número inteiro" +PythonIonFunction = "Prefixo da função do módulo ion" +PythonIsFinite = "Verificar se x é finito" +PythonIsInfinite = "Verificar se x é infinito" +PythonIsKeyDown = "Devolve True se tecla k pressionada" +PythonIsNaN = "Verificar se x é um NaN" +PythonKandinskyFunction = "Prefixo da função do módulo kandinsky" +PythonKeyLeft = "tecla SETA ESQUERDA" +PythonKeyUp = "tecla SETA CIMA " +PythonKeyDown = "tecla SETA BAIXO" +PythonKeyRight = "tecla SETA DIREITA" +PythonKeyOk = "tecla OK" +PythonKeyBack = "tecla VOLTAR" +PythonKeyHome = "tecla HOME" +PythonKeyOnOff = "tecla ON/OFF" +PythonKeyShift = "tecla SHIFT" +PythonKeyAlpha = "tecla ALPHA" +PythonKeyXnt = "tecla X,N,T" +PythonKeyVar = "tecla VAR" +PythonKeyToolbox = "tecla CAIXA DE FERRAMENTAS" +PythonKeyBackspace = "tecla APAGAR" +PythonKeyExp = "tecla EXPONENCIAL" +PythonKeyLn = "tecla LOGARITMO NATURAL" +PythonKeyLog = "tecla LOGARITMO DECIMAL" +PythonKeyImaginary = "tecla I IMAGINÁRIO" +PythonKeyComma = "tecla VÍRGULA" +PythonKeyPower = "tecla EXPOENTE" +PythonKeySine = "tecla SENO" +PythonKeyCosine = "tecla COSSENO" +PythonKeyTangent = "tecla TANGENTE" +PythonKeyPi = "tecla PI" +PythonKeySqrt = "tecla RAIZ QUADRADA" +PythonKeySquare = "tecla AO QUADRADO" +PythonKeySeven = "tecla 7" +PythonKeyEight = "tecla 8" +PythonKeyNine = "tecla 9" +PythonKeyLeftParenthesis = "tecla PARÊNTESE ESQUERDO" +PythonKeyRightParenthesis = "tecla PARÊNTESE DIREITO" +PythonKeyFour = "tecla 4" +PythonKeyFive = "tecla 5" +PythonKeySix = "tecla 6" +PythonKeyMultiplication = "tecla MULTIPLICAÇÃO" +PythonKeyDivision = "tecla DIVISÃO" +PythonKeyOne = "tecla 1" +PythonKeyTwo = "tecla 2" +PythonKeyThree = "tecla 3" +PythonKeyPlus = "tecla MAIS" +PythonKeyMinus = "tecla MENOS" +PythonKeyZero = "tecla 0" +PythonKeyDot = "tecla PONTO" +PythonKeyEe = "tecla 10 expoente X" +PythonKeyAns = "tecla ANS" +PythonKeyExe = "tecla EXE" +PythonLdexp = "Devolve x*(2**i), inverso de frexp" +PythonLength = "Comprimento de um objeto" +PythonLgamma = "Logaritmo da função gama" +PythonLog = "Logaritmo de base a" +PythonLog10 = "Logaritmo de base 10" +PythonLog2 = "Logaritmo de base 2" +PythonMathFunction = "Prefixo da função do módulo math" +PythonMatplotlibPyplotFunction = "Prefixo do módulo matplotlib.pyplot" +PythonMax = "Máximo" +PythonMin = "Mínimo" +PythonModf = "Partes inteira e frácionária de x" +PythonMonotonic = "Devolve o valor do relógio" +PythonOct = "Converter número inteiro em octal" +PythonPhase = "Argumento de z" +PythonPlot = "Desenhar y em função de x" +PythonPolar = "z em coordenadas polares" +PythonPop = "Remover o último item" +PythonPower = "x levantado a y" +PythonPrint = "Mostrar o objeto" +PythonRadians = "Converter x de graus para radianos" +PythonRandint = "Número inteiro aleatório em [a,b]" +PythonRandom = "Número decimal em [0,1[" +PythonRandomFunction = "Prefixo da função do módulo random" +PythonRandrange = "Número aleatório em [start,stop-1]" +PythonRangeStartStop = "Lista de start a stop-1" +PythonRangeStop = "Lista de 0 a stop-1" +PythonRect = "Converter para coordenadas cartesianas" +PythonRemove = "Remover a primeira ocorrência de x" +PythonReverse = "Inverter os elementos da lista" +PythonRound = "Arredondar para n dígitos" +PythonScatter = "Gráfico de dispersão (x,y)" +PythonSeed = "Iniciar gerador aleatório" +PythonSetPixel = "Cor do pixel (x,y)" +PythonShow = "Mostrar a figura" +PythonSin = "Seno" +PythonSinh = "Seno hiperbólico" +PythonSleep = "Suspender a execução por t segundos" +PythonSort = "Ordenar a lista" +PythonSqrt = "Raiz quadrada" +PythonSum = "Soma dos itens da lista" +PythonTan = "Tangente" +PythonTanh = "Tangente hiperbólica" +PythonText = "Mostrar um texto em (x,y)" +PythonTimeFunction = "Prefixo da função do módulo time" +PythonTrunc = "x truncado a um número inteiro" +PythonTurtleBackward = "Recuar x pixels" +PythonTurtleCircle = "Circunferência de raio r pixels" +PythonTurtleColor = "Definir a cor da caneta" +PythonTurtleColorMode = "Define modo de cor para 1.0 ou 255" +PythonTurtleForward = "Avançar x pixels" +PythonTurtleFunction = "Prefixo da função do módulo turtle" +PythonTurtleGoto = "Ir paras as coordenadas (x,y)" +PythonTurtleHeading = "Voltar para a orientação atual" +PythonTurtleHideturtle = "Esconder o turtle" +PythonTurtleIsdown = "True se a caneta está pressionada" +PythonTurtleLeft = "Vira à esquerda por a graus" +PythonTurtlePendown = "Puxar a caneta para baixo" +PythonTurtlePensize = "Definir a espessura para x pixels" +PythonTurtlePenup = "Puxar a caneta para cima" +PythonTurtlePosition = "Devolve a posição atual (x,y)" +PythonTurtleReset = "Reiniciar o desenho" +PythonTurtleRight = "Virar à esquerda por a graus" +PythonTurtleSetheading = "Definir a orientação por a graus" PythonTurtleSetposition = "Positionne la tortue" -PythonTurtleShowturtle = "Show the turtle" -PythonTurtleSpeed = "Drawing speed between 0 and 10" -PythonTurtleWhite = "White color" -PythonTurtleYellow = "Yellow color" -PythonUniform = "Floating point number in [a,b]" -PythonTimeFromImport = "Import time module" -PythonTimeImport = "Import time module" -PythonTimePrefix = "time module function prefix" -PythonTimeSleep = "Aguardar n segundos" -PythonTimeMonotonic = "Retornar tempo monotônico" +PythonTurtleShowturtle = "Mostrar o turtle" +PythonTurtleSpeed = "Velocidade do desenho entre 0 e 10" +PythonTurtleWrite = "Mostrar um texto" +PythonUniform = "Número decimal em [a,b]" PythonFileOpen = "Opens a file" PythonFileSeekable = "Tells if seek can be used on a file" -PythonFileSeek = "Move file's internal cursor" -PythonFileTell = "Get file's internal cursor location" +PythonFileSeek = "Move file's cursor" +PythonFileTell = "Get file's cursor location" PythonFileClose = "Closes a file" PythonFileClosed = "True if file was closed" PythonFileRead = "Read up to size bytes" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index bd028fe81..371a33f44 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -29,6 +29,17 @@ PythonCommandClearWithoutArg = ".clear()" PythonCommandCmathFunction = "cmath.function" PythonCommandCmathFunctionWithoutArg = "cmath.\x11" PythonCommandColor = "color(r,g,b)" +PythonCommandColorBlack = "'black'" +PythonCommandColorBlue = "'blue'" +PythonCommandColorBrown = "'brown'" +PythonCommandColorGreen = "'green'" +PythonCommandColorGrey = "'grey'" +PythonCommandColorOrange = "'orange'" +PythonCommandColorPink = "'pink'" +PythonCommandColorPurple = "'purple'" +PythonCommandColorRed = "'red'" +PythonCommandColorWhite = "'white'" +PythonCommandColorYellow = "'yellow'" PythonCommandComplex = "complex(a,b)" PythonCommandConstantPi = "pi" PythonCommandCopySign = "copysign(x,y)" @@ -154,7 +165,7 @@ PythonCommandModf = "modf(x)" PythonCommandMonotonic = "monotonic()" PythonCommandOct = "oct(x)" PythonCommandPhase = "phase(z)" -PythonCommandPlot = "plot(x,y)" +PythonCommandPlot = "plot(x,y,color)" PythonCommandPolar = "polar(z)" PythonCommandPop = "list.pop()" PythonCommandPopWithoutArg = ".pop()" @@ -202,29 +213,19 @@ PythonCommandUniform = "uniform(a,b)" PythonConstantE = "2.718281828459045" PythonConstantPi = "3.141592653589793" PythonTurtleCommandBackward = "backward(x)" -PythonTurtleCommandBlack = "'black'" -PythonTurtleCommandBlue = "'blue'" -PythonTurtleCommandBrown = "'brown'" PythonTurtleCommandCircle = "circle(r)" -PythonTurtleCommandColor = "color('c')/color(r,g,b)" -PythonTurtleCommandColorWithoutArg = "color(\x11)" +PythonTurtleCommandColor = "color('c')" PythonTurtleCommandColorMode = "colormode(x)" PythonTurtleCommandForward = "forward(x)" PythonTurtleCommandGoto = "goto(x,y)" -PythonTurtleCommandGreen = "'green'" -PythonTurtleCommandGrey = "'grey'" PythonTurtleCommandHeading = "heading()" PythonTurtleCommandHideturtle = "hideturtle()" PythonTurtleCommandIsdown= "isdown()" PythonTurtleCommandLeft = "left(a)" -PythonTurtleCommandOrange = "'orange'" PythonTurtleCommandPendown = "pendown()" PythonTurtleCommandPensize = "pensize(x)" PythonTurtleCommandPenup = "penup()" -PythonTurtleCommandPink = "'pink'" PythonTurtleCommandPosition = "position()" -PythonTurtleCommandPurple = "'purple'" -PythonTurtleCommandRed = "'red'" PythonTurtleCommandReset = "reset()" PythonTurtleCommandRight = "right(a)" PythonTurtleCommandSetheading = "setheading(a)" @@ -270,3 +271,4 @@ PythonCommandFileReadable = "file.readable()" PythonCommandFileReadableWithoutArg = ".readable()" PythonCommandFileWritable = "file.writable()" PythonCommandFileWritableWithoutArg = ".writable()" +PythonTurtleCommandWrite = "write(\"text\")" diff --git a/apps/code/console_controller.cpp b/apps/code/console_controller.cpp index 9a29b53b0..2d01720da 100644 --- a/apps/code/console_controller.cpp +++ b/apps/code/console_controller.cpp @@ -31,7 +31,7 @@ ConsoleController::ConsoleController(Responder * parentResponder, App * pythonDe m_pythonDelegate(pythonDelegate), m_importScriptsWhenViewAppears(false), m_selectableTableView(this, this, this, this), - m_editCell(this, pythonDelegate, this), + m_editCell(this, this, this), m_scriptStore(scriptStore), m_sandboxController(this), m_inputRunLoopActive(false) @@ -48,17 +48,13 @@ ConsoleController::ConsoleController(Responder * parentResponder, App * pythonDe } bool ConsoleController::loadPythonEnvironment() { - if (m_pythonDelegate->isPythonUser(this)) { - return true; + if (!m_pythonDelegate->isPythonUser(this)) { + m_scriptStore->clearConsoleFetchInformation(); + emptyOutputAccumulationBuffer(); + m_pythonDelegate->initPythonWithUser(this); + MicroPython::registerScriptProvider(m_scriptStore); + m_importScriptsWhenViewAppears = m_autoImportScripts; } - emptyOutputAccumulationBuffer(); - m_pythonDelegate->initPythonWithUser(this); - MicroPython::registerScriptProvider(m_scriptStore); - m_importScriptsWhenViewAppears = m_autoImportScripts; - /* We load functions and variables names in the variable box before running - * any other python code to avoid failling to load functions and variables - * due to memory exhaustion. */ - App::app()->variableBoxController()->loadFunctionsAndVariables(); return true; } @@ -290,7 +286,7 @@ void ConsoleController::willDisplayCellAtLocation(HighlightCell * cell, int i, i } } -void ConsoleController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { +void ConsoleController::tableViewDidChangeSelectionAndDidScroll(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { if (withinTemporarySelection) { return; } @@ -375,6 +371,14 @@ bool ConsoleController::textFieldDidAbortEditing(TextField * textField) { return true; } +VariableBoxController * ConsoleController::variableBoxForInputEventHandler(InputEventHandler * textInput) { + VariableBoxController * varBox = App::app()->variableBoxController(); + varBox->loadVariablesImportedFromScripts(); + varBox->setTitle(I18n::Message::FunctionsAndVariables); + varBox->setDisplaySubtitles(false); + return varBox; +} + void ConsoleController::resetSandbox() { if (stackViewController()->topViewController() != sandbox()) { return; @@ -479,7 +483,7 @@ void ConsoleController::autoImportScript(Script script, bool force) { * the sandbox. */ hideAnyDisplayedViewController(); - if (script.importationStatus() || force) { + if (script.autoImportationStatus() || force) { // Step 1 - Create the command "from scriptName import *". assert(strlen(k_importCommand1) + strlen(script.fullName()) - strlen(ScriptStore::k_scriptExtension) - 1 + strlen(k_importCommand2) + 1 <= k_maxImportCommandSize); diff --git a/apps/code/console_controller.h b/apps/code/console_controller.h index e50d31c34..d4fa256f6 100644 --- a/apps/code/console_controller.h +++ b/apps/code/console_controller.h @@ -10,12 +10,14 @@ #include "console_store.h" #include "sandbox_controller.h" #include "script_store.h" +#include "variable_box_controller.h" +#include "../shared/input_event_handler_delegate.h" namespace Code { class App; -class ConsoleController : public ViewController, public ListViewDataSource, public SelectableTableViewDataSource, public SelectableTableViewDelegate, public TextFieldDelegate, public MicroPython::ExecutionEnvironment { +class ConsoleController : public ViewController, public ListViewDataSource, public SelectableTableViewDataSource, public SelectableTableViewDelegate, public TextFieldDelegate, public Shared::InputEventHandlerDelegate, public MicroPython::ExecutionEnvironment { public: ConsoleController(Responder * parentResponder, App * pythonDelegate, ScriptStore * scriptStore #if EPSILON_GETOPT @@ -52,7 +54,7 @@ public: void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; // SelectableTableViewDelegate - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; + void tableViewDidChangeSelectionAndDidScroll(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; // TextFieldDelegate bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; @@ -60,6 +62,9 @@ public: bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; bool textFieldDidAbortEditing(TextField * textField) override; + // InputEventHandlerDelegate + VariableBoxController * variableBoxForInputEventHandler(InputEventHandler * textInput) override; + // MicroPython::ExecutionEnvironment ViewController * sandbox() override { return &m_sandboxController; } void resetSandbox() override; diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index 583e54cc1..7e14421a9 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -13,13 +13,15 @@ EditorController::EditorController(MenuController * menuController, App * python ViewController(nullptr), m_editorView(this, pythonDelegate), m_script(Ion::Storage::Record()), + m_scriptIndex(-1), m_menuController(menuController) { m_editorView.setTextAreaDelegates(this, this); } -void EditorController::setScript(Script script) { +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 * storage available space at the end of the current edited script and we set @@ -35,7 +37,7 @@ void EditorController::setScript(Script script) { * */ size_t newScriptSize = Ion::Storage::sharedStorage()->putAvailableSpaceAtEndOfRecord(m_script); - m_editorView.setText(const_cast(m_script.scriptContent()), newScriptSize - Script::k_importationStatusSize); + m_editorView.setText(const_cast(m_script.content()), newScriptSize - Script::StatusSize()); } void EditorController::willExitApp() { @@ -131,7 +133,24 @@ bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events: VariableBoxController * EditorController::variableBoxForInputEventHandler(InputEventHandler * textInput) { VariableBoxController * varBox = App::app()->variableBoxController(); - varBox->loadFunctionsAndVariables(); + /* If the editor should be autocompleting an identifier, the variable box has + * already been loaded. We check shouldAutocomplete and not isAutocompleting, + * because the autocompletion result might be empty. */ + const char * beginningOfAutocompletion = nullptr; + const char * cursor = nullptr; + PythonTextArea::AutocompletionType autocompType = m_editorView.autocompletionType(&beginningOfAutocompletion, &cursor); + if (autocompType == PythonTextArea::AutocompletionType::NoIdentifier) { + varBox->loadFunctionsAndVariables(m_scriptIndex, nullptr, 0); + } else if (autocompType == PythonTextArea::AutocompletionType::MiddleOfIdentifier) { + varBox->empty(); + } else { + assert(autocompType == PythonTextArea::AutocompletionType::EndOfIdentifier); + assert(beginningOfAutocompletion != nullptr && cursor != nullptr); + assert(cursor > beginningOfAutocompletion); + varBox->loadFunctionsAndVariables(m_scriptIndex, beginningOfAutocompletion, cursor - beginningOfAutocompletion); + } + varBox->setTitle(I18n::Message::Autocomplete); + varBox->setDisplaySubtitles(true); return varBox; } @@ -146,7 +165,7 @@ void EditorController::cleanStorageEmptySpace() { Ion::Storage::Record::Data scriptValue = m_script.value(); Ion::Storage::sharedStorage()->getAvailableSpaceFromEndOfRecord( m_script, - scriptValue.size - Script::k_importationStatusSize - (strlen(m_script.scriptContent()) + 1)); // TODO optimize number of script fetches + scriptValue.size - Script::StatusSize() - (strlen(m_script.content()) + 1)); // TODO optimize number of script fetches } diff --git a/apps/code/editor_controller.h b/apps/code/editor_controller.h index 7fb5d9560..4cd32c1ed 100644 --- a/apps/code/editor_controller.h +++ b/apps/code/editor_controller.h @@ -16,7 +16,8 @@ class App; class EditorController : public ViewController, public TextAreaDelegate, public Shared::InputEventHandlerDelegate { public: EditorController(MenuController * menuController, App * pythonDelegate); - void setScript(Script script); + void setScript(Script script, int scriptIndex); + int scriptIndex() const { return m_scriptIndex; } void willExitApp(); /* ViewController */ @@ -39,6 +40,7 @@ private: StackViewController * stackController(); EditorView m_editorView; Script m_script; + int m_scriptIndex; MenuController * m_menuController; }; diff --git a/apps/code/editor_view.cpp b/apps/code/editor_view.cpp index 2e0c63acd..8c4832c8c 100644 --- a/apps/code/editor_view.cpp +++ b/apps/code/editor_view.cpp @@ -17,6 +17,10 @@ EditorView::EditorView(Responder * parentResponder, App * pythonDelegate) : m_textArea.setScrollViewDelegate(this); } +bool EditorView::isAutocompleting() const { + return m_textArea.isAutocompleting(); +} + void EditorView::resetSelection() { m_textArea.resetSelection(); } diff --git a/apps/code/editor_view.h b/apps/code/editor_view.h index bbc608e88..ab3038c94 100644 --- a/apps/code/editor_view.h +++ b/apps/code/editor_view.h @@ -9,6 +9,8 @@ namespace Code { class EditorView : public Responder, public View, public ScrollViewDelegate { public: EditorView(Responder * parentResponder, App * pythonDelegate); + PythonTextArea::AutocompletionType autocompletionType(const char ** autocompletionBeginning, const char ** autocompletionEnd) const { return m_textArea.autocompletionType(nullptr, autocompletionBeginning, autocompletionEnd); } + bool isAutocompleting() const; void resetSelection(); void setTextAreaDelegates(InputEventHandlerDelegate * inputEventHandlerDelegate, TextAreaDelegate * delegate) { m_textArea.setDelegates(inputEventHandlerDelegate, delegate); @@ -17,6 +19,9 @@ public: void setText(char * textBuffer, size_t textBufferSize) { m_textArea.setText(textBuffer, textBufferSize); } + const char * cursorLocation() { + return m_textArea.cursorLocation(); + } bool setCursorLocation(const char * location) { return m_textArea.setCursorLocation(location); } diff --git a/apps/code/menu_controller.cpp b/apps/code/menu_controller.cpp index e797ba675..a857fc539 100644 --- a/apps/code/menu_controller.cpp +++ b/apps/code/menu_controller.cpp @@ -385,7 +385,7 @@ void MenuController::configureScript() { void MenuController::editScriptAtIndex(int scriptIndex) { assert(scriptIndex >=0 && scriptIndex < m_scriptStore->numberOfScripts()); Script script = m_scriptStore->scriptAtIndex(scriptIndex); - m_editorController.setScript(script); + m_editorController.setScript(script, scriptIndex); stackViewController()->push(&m_editorController); } diff --git a/apps/code/menu_controller.h b/apps/code/menu_controller.h index 5730fdccb..72bb63130 100644 --- a/apps/code/menu_controller.h +++ b/apps/code/menu_controller.h @@ -25,6 +25,7 @@ public: void openConsoleWithScript(Script script); void scriptContentEditionDidFinish(); void willExitApp(); + int editedScriptIndex() const { return m_editorController.scriptIndex(); } /* ViewController */ View * view() override { return &m_selectableTableView; } diff --git a/apps/code/python_text_area.cpp b/apps/code/python_text_area.cpp index 4df023dae..eef77034a 100644 --- a/apps/code/python_text_area.cpp +++ b/apps/code/python_text_area.cpp @@ -21,6 +21,7 @@ constexpr KDColor OperatorColor = Palette::CodeOperator; constexpr KDColor StringColor = Palette::CodeString; constexpr KDColor BackgroundColor = Palette::CodeBackground; constexpr KDColor HighlightColor = Palette::CodeBackgroundSelected; +constexpr KDColor AutocompleteColor = KDColor::RGB24(0xC6C6C6); // TODO Palette change static inline KDColor TokenColor(mp_token_kind_t tokenKind) { if (tokenKind == MP_TOKEN_STRING) { @@ -29,13 +30,96 @@ static inline KDColor TokenColor(mp_token_kind_t tokenKind) { if (tokenKind == MP_TOKEN_INTEGER || tokenKind == MP_TOKEN_FLOAT_OR_IMAG) { return NumberColor; } + static_assert(MP_TOKEN_ELLIPSIS + 1 == MP_TOKEN_KW_FALSE + && MP_TOKEN_KW_FALSE + 1 == MP_TOKEN_KW_NONE + && MP_TOKEN_KW_NONE + 1 == MP_TOKEN_KW_TRUE + && MP_TOKEN_KW_TRUE + 1 == MP_TOKEN_KW___DEBUG__ + && MP_TOKEN_KW___DEBUG__ + 1 == MP_TOKEN_KW_AND + && MP_TOKEN_KW_AND + 1 == MP_TOKEN_KW_AS + && MP_TOKEN_KW_AS + 1 == MP_TOKEN_KW_ASSERT + /* Here there are keywords that depend on MICROPY_PY_ASYNC_AWAIT, we do + * not test them */ + && MP_TOKEN_KW_BREAK + 1 == MP_TOKEN_KW_CLASS + && MP_TOKEN_KW_CLASS + 1 == MP_TOKEN_KW_CONTINUE + && MP_TOKEN_KW_CONTINUE + 1 == MP_TOKEN_KW_DEF + && MP_TOKEN_KW_DEF + 1 == MP_TOKEN_KW_DEL + && MP_TOKEN_KW_DEL + 1 == MP_TOKEN_KW_ELIF + && MP_TOKEN_KW_ELIF + 1 == MP_TOKEN_KW_ELSE + && MP_TOKEN_KW_ELSE + 1 == MP_TOKEN_KW_EXCEPT + && MP_TOKEN_KW_EXCEPT + 1 == MP_TOKEN_KW_FINALLY + && MP_TOKEN_KW_FINALLY + 1 == MP_TOKEN_KW_FOR + && MP_TOKEN_KW_FOR + 1 == MP_TOKEN_KW_FROM + && MP_TOKEN_KW_FROM + 1 == MP_TOKEN_KW_GLOBAL + && MP_TOKEN_KW_GLOBAL + 1 == MP_TOKEN_KW_IF + && MP_TOKEN_KW_IF + 1 == MP_TOKEN_KW_IMPORT + && MP_TOKEN_KW_IMPORT + 1 == MP_TOKEN_KW_IN + && MP_TOKEN_KW_IN + 1 == MP_TOKEN_KW_IS + && MP_TOKEN_KW_IS + 1 == MP_TOKEN_KW_LAMBDA + && MP_TOKEN_KW_LAMBDA + 1 == MP_TOKEN_KW_NONLOCAL + && MP_TOKEN_KW_NONLOCAL + 1 == MP_TOKEN_KW_NOT + && MP_TOKEN_KW_NOT + 1 == MP_TOKEN_KW_OR + && MP_TOKEN_KW_OR + 1 == MP_TOKEN_KW_PASS + && MP_TOKEN_KW_PASS + 1 == MP_TOKEN_KW_RAISE + && MP_TOKEN_KW_RAISE + 1 == MP_TOKEN_KW_RETURN + && MP_TOKEN_KW_RETURN + 1 == MP_TOKEN_KW_TRY + && MP_TOKEN_KW_TRY + 1 == MP_TOKEN_KW_WHILE + && MP_TOKEN_KW_WHILE + 1 == MP_TOKEN_KW_WITH + && MP_TOKEN_KW_WITH + 1 == MP_TOKEN_KW_YIELD + && MP_TOKEN_KW_YIELD + 1 == MP_TOKEN_OP_TILDE, + "MP_TOKEN order changed, so Code::PythonTextArea::TokenColor might need to change too."); if (tokenKind >= MP_TOKEN_KW_FALSE && tokenKind <= MP_TOKEN_KW_YIELD) { return KeywordColor; } - if (tokenKind >= MP_TOKEN_OP_PLUS && tokenKind <= MP_TOKEN_OP_NOT_EQUAL) { - return OperatorColor; - } - if (tokenKind >= MP_TOKEN_DEL_EQUAL && tokenKind <= MP_TOKEN_DEL_MINUS_MORE) { + static_assert(MP_TOKEN_OP_TILDE + 1 == MP_TOKEN_OP_LESS + && MP_TOKEN_OP_LESS + 1 == MP_TOKEN_OP_MORE + && MP_TOKEN_OP_MORE + 1 == MP_TOKEN_OP_DBL_EQUAL + && MP_TOKEN_OP_DBL_EQUAL + 1 == MP_TOKEN_OP_LESS_EQUAL + && MP_TOKEN_OP_LESS_EQUAL + 1 == MP_TOKEN_OP_MORE_EQUAL + && MP_TOKEN_OP_MORE_EQUAL + 1 == MP_TOKEN_OP_NOT_EQUAL + && MP_TOKEN_OP_NOT_EQUAL + 1 == MP_TOKEN_OP_PIPE + && MP_TOKEN_OP_PIPE + 1 == MP_TOKEN_OP_CARET + && MP_TOKEN_OP_CARET + 1 == MP_TOKEN_OP_AMPERSAND + && MP_TOKEN_OP_AMPERSAND + 1 == MP_TOKEN_OP_DBL_LESS + && MP_TOKEN_OP_DBL_LESS + 1 == MP_TOKEN_OP_DBL_MORE + && MP_TOKEN_OP_DBL_MORE + 1 == MP_TOKEN_OP_PLUS + && MP_TOKEN_OP_PLUS + 1 == MP_TOKEN_OP_MINUS + && MP_TOKEN_OP_MINUS + 1 == MP_TOKEN_OP_STAR + && MP_TOKEN_OP_STAR + 1 == MP_TOKEN_OP_AT + && MP_TOKEN_OP_AT + 1 == MP_TOKEN_OP_DBL_SLASH + && MP_TOKEN_OP_DBL_SLASH + 1 == MP_TOKEN_OP_SLASH + && MP_TOKEN_OP_SLASH + 1 == MP_TOKEN_OP_PERCENT + && MP_TOKEN_OP_PERCENT + 1 == MP_TOKEN_OP_DBL_STAR + && MP_TOKEN_OP_DBL_STAR + 1 == MP_TOKEN_DEL_PIPE_EQUAL + && MP_TOKEN_DEL_PIPE_EQUAL + 1 == MP_TOKEN_DEL_CARET_EQUAL + && MP_TOKEN_DEL_CARET_EQUAL + 1 == MP_TOKEN_DEL_AMPERSAND_EQUAL + && MP_TOKEN_DEL_AMPERSAND_EQUAL + 1 == MP_TOKEN_DEL_DBL_LESS_EQUAL + && MP_TOKEN_DEL_DBL_LESS_EQUAL + 1 == MP_TOKEN_DEL_DBL_MORE_EQUAL + && MP_TOKEN_DEL_DBL_MORE_EQUAL + 1 == MP_TOKEN_DEL_PLUS_EQUAL + && MP_TOKEN_DEL_PLUS_EQUAL + 1 == MP_TOKEN_DEL_MINUS_EQUAL + && MP_TOKEN_DEL_MINUS_EQUAL + 1 == MP_TOKEN_DEL_STAR_EQUAL + && MP_TOKEN_DEL_STAR_EQUAL + 1 == MP_TOKEN_DEL_AT_EQUAL + && MP_TOKEN_DEL_AT_EQUAL + 1 == MP_TOKEN_DEL_DBL_SLASH_EQUAL + && MP_TOKEN_DEL_DBL_SLASH_EQUAL + 1 == MP_TOKEN_DEL_SLASH_EQUAL + && MP_TOKEN_DEL_SLASH_EQUAL + 1 == MP_TOKEN_DEL_PERCENT_EQUAL + && MP_TOKEN_DEL_PERCENT_EQUAL + 1 == MP_TOKEN_DEL_DBL_STAR_EQUAL + && MP_TOKEN_DEL_DBL_STAR_EQUAL + 1 == MP_TOKEN_DEL_PAREN_OPEN + && MP_TOKEN_DEL_PAREN_OPEN + 1 == MP_TOKEN_DEL_PAREN_CLOSE + && MP_TOKEN_DEL_PAREN_CLOSE + 1 == MP_TOKEN_DEL_BRACKET_OPEN + && MP_TOKEN_DEL_BRACKET_OPEN + 1 == MP_TOKEN_DEL_BRACKET_CLOSE + && MP_TOKEN_DEL_BRACKET_CLOSE + 1 == MP_TOKEN_DEL_BRACE_OPEN + && MP_TOKEN_DEL_BRACE_OPEN + 1 == MP_TOKEN_DEL_BRACE_CLOSE + && MP_TOKEN_DEL_BRACE_CLOSE + 1 == MP_TOKEN_DEL_COMMA + && MP_TOKEN_DEL_COMMA + 1 == MP_TOKEN_DEL_COLON + && MP_TOKEN_DEL_COLON + 1 == MP_TOKEN_DEL_PERIOD + && MP_TOKEN_DEL_PERIOD + 1 == MP_TOKEN_DEL_SEMICOLON + && MP_TOKEN_DEL_SEMICOLON + 1 == MP_TOKEN_DEL_EQUAL + && MP_TOKEN_DEL_EQUAL + 1 == MP_TOKEN_DEL_MINUS_MORE, + "MP_TOKEN order changed, so Code::PythonTextArea::TokenColor might need to change too."); + + if ((tokenKind >= MP_TOKEN_OP_TILDE && tokenKind <= MP_TOKEN_DEL_DBL_STAR_EQUAL) + || tokenKind == MP_TOKEN_DEL_EQUAL + || tokenKind == MP_TOKEN_DEL_MINUS_MORE) + { return OperatorColor; } return Palette::CodeText; @@ -52,6 +136,76 @@ static inline size_t TokenLength(mp_lexer_t * lex, const char * tokenPosition) { return lex->column - lex->tok_column; } +PythonTextArea::AutocompletionType PythonTextArea::autocompletionType(const char * autocompletionLocation, const char ** autocompletionLocationBeginning, const char ** autocompletionLocationEnd) const { + const char * location = autocompletionLocation != nullptr ? autocompletionLocation : cursorLocation(); + const char * beginningOfToken = nullptr; + + /* If there is already autocompleting, the cursor must be at the end of an + * identifier. Trying to compute autocompletionType will fail: because of the + * autocompletion text, the cursor seems to be in the middle of an identifier. */ + AutocompletionType autocompleteType = isAutocompleting() ? AutocompletionType::EndOfIdentifier : AutocompletionType::NoIdentifier; + if (autocompletionLocationBeginning == nullptr && autocompletionLocationEnd == nullptr) { + return autocompleteType; + } + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + const char * firstNonSpace = UTF8Helper::BeginningOfWord(m_contentView.editedText(), location); + mp_lexer_t * lex = mp_lexer_new_from_str_len(0, firstNonSpace, UTF8Helper::EndOfWord(location) - firstNonSpace, 0); + + const char * tokenStart; + const char * tokenEnd; + _mp_token_kind_t currentTokenKind = lex->tok_kind; + + while (currentTokenKind != MP_TOKEN_NEWLINE && currentTokenKind != MP_TOKEN_END) { + tokenStart = firstNonSpace + lex->tok_column - 1; + tokenEnd = tokenStart + TokenLength(lex, tokenStart); + + if (location < tokenStart) { + // The location for autocompletion is not in an identifier + assert(autocompleteType == AutocompletionType::NoIdentifier); + break; + } + if (location <= tokenEnd) { + if (currentTokenKind == MP_TOKEN_NAME + || (currentTokenKind >= MP_TOKEN_KW_FALSE + && currentTokenKind <= MP_TOKEN_KW_YIELD)) + { + /* The location for autocompletion is in the middle or at the end of + * an identifier. */ + beginningOfToken = tokenStart; + /* If autocompleteType is already EndOfIdentifier, we are + * autocompleting, so we do not need to update autocompleteType. If we + * recomputed autocompleteType now, we might wrongly think that it is + * MiddleOfIdentifier because of the autocompetion text. + * Example : fin|ally -> the lexer is at the end of "fin", but because + * we are autocompleting with "ally", the lexer thinks the cursor is + * in the middle of an identifier. */ + if (autocompleteType != AutocompletionType::EndOfIdentifier) { + autocompleteType = location < tokenEnd ? AutocompletionType::MiddleOfIdentifier : AutocompletionType::EndOfIdentifier; + } + } + break; + } + mp_lexer_to_next(lex); + currentTokenKind = lex->tok_kind; + } + mp_lexer_free(lex); + nlr_pop(); + } + if (autocompletionLocationBeginning != nullptr) { + *autocompletionLocationBeginning = beginningOfToken; + } + if (autocompletionLocationEnd != nullptr) { + *autocompletionLocationEnd = location; + } + assert(!isAutocompleting() || autocompleteType == AutocompletionType::EndOfIdentifier); + return autocompleteType; +} + +const char * PythonTextArea::ContentView::textToAutocomplete() const { + return UTF8Helper::BeginningOfWord(editedText(), cursorLocation()); +} + void PythonTextArea::ContentView::loadSyntaxHighlighter() { m_pythonDelegate->initPythonWithUser(this); } @@ -75,23 +229,7 @@ void PythonTextArea::ContentView::clearRect(KDContext * ctx, KDRect rect) const void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char * text, size_t byteLength, int fromColumn, int toColumn, const char * selectionStart, const char * selectionEnd) const { LOG_DRAW("Drawing \"%.*s\"\n", byteLength, text); - if (!m_pythonDelegate->isPythonUser(this)) { - const char * lineStart = UTF8Helper::CodePointAtGlyphOffset(text, fromColumn); - const char * lineEnd = UTF8Helper::CodePointAtGlyphOffset(text, toColumn); - drawStringAt( - ctx, - line, - fromColumn, - lineStart, - std::min(text + byteLength, lineEnd) - lineStart, - StringColor, - BackgroundColor, - selectionStart, - selectionEnd, - HighlightColor - ); - return; - } + assert(m_pythonDelegate->isPythonUser(this)); /* We're using the MicroPython lexer to do syntax highlighting on a per-line * basis. This can work, however the MicroPython lexer won't accept a line @@ -117,6 +255,8 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char return; } + const char * autocompleteStart = m_autocomplete ? m_cursorLocation : nullptr; + nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_lexer_t * lex = mp_lexer_new_from_str_len(0, firstNonSpace, byteLength - (firstNonSpace - text), 0); @@ -143,12 +283,16 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char } tokenLength = TokenLength(lex, tokenFrom); tokenEnd = tokenFrom + tokenLength; + + // If the token is being autocompleted, use DefaultColor + KDColor color = (tokenFrom <= autocompleteStart && autocompleteStart < tokenEnd) ? Palette::CodeText : TokenColor(lex->tok_kind); + LOG_DRAW("Draw \"%.*s\" for token %d\n", tokenLength, tokenFrom, lex->tok_kind); drawStringAt(ctx, line, UTF8Helper::GlyphOffsetAtCodePoint(text, tokenFrom), tokenFrom, tokenLength, - TokenColor(lex->tok_kind), + color, BackgroundColor, selectionStart, selectionEnd, @@ -160,6 +304,7 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char tokenFrom += tokenLength; + // Even if the token is being autocompleted, use CommentColor if (tokenFrom < text + byteLength) { LOG_DRAW("Draw comment \"%.*s\" from %d\n", byteLength - (tokenFrom - text), firstNonSpace, tokenFrom); drawStringAt(ctx, line, @@ -176,6 +321,22 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char mp_lexer_free(lex); nlr_pop(); } + + // Redraw the autocompleted word in the right color + if (m_autocomplete && autocompleteStart >= text && autocompleteStart < text + byteLength) { + assert(m_autocompletionEnd != nullptr && m_autocompletionEnd > autocompleteStart); + drawStringAt( + ctx, + line, + UTF8Helper::GlyphOffsetAtCodePoint(text, autocompleteStart), + autocompleteStart, + std::min(text + byteLength, m_autocompletionEnd) - autocompleteStart, + AutocompleteColor, + BackgroundColor, + nullptr, + nullptr, + HighlightColor); + } } KDRect PythonTextArea::ContentView::dirtyRectFromPosition(const char * position, bool includeFollowingLines) const { @@ -193,4 +354,158 @@ KDRect PythonTextArea::ContentView::dirtyRectFromPosition(const char * position, ); } +bool PythonTextArea::handleEvent(Ion::Events::Event event) { + if (m_contentView.isAutocompleting()) { + // Handle event with autocompletion + if (event == Ion::Events::Right + || event == Ion::Events::ShiftRight + || event == Ion::Events::OK) + { + m_contentView.reloadRectFromPosition(m_contentView.cursorLocation(), false); + acceptAutocompletion(event != Ion::Events::ShiftRight); + if (event != Ion::Events::ShiftRight) { + // Do not process the event more + scrollToCursor(); + return true; + } + } else if (event == Ion::Events::Toolbox + || event == Ion::Events::Var + || event == Ion::Events::Shift + || event == Ion::Events::Alpha + || event == Ion::Events::OnOff) + { + } else if(event == Ion::Events::Up + || event == Ion::Events::Down) + { + cycleAutocompletion(event == Ion::Events::Down); + return true; + } else { + removeAutocompletion(); + m_contentView.reloadRectFromPosition(m_contentView.cursorLocation(), false); + if (event == Ion::Events::Back) { + // Do not process the event more + return true; + } + } + } + bool result = TextArea::handleEvent(event); + if (event == Ion::Events::Backspace && !m_contentView.isAutocompleting() && selectionIsEmpty()) { + /* We want to add autocompletion when we are editing a word (after adding or + * deleting text). So if nothing is selected, we add the autocompletion if + * the event is backspace, as autocompletion has already been added if the + * event added text, in handleEventWithText. */ + addAutocompletion(); + } + return result; +} + +bool PythonTextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) { + if (*text == 0) { + return false; + } + if (m_contentView.isAutocompleting()) { + removeAutocompletion(); + } + bool result = TextArea::handleEventWithText(text, indentation, forceCursorRightOfText); + addAutocompletion(); + return result; +} + +void PythonTextArea::removeAutocompletion() { + assert(m_contentView.isAutocompleting()); + removeAutocompletionText(); + m_contentView.setAutocompleting(false); +} + +void PythonTextArea::removeAutocompletionText() { + assert(m_contentView.isAutocompleting()); + assert(m_contentView.autocompletionEnd() != nullptr); + const char * autocompleteStart = m_contentView.cursorLocation(); + const char * autocompleteEnd = m_contentView.autocompletionEnd(); + assert(autocompleteEnd != nullptr && autocompleteEnd > autocompleteStart); + m_contentView.removeText(autocompleteStart, autocompleteEnd); +} + +void PythonTextArea::addAutocompletion() { + assert(!m_contentView.isAutocompleting()); + const char * autocompletionTokenBeginning = nullptr; + const char * autocompletionLocation = const_cast(cursorLocation()); + m_autocompletionResultIndex = 0; + if (autocompletionType(autocompletionLocation, &autocompletionTokenBeginning) != AutocompletionType::EndOfIdentifier) { + // The cursor is not at the end of an identifier. + return; + } + + // First load variables and functions that complete the textToAutocomplete + const int scriptIndex = m_contentView.pythonDelegate()->menuController()->editedScriptIndex(); + m_contentView.pythonDelegate()->variableBoxController()->loadFunctionsAndVariables(scriptIndex, autocompletionTokenBeginning, autocompletionLocation - autocompletionTokenBeginning); + + if (addAutocompletionTextAtIndex(0)) { + m_contentView.setAutocompleting(true); + } +} + +bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIndexToUpdate) { + // The variable box should be loaded at this point + const char * autocompletionTokenBeginning = nullptr; + const char * autocompletionLocation = const_cast(cursorLocation()); + AutocompletionType type = autocompletionType(autocompletionLocation, &autocompletionTokenBeginning); // Done to get autocompletionTokenBeginning + assert(type == AutocompletionType::EndOfIdentifier); + (void)type; // Silence warnings + VariableBoxController * varBox = m_contentView.pythonDelegate()->variableBoxController(); + int textToInsertLength = 0; + bool addParentheses = false; + const char * textToInsert = varBox->autocompletionAlternativeAtIndex(autocompletionLocation - autocompletionTokenBeginning, &textToInsertLength, &addParentheses, nextIndex, currentIndexToUpdate); + + if (textToInsert == nullptr) { + return false; + } + + if (textToInsertLength > 0) { + // Try to insert the text (this might fail if the buffer is full) + if (!m_contentView.insertTextAtLocation(textToInsert, const_cast(autocompletionLocation), textToInsertLength)) { + return false; + } + autocompletionLocation += textToInsertLength; + m_contentView.setAutocompletionEnd(autocompletionLocation); + } + + // Try to insert the parentheses if needed + const char * parentheses = ScriptNodeCell::k_parentheses; + constexpr int parenthesesLength = 2; + assert(strlen(parentheses) == parenthesesLength); + /* If couldInsertText is false, we should not try to add the parentheses as + * there was already not enough space to add the autocompletion. */ + if (addParentheses && m_contentView.insertTextAtLocation(parentheses, const_cast(autocompletionLocation), parenthesesLength)) { + m_contentView.setAutocompleting(true); + m_contentView.setAutocompletionEnd(autocompletionLocation + parenthesesLength); + } + return true; +} + +void PythonTextArea::cycleAutocompletion(bool downwards) { + assert(m_contentView.isAutocompleting()); + removeAutocompletionText(); + addAutocompletionTextAtIndex(m_autocompletionResultIndex + (downwards ? 1 : -1), &m_autocompletionResultIndex); +} + +void PythonTextArea::acceptAutocompletion(bool moveCursorToEndOfAutocompletion) { + assert(m_contentView.isAutocompleting()); + + // Save the cursor location + const char * previousCursorLocation = cursorLocation(); + + removeAutocompletion(); + + m_contentView.pythonDelegate()->variableBoxController()->setSender(this); + m_contentView.pythonDelegate()->variableBoxController()->insertAutocompletionResultAtIndex(m_autocompletionResultIndex); + + // insertAutocompletionResultAtIndex already added the autocompletion + + // If we did not want to move the cursor, restore its position. + if (!moveCursorToEndOfAutocompletion) { + setCursorLocation(previousCursorLocation); + } +} + } diff --git a/apps/code/python_text_area.h b/apps/code/python_text_area.h index 149d6bfd0..c1a64db5f 100644 --- a/apps/code/python_text_area.h +++ b/apps/code/python_text_area.h @@ -9,21 +9,46 @@ class App; class PythonTextArea : public TextArea { public: + enum class AutocompletionType : uint8_t { + EndOfIdentifier, + MiddleOfIdentifier, + NoIdentifier + }; PythonTextArea(Responder * parentResponder, App * pythonDelegate, const KDFont * font) : TextArea(parentResponder, &m_contentView, font), - m_contentView(pythonDelegate, font) + m_contentView(pythonDelegate, font), + m_autocompletionResultIndex(0) { } void loadSyntaxHighlighter() { m_contentView.loadSyntaxHighlighter(); } void unloadSyntaxHighlighter() { m_contentView.unloadSyntaxHighlighter(); } + bool handleEvent(Ion::Events::Event event) override; + bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; + /* autocompletionType returns: + * - EndOfIdentifier if there is currently autocompletion, or if the cursor is + * at the end of an identifier, + * - MiddleOfIdentifier is the cursor is in the middle of an identifier, + * - No identifier otherwise. + * The autocompletionLocation can be provided with autocompletionLocation, or + * retreived with autocompletionLocationBeginning and autocompletionLocationEnd. */ + AutocompletionType autocompletionType(const char * autocompletionLocation = nullptr, const char ** autocompletionLocationBeginning = nullptr, const char ** autocompletionLocationEnd = nullptr) const; + bool isAutocompleting() const { return m_contentView.isAutocompleting(); } protected: class ContentView : public TextArea::ContentView { public: ContentView(App * pythonDelegate, const KDFont * font) : TextArea::ContentView(font), - m_pythonDelegate(pythonDelegate) + m_pythonDelegate(pythonDelegate), + m_autocomplete(false), + m_autocompletionEnd(nullptr) { } + App * pythonDelegate() { return m_pythonDelegate; } + void setAutocompleting(bool autocomplete) { m_autocomplete = autocomplete; } + bool isAutocompleting() const { return m_autocomplete; } + const char * autocompletionEnd() const { assert(m_autocomplete); return m_autocompletionEnd; } + void setAutocompletionEnd(const char * end) { m_autocompletionEnd = end; } + const char * textToAutocomplete() const; void loadSyntaxHighlighter(); void unloadSyntaxHighlighter(); void clearRect(KDContext * ctx, KDRect rect) const override; @@ -31,10 +56,19 @@ protected: KDRect dirtyRectFromPosition(const char * position, bool includeFollowingLines) const override; private: App * m_pythonDelegate; + bool m_autocomplete; + const char * m_autocompletionEnd; }; private: + void removeAutocompletion(); + void removeAutocompletionText(); // Just removes the suggested text, not the autocompletion mode + void addAutocompletion(); + bool addAutocompletionTextAtIndex(int nextIndex, int * currentIndexToUpdate = nullptr); // Assumes the var box is already loaded + void cycleAutocompletion(bool downwards); + void acceptAutocompletion(bool moveCursorToEndOfAutocompletion); const ContentView * nonEditableContentView() const override { return &m_contentView; } ContentView m_contentView; + int m_autocompletionResultIndex; }; } diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 9c30d04d6..bcd5ec925 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -120,7 +120,18 @@ const ToolboxMessageTree MatplotlibPyplotModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPlot, I18n::Message::PythonPlot), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandScatter, I18n::Message::PythonScatter), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandShow, I18n::Message::PythonShow), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandText, I18n::Message::PythonText) + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandText, I18n::Message::PythonText), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorBlue, I18n::Message::PythonColorBlue, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorRed, I18n::Message::PythonColorRed, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGreen, I18n::Message::PythonColorGreen, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorYellow, I18n::Message::PythonColorYellow, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorBrown, I18n::Message::PythonColorBrown, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorBlack, I18n::Message::PythonColorBlack, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorWhite, I18n::Message::PythonColorWhite, false), + 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) }; const ToolboxMessageTree TurtleModuleChildren[] = { @@ -141,22 +152,23 @@ const ToolboxMessageTree TurtleModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandPenup, I18n::Message::PythonTurtlePenup, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandPensize, I18n::Message::PythonTurtlePensize), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandIsdown, I18n::Message::PythonTurtleIsdown, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandWrite, I18n::Message::PythonTurtleWrite), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandReset, I18n::Message::PythonTurtleReset, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandShowturtle, I18n::Message::PythonTurtleShowturtle, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandHideturtle, I18n::Message::PythonTurtleHideturtle, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandColor, I18n::Message::PythonTurtleColor, false, I18n::Message::PythonTurtleCommandColorWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandColor, I18n::Message::PythonTurtleColor), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandColorMode, I18n::Message::PythonTurtleColorMode), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandBlue, I18n::Message::PythonTurtleBlue, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandRed, I18n::Message::PythonTurtleRed, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandGreen, I18n::Message::PythonTurtleGreen, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandYellow, I18n::Message::PythonTurtleYellow, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandBrown, I18n::Message::PythonTurtleBrown, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandBlack, I18n::Message::PythonTurtleBlack, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandWhite, I18n::Message::PythonTurtleWhite, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandPink, I18n::Message::PythonTurtlePink, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandOrange, I18n::Message::PythonTurtleOrange, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandPurple, I18n::Message::PythonTurtlePurple, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandGrey, I18n::Message::PythonTurtleGrey, false) + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorBlue, I18n::Message::PythonColorBlue, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorRed, I18n::Message::PythonColorRed, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorGreen, I18n::Message::PythonColorGreen, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorYellow, I18n::Message::PythonColorYellow, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorBrown, I18n::Message::PythonColorBrown, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorBlack, I18n::Message::PythonColorBlack, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorWhite, I18n::Message::PythonColorWhite, false), + 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) }; const ToolboxMessageTree RandomModuleChildren[] = { @@ -278,9 +290,9 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandBackward, I18n::Message::PythonTurtleBackward), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBar, I18n::Message::PythonBar), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandBin, I18n::Message::PythonBin), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandBlack, I18n::Message::PythonTurtleBlack, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandBlue, I18n::Message::PythonTurtleBlue, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandBrown, I18n::Message::PythonTurtleBrown, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorBlack, I18n::Message::PythonColorBlack, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorBlue, I18n::Message::PythonColorBlue, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorBrown, I18n::Message::PythonColorBrown, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCeil, I18n::Message::PythonCeil), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandChoice, I18n::Message::PythonChoice), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandCircle, I18n::Message::PythonTurtleCircle), @@ -319,8 +331,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::PythonTurtleCommandGreen, I18n::Message::PythonTurtleGreen, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandGrey, I18n::Message::PythonTurtleGrey, 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), @@ -366,19 +378,19 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandModf, I18n::Message::PythonModf), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandMonotonic, I18n::Message::PythonMonotonic, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandOct, I18n::Message::PythonOct), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandOrange, I18n::Message::PythonTurtleOrange, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorOrange, I18n::Message::PythonColorOrange, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandPendown, I18n::Message::PythonTurtlePendown, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandPenup, I18n::Message::PythonTurtlePenup, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandPensize, I18n::Message::PythonTurtlePensize), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPhase, I18n::Message::PythonPhase), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandConstantPi, I18n::Message::PythonConstantPi, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandPink, I18n::Message::PythonTurtlePink, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPink, I18n::Message::PythonColorPink, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPolar, I18n::Message::PythonPolar), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandPosition, I18n::Message::PythonTurtlePosition, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPower, I18n::Message::PythonPower), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPlot, I18n::Message::PythonPlot), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPrint, I18n::Message::PythonPrint), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandPurple, I18n::Message::PythonTurtlePurple, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorPurple, I18n::Message::PythonColorPurple, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRadians, I18n::Message::PythonRadians), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRandint, I18n::Message::PythonRandint), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRandom, I18n::Message::PythonRandom, false), @@ -387,7 +399,7 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRangeStartStop, I18n::Message::PythonRangeStartStop), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRangeStop, I18n::Message::PythonRangeStop), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRect, I18n::Message::PythonRect), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandRed, I18n::Message::PythonTurtleRed, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorRed, I18n::Message::PythonColorRed, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandReset, I18n::Message::PythonTurtleReset, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandRight, I18n::Message::PythonTurtleRight), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRound, I18n::Message::PythonRound), @@ -411,8 +423,9 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandTrunc, I18n::Message::PythonTrunc), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandTurtleFunction, I18n::Message::PythonTurtleFunction, false, I18n::Message::PythonCommandTurtleFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandUniform, I18n::Message::PythonUniform), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandWhite, I18n::Message::PythonTurtleWhite, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandYellow, I18n::Message::PythonTurtleYellow, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorWhite, I18n::Message::PythonColorWhite, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandWrite, I18n::Message::PythonTurtleWrite), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColorYellow, I18n::Message::PythonColorYellow, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImag, I18n::Message::PythonImag, false, I18n::Message::PythonCommandImagWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandReal, I18n::Message::PythonReal, false, I18n::Message::PythonCommandRealWithoutArg) }; @@ -465,6 +478,20 @@ PythonToolbox::PythonToolbox() : { } +const ToolboxMessageTree * PythonToolbox::moduleChildren(const char * name, int * numberOfNodes) const { + for (ToolboxMessageTree t : modulesChildren) { + if (strcmp(I18n::translate(t.label()), name) == 0) { + const int childrenCount = t.numberOfChildren(); + if (numberOfNodes != nullptr) { + *numberOfNodes = childrenCount; + } + assert(childrenCount > 0); + return static_cast(t.childAtIndex(0)); + } + } + return nullptr; +} + bool PythonToolbox::handleEvent(Ion::Events::Event event) { if (Toolbox::handleEvent(event)) { return true; @@ -490,7 +517,7 @@ KDCoordinate PythonToolbox::rowHeight(int j) { * We thus decided to compute the real height only for the ifStatement * children of the toolbox, which is the only menu that has special height * rows. */ - const ToolboxMessageTree * messageTree = static_cast(m_messageTreeModel->children(j)); + const ToolboxMessageTree * messageTree = static_cast(m_messageTreeModel->childAtIndex(j)); return k_font->stringSize(I18n::translate(messageTree->label())).height() + 2*Metric::TableCellVerticalMargin + (messageTree->text() == I18n::Message::Default ? 0 : Toolbox::rowHeight(j)); } return Toolbox::rowHeight(j); @@ -498,7 +525,7 @@ KDCoordinate PythonToolbox::rowHeight(int j) { bool PythonToolbox::selectLeaf(int selectedRow) { m_selectableTableView.deselectTable(); - ToolboxMessageTree * node = (ToolboxMessageTree *)m_messageTreeModel->children(selectedRow); + ToolboxMessageTree * node = (ToolboxMessageTree *)m_messageTreeModel->childAtIndex(selectedRow); const char * editedText = I18n::translate(node->insertedText()); // strippedEditedText array needs to be in the same scope as editedText char strippedEditedText[k_maxMessageSize]; @@ -539,7 +566,7 @@ void PythonToolbox::scrollToLetter(char letter) { char lowerLetter = tolower(letter); int index = -1; for (int i = 0; i < m_messageTreeModel->numberOfChildren(); i++) { - char l = tolower(I18n::translate(m_messageTreeModel->children(i)->label())[0]); + char l = tolower(I18n::translate(m_messageTreeModel->childAtIndex(i)->label())[0]); if (l == lowerLetter) { index = i; break; diff --git a/apps/code/python_toolbox.h b/apps/code/python_toolbox.h index d3e9d7f21..bfe682058 100644 --- a/apps/code/python_toolbox.h +++ b/apps/code/python_toolbox.h @@ -10,7 +10,11 @@ namespace Code { class PythonToolbox : public Toolbox { public: + // PythonToolbox PythonToolbox(); + const ToolboxMessageTree * moduleChildren(const char * name, int * numberOfNodes) const; + + // Toolbox bool handleEvent(Ion::Events::Event event) override; const ToolboxMessageTree * rootModel() const override; protected: diff --git a/apps/code/script.cpp b/apps/code/script.cpp index 1b33fd0e7..4b39b3452 100644 --- a/apps/code/script.cpp +++ b/apps/code/script.cpp @@ -65,22 +65,54 @@ bool Script::nameCompliant(const char * name) { return false; } -bool Script::importationStatus() const { - assert(!isNull()); - Data d = value(); - return (((char *)d.buffer)[0] == 1); +uint8_t * StatusFromData(Script::Data d) { + return const_cast(static_cast(d.buffer)); } -void Script::toggleImportationStatus() { +bool Script::autoImportationStatus() const { + return getStatutBit(k_autoImportationStatusMask); +} + +void Script::toggleAutoimportationStatus() { + assert(!isNull()); Data d = value(); - ((char *)d.buffer)[0] = (((char *)d.buffer)[0] == 1 ? 0 : 1); + *StatusFromData(d) ^= k_autoImportationStatusMask; setValue(d); } -const char * Script::scriptContent() const { +const char * Script::content() const { + Data d = value(); + return ((const char *)d.buffer) + StatusSize(); +} + +bool Script::fetchedFromConsole() const { + return getStatutBit(k_fetchedFromConsoleMask); +} + +void Script::setFetchedFromConsole(bool fetched) { + setStatutBit(k_fetchedFromConsoleMask, k_fetchedFromConsoleOffset, fetched); +} + +bool Script::fetchedForVariableBox() const { + return getStatutBit(k_fetchedForVariableBoxMask); +} + +void Script::setFetchedForVariableBox(bool fetched) { + setStatutBit(k_fetchedForVariableBoxMask, k_fetchedForVariableBoxOffset, fetched); +} + +bool Script::getStatutBit(uint8_t mask) const { assert(!isNull()); Data d = value(); - return (const char *)d.buffer + k_importationStatusSize; + return ((*StatusFromData(d)) & mask) != 0; +} + +void Script::setStatutBit(uint8_t mask, uint8_t offset, bool statusBit) { + assert(!isNull()); + Data d = value(); + uint8_t * status = StatusFromData(d); + *status = ((*status) & ~mask) | (static_cast(statusBit) << offset); //TODO Create and use a bit operations library + setValue(d); } } diff --git a/apps/code/script.h b/apps/code/script.h index fc10352f3..9d2df78c6 100644 --- a/apps/code/script.h +++ b/apps/code/script.h @@ -5,16 +5,36 @@ namespace Code { -/* Record : | Total Size | Name | Body | - * Script: | AutoImportationStatus | Content |*/ +/* Record: | Size | Name | Body | + * Script: | | | Status | Content | + * + * + * |FetchedForVariableBoxBit + * Status is one byte long: xxxxxxxx + * ^ ^ + * FetchedFromConsoleBit AutoImportationBit + * + * AutoImportationBit is 1 if the script should be auto imported when the + * console opens. + * + * FetchedFromConsoleBit is 1 if its content has been fetched from the console, + * so we can retrieve the correct variables afterwards in the variable box. + * + * FetchedForVariableBoxBit is used to prevent circular importation problems, + * such as scriptA importing scriptB, which imports scriptA. Once we get the + * variables from a script to put them in the variable box, we switch the bit to + * 1 and won't reload it afterwards. */ class Script : public Ion::Storage::Record { private: // Default script names are chosen between script1 and script99 static constexpr int k_maxNumberOfDefaultScriptNames = 99; static constexpr int k_defaultScriptNameNumberMaxSize = 2; // Numbers from 1 to 99 have 2 digits max + + // See the comment at the beginning of the file + static constexpr size_t k_statusSize = 1; + public: - static constexpr size_t k_importationStatusSize = 1; static constexpr int k_defaultScriptNameMaxSize = 6 + k_defaultScriptNameNumberMaxSize + 1; /* 6 = strlen("script") * k_defaultScriptNameNumberMaxSize = maxLength of integers between 1 and 99 @@ -22,11 +42,29 @@ public: static bool DefaultName(char buffer[], size_t bufferSize); static bool nameCompliant(const char * name); + static constexpr size_t StatusSize() { return k_statusSize; } - Script(Ion::Storage::Record r) : Record(r) {} - bool importationStatus() const; - void toggleImportationStatus(); - const char * scriptContent() const; + + Script(Ion::Storage::Record r = Ion::Storage::Record()) : Record(r) {} + bool autoImportationStatus() const; + void toggleAutoimportationStatus(); + const char * content() const; + + /* Fetched status */ + bool fetchedFromConsole() const; + void setFetchedFromConsole(bool fetched); + bool fetchedForVariableBox() const; + void setFetchedForVariableBox(bool fetched); + +private: + static constexpr uint8_t k_autoImportationStatusMask = 0b1; + static constexpr uint8_t k_fetchedForVariableBoxOffset = 7; + static constexpr uint8_t k_fetchedFromConsoleOffset = 6; + static constexpr uint8_t k_fetchedForVariableBoxMask = 0b1 << k_fetchedForVariableBoxOffset; + static constexpr uint8_t k_fetchedFromConsoleMask = 0b1 << k_fetchedFromConsoleOffset; + + bool getStatutBit(uint8_t offset) const; + void setStatutBit(uint8_t mask, uint8_t offset, bool value); }; } diff --git a/apps/code/script_node.h b/apps/code/script_node.h index 1bac0f81d..6f2bd49f0 100644 --- a/apps/code/script_node.h +++ b/apps/code/script_node.h @@ -1,33 +1,35 @@ #ifndef CODE_SCRIPT_NODE_H #define CODE_SCRIPT_NODE_H +#include #include namespace Code { class ScriptNode { public: - enum class Type { - Function = 0, - Variable = 1 + enum class Type : bool { + WithoutParentheses, + WithParentheses }; - ScriptNode() : - m_type(Type::Function), m_name(nullptr), m_scriptIndex(0) {} - static ScriptNode FunctionNode(const char * name, uint16_t scriptIndex) { - return ScriptNode(Type::Function, name, scriptIndex); - } - static ScriptNode VariableNode(const char * name, uint16_t scriptIndex) { - return ScriptNode(Type::Variable, name, scriptIndex); - } + ScriptNode(Type type = Type::WithoutParentheses, const char * name = nullptr, int nameLength = -1, const char * nodeSourceName = nullptr, const char * description = nullptr) : + m_type(type), + m_name(name), + m_nodeSourceName(nodeSourceName), + m_description(description), + m_nameLength(nameLength) + {} Type type() const { return m_type; } const char * name() const { return m_name; } - uint16_t scriptIndex() const { return m_scriptIndex; } + int nameLength() const { return static_cast(m_nameLength); } + const char * nodeSourceName() const { return m_nodeSourceName; } + const char * description() const { return m_description; } private: - ScriptNode(Type type, const char * name, uint16_t scriptIndex) : - m_type(type), m_name(name), m_scriptIndex(scriptIndex) {} Type m_type; const char * m_name; - uint16_t m_scriptIndex; + const char * m_nodeSourceName; + const char * m_description; + size_t m_nameLength; }; } diff --git a/apps/code/script_node_cell.cpp b/apps/code/script_node_cell.cpp index a3e265bf2..1c8580a44 100644 --- a/apps/code/script_node_cell.cpp +++ b/apps/code/script_node_cell.cpp @@ -7,47 +7,51 @@ namespace Code { constexpr char ScriptNodeCell::k_parentheses[]; constexpr char ScriptNodeCell::k_parenthesesWithEmpty[]; -ScriptNodeCell::ScriptNodeView::ScriptNodeView() : - HighlightCell(), - m_scriptNode(nullptr), - m_scriptStore(nullptr) -{ -} - -void ScriptNodeCell::ScriptNodeView::setScriptNode(ScriptNode * scriptNode) { - m_scriptNode = scriptNode; -} - -void ScriptNodeCell::ScriptNodeView::setScriptStore(ScriptStore * scriptStore) { - m_scriptStore = scriptStore; -} - void ScriptNodeCell::ScriptNodeView::drawRect(KDContext * ctx, KDRect rect) const { - ctx->drawString(m_scriptNode->name(), KDPoint(0, Metric::TableCellVerticalMargin), k_font, Palette::CodeText, isHighlighted()? Palette::CodeBackgroundSelected : Palette::CodeBackground); - KDSize nameSize = k_font->stringSize(m_scriptNode->name()); - if (m_scriptNode->type() == ScriptNode::Type::Function) { - ctx->drawString(ScriptNodeCell::k_parentheses, KDPoint(nameSize.width(), Metric::TableCellVerticalMargin), k_font, Palette::CodeText, isHighlighted()? Palette::CodeBackgroundSelected : Palette::CodeBackground); + const KDColor backgroundColor = isHighlighted()? Palette::CodeBackgroundSelected : Palette::CodeBackground; + + // 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); + } + + // Draw the node name + const char * nodeName = m_scriptNode->name(); + const int nodeNameLength = m_scriptNode->nameLength(); + KDSize nameSize = k_font->stringSize(nodeName, nodeNameLength); + const KDCoordinate nodeNameY = k_topMargin; + ctx->drawString(nodeName, KDPoint(0, nodeNameY), k_font, KDColorBlack, backgroundColor, nodeNameLength); + // If it is needed, draw the parentheses + if (m_scriptNode->type() == ScriptNode::Type::WithParentheses) { + ctx->drawString(ScriptNodeCell::k_parentheses, KDPoint(nameSize.width(), nodeNameY), k_font, KDColorBlack, backgroundColor); + } + + /* If it exists, draw the source name. If it did not fit, we would have put + * nullptr at the node creation. */ + const char * sourceName = m_scriptNode->nodeSourceName(); + if (sourceName != nullptr) { + KDSize sourceNameSize = k_font->stringSize(sourceName); + ctx->drawString(sourceName, KDPoint(m_frame.width() - sourceNameSize.width(), nodeNameY), k_font, Palette::CodeText, backgroundColor); } - ctx->drawString(m_scriptStore->scriptAtIndex(m_scriptNode->scriptIndex()).fullName(), KDPoint(0, Metric::TableCellVerticalMargin + nameSize.height() + k_verticalMargin), k_font, Palette::SecondaryText, isHighlighted()? Palette::CodeBackgroundSelected : Palette::CodeBackground); } KDSize ScriptNodeCell::ScriptNodeView::minimalSizeForOptimalDisplay() const { if (m_scriptNode->name() == nullptr) { return KDSizeZero; } - KDSize size1 = k_font->stringSize(m_scriptNode->name()); - KDSize size2 = k_font->stringSize(m_scriptStore->scriptAtIndex(m_scriptNode->scriptIndex()).fullName()); - KDSize size3 = KDSizeZero; - if (m_scriptNode->type() == ScriptNode::Type::Function) { - size3 = k_font->stringSize(ScriptNodeCell::k_parentheses); - } - return KDSize(size1.width() + size3.width() > size2.width() ? size1.width() + size3.width() : size2.width(), Metric::TableCellVerticalMargin + size1.width() + k_verticalMargin + size2.width()); + return KDSize( + k_optimalWidth, + m_scriptNode->description() == nullptr ? k_simpleItemHeight : k_complexItemHeight); } -ScriptNodeCell::ScriptNodeCell() : - TableCell(), - m_scriptNodeView() -{ +bool ScriptNodeCell::CanDisplayNameAndSource(int nameLength, const char * source) { + if (source == nullptr) { + return true; + } + assert(nameLength > 0); + const KDFont * font = ScriptNodeView::k_font; + return font->glyphSize().width()*(nameLength + 1) + font->stringSize(source).width() <= ScriptNodeView::k_optimalWidth; // + 1 for the separating space } void ScriptNodeCell::setScriptNode(ScriptNode * scriptNode) { @@ -55,10 +59,6 @@ void ScriptNodeCell::setScriptNode(ScriptNode * scriptNode) { reloadCell(); } -void ScriptNodeCell::setScriptStore(ScriptStore * scriptStore) { - m_scriptNodeView.setScriptStore(scriptStore); -} - void ScriptNodeCell::setHighlighted(bool highlight) { TableCell::setHighlighted(highlight); m_scriptNodeView.setHighlighted(highlight); diff --git a/apps/code/script_node_cell.h b/apps/code/script_node_cell.h index c4d7651b7..672a9fb44 100644 --- a/apps/code/script_node_cell.h +++ b/apps/code/script_node_cell.h @@ -10,9 +10,18 @@ namespace Code { class ScriptNodeCell : public TableCell { public: - ScriptNodeCell(); + static_assert('\x11' == UCodePointEmpty, "Unicode error"); + constexpr static char k_parentheses[] = "()"; + constexpr static char k_parenthesesWithEmpty[] = "(\x11)"; + constexpr static KDCoordinate k_simpleItemHeight = 27; + constexpr static KDCoordinate k_complexItemHeight = 42; + + ScriptNodeCell() : + TableCell(), + m_scriptNodeView() + {} void setScriptNode(ScriptNode * node); - void setScriptStore(ScriptStore * scriptStore); + static bool CanDisplayNameAndSource(int nameLength, const char * source); /* TableCell */ View * labelView() const override { return const_cast(static_cast(&m_scriptNodeView)); } @@ -22,26 +31,25 @@ public: void reloadCell() override; const char * text() const override { return m_scriptNodeView.text(); } - static_assert('\x11' == UCodePointEmpty, "Unicode error"); - constexpr static char k_parentheses[] = "()"; - constexpr static char k_parenthesesWithEmpty[] = "(\x11)"; - protected: class ScriptNodeView : public HighlightCell { public: - ScriptNodeView(); - void setScriptNode(ScriptNode * scriptNode); - void setScriptStore(ScriptStore * scriptStore); + constexpr static const KDFont * k_font = KDFont::SmallFont; + constexpr static KDCoordinate k_optimalWidth = Ion::Display::Width - Metric::PopUpLeftMargin - Metric::PopUpRightMargin; + ScriptNodeView() : + HighlightCell(), + m_scriptNode(nullptr) + {} + void setScriptNode(ScriptNode * node) { m_scriptNode = node; } void drawRect(KDContext * ctx, KDRect rect) const override; virtual KDSize minimalSizeForOptimalDisplay() const override; const char * text() const override { - return m_scriptStore->scriptAtIndex(m_scriptNode->scriptIndex()).fullName(); + return m_scriptNode->description(); } private: - constexpr static const KDFont * k_font = KDFont::SmallFont; - constexpr static KDCoordinate k_verticalMargin = 7; + constexpr static KDCoordinate k_bottomMargin = 5; + constexpr static KDCoordinate k_topMargin = k_bottomMargin + k_separatorThickness; ScriptNode * m_scriptNode; - ScriptStore * m_scriptStore; }; ScriptNodeView m_scriptNodeView; }; diff --git a/apps/code/script_parameter_controller.cpp b/apps/code/script_parameter_controller.cpp index 6e99b63a7..1390fcd99 100644 --- a/apps/code/script_parameter_controller.cpp +++ b/apps/code/script_parameter_controller.cpp @@ -45,7 +45,7 @@ bool ScriptParameterController::handleEvent(Ion::Events::Event event) { m_menuController->renameSelectedScript(); return true; case 2: - m_script.toggleImportationStatus(); + m_script.toggleAutoimportationStatus(); m_selectableTableView.reloadData(); m_menuController->reloadConsole(); Container::activeApp()->setFirstResponder(&m_selectableTableView); @@ -95,7 +95,7 @@ HighlightCell * ScriptParameterController::reusableCell(int index) { void ScriptParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { if (cell == &m_autoImportScript) { SwitchView * switchView = (SwitchView *)m_autoImportScript.accessoryView(); - switchView->setState(m_script.importationStatus()); + switchView->setState(m_script.autoImportationStatus()); } else if (cell == &m_size) { MessageTableCellWithBuffer * myCell = (MessageTableCellWithBuffer *)cell; GetScriptSize(myCell); diff --git a/apps/code/script_store.cpp b/apps/code/script_store.cpp index 25b9dce67..ebc358827 100644 --- a/apps/code/script_store.cpp +++ b/apps/code/script_store.cpp @@ -1,23 +1,14 @@ #include "script_store.h" -#include "string.h" -#include - -extern "C" { -#include "py/lexer.h" -#include "py/nlr.h" -} namespace Code { constexpr char ScriptStore::k_scriptExtension[]; - bool ScriptStore::ScriptNameIsFree(const char * baseName) { - return Ion::Storage::sharedStorage()->recordBaseNamedWithExtension(baseName, k_scriptExtension).isNull(); + return ScriptBaseNamed(baseName).isNull(); } -ScriptStore::ScriptStore() -{ +ScriptStore::ScriptStore() { addScriptFromTemplate(ScriptTemplate::Squares()); addScriptFromTemplate(ScriptTemplate::Parabola()); addScriptFromTemplate(ScriptTemplate::Mandelbrot()); @@ -34,124 +25,39 @@ bool ScriptStore::isFull() { return Ion::Storage::sharedStorage()->availableSize() < k_fullFreeSpaceSizeLimit; } -void ScriptStore::scanScriptsForFunctionsAndVariables(void * context, ScanCallback storeFunction, ScanCallback storeVariable) { - for (int scriptIndex = 0; scriptIndex < numberOfScripts(); scriptIndex++) { - - //Don't scan not loaded script - if (!scriptAtIndex(scriptIndex).importationStatus()){ - continue; - } - - // Handle lexer or parser errors with nlr. - nlr_buf_t nlr; - if (nlr_push(&nlr) == 0) { - const char * scriptContent = scriptAtIndex(scriptIndex).scriptContent(); - if (scriptContent == nullptr) { - continue; - } - mp_lexer_t *lex = mp_lexer_new_from_str_len(0, scriptContent, strlen(scriptContent), false); - mp_parse_tree_t parseTree = mp_parse(lex, MP_PARSE_FILE_INPUT); - mp_parse_node_t pn = parseTree.root; - - if (!MP_PARSE_NODE_IS_STRUCT(pn)) { - mp_parse_tree_clear(&parseTree); - nlr_pop(); - continue; - } - - mp_parse_node_struct_t *pns = (mp_parse_node_struct_t*)pn; - - // The script is only a single function definition. - if (((uint)(MP_PARSE_NODE_STRUCT_KIND(pns))) == k_functionDefinitionParseNodeStructKind) { - const char * id = structID(pns); - if (id == nullptr) { - continue; - } - storeFunction(context, id, scriptIndex); - mp_parse_tree_clear(&parseTree); - nlr_pop(); - continue; - } - - // The script is only a single global variable definition. - if (((uint)(MP_PARSE_NODE_STRUCT_KIND(pns))) == k_expressionStatementParseNodeStructKind) { - const char * id = structID(pns); - if (id == nullptr) { - continue; - } - storeVariable(context, id, scriptIndex); - mp_parse_tree_clear(&parseTree); - nlr_pop(); - continue; - } - - if (((uint)(MP_PARSE_NODE_STRUCT_KIND(pns))) != k_fileInput2ParseNodeStructKind) { - // The script node is not of type "file_input_2", thus it will not have main - // structures of the wanted type. - mp_parse_tree_clear(&parseTree); - nlr_pop(); - continue; - } - - // Count the number of structs in child nodes. - - size_t n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); - for (size_t i = 0; i < n; i++) { - mp_parse_node_t child = pns->nodes[i]; - if (MP_PARSE_NODE_IS_STRUCT(child)) { - mp_parse_node_struct_t *child_pns = (mp_parse_node_struct_t*)(child); - if (((uint)(MP_PARSE_NODE_STRUCT_KIND(child_pns))) == k_functionDefinitionParseNodeStructKind) { - const char * id = structID(child_pns); - if (id == nullptr) { - continue; - } - storeFunction(context, id, scriptIndex); - } else if (((uint)(MP_PARSE_NODE_STRUCT_KIND(child_pns))) == k_expressionStatementParseNodeStructKind) { - const char * id = structID(child_pns); - if (id == nullptr) { - continue; - } - storeVariable(context, id, scriptIndex); - } - } - } - - mp_parse_tree_clear(&parseTree); - nlr_pop(); - } - } -} - -const char * ScriptStore::contentOfScript(const char * name) { - Script script = scriptNamed(name); +const char * ScriptStore::contentOfScript(const char * name, bool markAsFetched) { + Script script = ScriptNamed(name); if (script.isNull()) { return nullptr; } - return script.scriptContent(); + if (markAsFetched) { + script.setFetchedFromConsole(true); + } + return script.content(); +} + +void ScriptStore::clearVariableBoxFetchInformation() { + // TODO optimize fetches + const int scriptsCount = numberOfScripts(); + for (int i = 0; i < scriptsCount; i++) { + scriptAtIndex(i).setFetchedForVariableBox(false); + } +} + +void ScriptStore::clearConsoleFetchInformation() { + // TODO optimize fetches + const int scriptsCount = numberOfScripts(); + for (int i = 0; i < scriptsCount; i++) { + scriptAtIndex(i).setFetchedFromConsole(false); + } } Script::ErrorStatus ScriptStore::addScriptFromTemplate(const ScriptTemplate * scriptTemplate) { - size_t valueSize = strlen(scriptTemplate->content())+1+1;// scriptcontent size + 1 char for the importation status + size_t valueSize = Script::StatusSize() + strlen(scriptTemplate->content()) + 1; // (auto importation status + content fetched status) + scriptcontent size + null-terminating char assert(Script::nameCompliant(scriptTemplate->name())); Script::ErrorStatus err = Ion::Storage::sharedStorage()->createRecordWithFullName(scriptTemplate->name(), scriptTemplate->value(), valueSize); assert(err != Script::ErrorStatus::NonCompliantName); return err; } -const char * ScriptStore::structID(mp_parse_node_struct_t *structNode) { - // Find the id child node, which stores the struct's name - size_t childNodesCount = MP_PARSE_NODE_STRUCT_NUM_NODES(structNode); - if (childNodesCount < 1) { - return nullptr; - } - mp_parse_node_t child = structNode->nodes[0]; - if (MP_PARSE_NODE_IS_LEAF(child) - && MP_PARSE_NODE_LEAF_KIND(child) == MP_PARSE_NODE_ID) - { - uintptr_t arg = MP_PARSE_NODE_LEAF_ARG(child); - return qstr_str(arg); - } - return nullptr; -} - } diff --git a/apps/code/script_store.h b/apps/code/script_store.h index 46273c57d..ad9b59ff7 100644 --- a/apps/code/script_store.h +++ b/apps/code/script_store.h @@ -23,8 +23,11 @@ public: Script scriptAtIndex(int index) { return Script(Ion::Storage::sharedStorage()->recordWithExtensionAtIndex(k_scriptExtension, index)); } - Script scriptNamed(const char * name) { - return Script(Ion::Storage::sharedStorage()->recordNamed(name)); + static Script ScriptNamed(const char * fullName) { + return Script(Ion::Storage::sharedStorage()->recordNamed(fullName)); + } + static Script ScriptBaseNamed(const char * baseName) { + return Script(Ion::Storage::sharedStorage()->recordBaseNamedWithExtension(baseName, k_scriptExtension)); } int numberOfScripts() { return Ion::Storage::sharedStorage()->numberOfRecordsWithExtension(k_scriptExtension); @@ -35,12 +38,10 @@ public: void deleteAllScripts(); bool isFull(); - /* Provide scripts content information */ - typedef void (* ScanCallback)(void * context, const char * p, int n); - void scanScriptsForFunctionsAndVariables(void * context, ScanCallback storeFunction,ScanCallback storeVariable); - /* MicroPython::ScriptProvider */ - const char * contentOfScript(const char * name) override; + const char * contentOfScript(const char * name, bool markAsFetched) override; + void clearVariableBoxFetchInformation(); + void clearConsoleFetchInformation(); Ion::Storage::Record::ErrorStatus addScriptFromTemplate(const ScriptTemplate * scriptTemplate); private: @@ -51,10 +52,6 @@ private: * importation status (1 char), the default content "from math import *\n" * (20 char) and 10 char of free space. */ static constexpr int k_fullFreeSpaceSizeLimit = sizeof(Ion::Storage::record_size_t)+Script::k_defaultScriptNameMaxSize+k_scriptExtensionLength+1+20+10; - static constexpr size_t k_fileInput2ParseNodeStructKind = 1; - static constexpr size_t k_functionDefinitionParseNodeStructKind = 3; - static constexpr size_t k_expressionStatementParseNodeStructKind = 5; - const char * structID(mp_parse_node_struct_t *structNode); }; } diff --git a/apps/code/script_template.h b/apps/code/script_template.h index d0d048ab1..ec32e7052 100644 --- a/apps/code/script_template.h +++ b/apps/code/script_template.h @@ -1,6 +1,8 @@ #ifndef CODE_SCRIPT_TEMPLATE_H #define CODE_SCRIPT_TEMPLATE_H +#include "script.h" + namespace Code { class ScriptTemplate { @@ -12,11 +14,11 @@ public: static const ScriptTemplate * Polynomial(); static const ScriptTemplate * Parabola(); const char * name() const { return m_name; } - const char * content() const { return m_value+1; } + const char * content() const { return m_value + Script::StatusSize(); } const char * value() const { return m_value; } private: const char * m_name; - const char * m_value; // hold the 'importation status' flag concatenate with the script content + const char * m_value; // holds the 'importation status' and 'current importation status' flags concatenated with the script content }; } diff --git a/apps/code/test/variable_box_controller.cpp b/apps/code/test/variable_box_controller.cpp new file mode 100644 index 000000000..2c7e4abfc --- /dev/null +++ b/apps/code/test/variable_box_controller.cpp @@ -0,0 +1,70 @@ +#include +#include "../script_store.h" +#include "../variable_box_controller.h" +#include + +using namespace Code; + +void assert_variables_are(const char * script, const char * nameToComplete, const char * * expectedVariables, int expectedVariablesCount) { + // Clean the store + ScriptStore store; + store.deleteAllScripts(); + + // Add the script + store.addNewScript(); + constexpr int dataBufferSize = 500; + char dataBuffer[dataBufferSize]; + Ion::Storage::Record::Data data = { + .buffer = &dataBuffer, + .size = dataBufferSize + }; + strlcpy(dataBuffer, script, dataBufferSize); + constexpr int scriptIndex = 0; + store.scriptAtIndex(scriptIndex).setValue(data); + + // Load the variable box + VariableBoxController varBox(&store); + + const size_t nameToCompleteLength = strlen(nameToComplete); + varBox.loadFunctionsAndVariables(scriptIndex, nameToComplete, nameToCompleteLength); + + // Compare the variables + int index = 0; // Index to make sure we are not cycling through the results + int textToInsertLength; + bool addParentheses; + for (int i = 0; i < expectedVariablesCount; i++) { + quiz_assert(i == index); + const char * autocompletionI = varBox.autocompletionAlternativeAtIndex( + nameToCompleteLength, + &textToInsertLength, + &addParentheses, + i, + &index); + quiz_assert(i == index); // If false, the autompletion has cycled: there are not as many results as expected + quiz_assert(strncmp(*(expectedVariables + i), autocompletionI - nameToCompleteLength, textToInsertLength + nameToCompleteLength) == 0); + index++; + } + varBox.autocompletionAlternativeAtIndex( + strlen(nameToComplete), + &textToInsertLength, + &addParentheses, + index, + &index); + /* Assert the autocompletion has cycles: otherwise, there are more results + * than expected. */ + quiz_assert(index == 0); +} + +QUIZ_CASE(variable_box_controller) { + const char * expectedVariables[] = { + "froo", + "from", + "frozenset()" + }; + // FIXME This test does not load imported variables for now + assert_variables_are( + "\x01 from math import *\nfroo=3", + "fr", + expectedVariables, + sizeof(expectedVariables) / sizeof(const char *)); +} diff --git a/apps/code/toolbox.it.i18n b/apps/code/toolbox.it.i18n new file mode 100644 index 000000000..5fd875949 --- /dev/null +++ b/apps/code/toolbox.it.i18n @@ -0,0 +1,4 @@ +Functions = "Funzioni" +Catalog = "Catalogo" +Modules = "Moduli" +LoopsAndTests = "Loops e test" diff --git a/apps/code/toolbox.nl.i18n b/apps/code/toolbox.nl.i18n new file mode 100644 index 000000000..3fb634129 --- /dev/null +++ b/apps/code/toolbox.nl.i18n @@ -0,0 +1,4 @@ +Functions = "Functies" +Catalog = "Catalogus" +Modules = "Modules" +LoopsAndTests = "Loops and tests" diff --git a/apps/code/toolbox.pt.i18n b/apps/code/toolbox.pt.i18n index 81e60c7da..f7cfad07b 100644 --- a/apps/code/toolbox.pt.i18n +++ b/apps/code/toolbox.pt.i18n @@ -1,6 +1,6 @@ -Functions = "Functions" -Catalog = "Catalog" -Modules = "Modules" -LoopsAndTests = "Loops and tests" +Functions = "Funções" +Catalog = "Catálogo" +Modules = "Módulos" +LoopsAndTests = "Laços e testes" Files = "Files" Exceptions = "Exceptions" diff --git a/apps/code/variable_box_controller.cpp b/apps/code/variable_box_controller.cpp index c937b4896..9881b213d 100644 --- a/apps/code/variable_box_controller.cpp +++ b/apps/code/variable_box_controller.cpp @@ -1,4 +1,5 @@ #include "variable_box_controller.h" +#include "python_toolbox.h" #include "script.h" #include "app.h" #include "../shared/toolbox_helpers.h" @@ -8,16 +9,43 @@ #include #include #include +#include + +extern "C" { +#include "py/lexer.h" +#include "py/nlr.h" +#include "py/objmodule.h" +} namespace Code { +// Got these in python/py/src/compile.cpp compiled file +constexpr static uint PN_file_input_2 = 1; +constexpr static uint PN_funcdef = 3; +constexpr static uint PN_expr_stmt = 5; +constexpr static uint PN_import_name = 14; // import math // import math as m // import math, cmath // import math as m, cmath as cm +constexpr static uint PN_import_from = 15; // from math import * // from math import sin // from math import sin as stew // from math import sin, cos // from math import sin as stew, cos as cabbage // from a.b import * +constexpr static uint PN_import_as_name = 99; // sin as stew +constexpr static uint PN_import_as_names = 102; // ... import sin as stew, cos as cabbage +constexpr static uint PN_dotted_name = 104; +/* These are not used for now but might be relevant at some point? +constexpr static uint PN_import_stmt = 92; +constexpr static uint PN_import_from_2 = 93; +constexpr static uint PN_import_from_2b = 94; // "from .foo import" +constexpr static uint PN_import_from_3 = 95; +constexpr static uint PN_import_as_names_paren = 96; +*/ + VariableBoxController::VariableBoxController(ScriptStore * scriptStore) : - NestedMenuController(nullptr, I18n::Message::FunctionsAndVariables), - m_scriptNodesCount(0), - m_scriptStore(scriptStore) + AlternateEmptyNestedMenuController(I18n::Message::FunctionsAndVariables), + m_scriptStore(scriptStore), + m_currentScriptNodesCount(0), + m_builtinNodesCount(0), + m_importedNodesCount(0) { - for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) { - m_leafCells[i].setScriptStore(scriptStore); + for (int i = 0; i < k_scriptOriginsCount; i++) { + m_subtitleCells[i].setBackgroundColor(Palette::WallScreen); + m_subtitleCells[i].setTextColor(Palette::BlueishGrey); } } @@ -29,101 +57,920 @@ bool VariableBoxController::handleEvent(Ion::Events::Event event) { } void VariableBoxController::didEnterResponderChain(Responder * previousFirstResponder) { - /* This Code::VariableBoxController should always be called from an - * environment where Python has already been inited. This way, we do not - * deinit Python when leaving the VariableBoxController, so we do not lose the - * environment that was loaded when entering the VariableBoxController. */ + /* Code::VariableBoxController should always be called from an environment + * where Python has already been inited. This way, we do not deinit Python + * when leaving the VariableBoxController, so we do not lose the environment + * that was loaded when entering the VariableBoxController. */ assert(App::app()->pythonIsInited()); + displayEmptyControllerIfNeeded(); } -static bool shouldAddObject(const char * name, int maxLength) { - if (strlen(name)+1 > (size_t)maxLength) { - return false; +KDCoordinate VariableBoxController::rowHeight(int j) { + NodeOrigin cellOrigin = NodeOrigin::CurrentScript; + int cumulatedOriginsCount = 0; + int cellType = typeAndOriginAtLocation(j, &cellOrigin, &cumulatedOriginsCount); + if (cellType == k_itemCellType) { + if (scriptNodeAtIndex(j - (m_displaySubtitles ? cumulatedOriginsCount : 0))->description() != nullptr) { + // If there is a node description, the cell is bigger + return ScriptNodeCell::k_complexItemHeight; + } + return ScriptNodeCell::k_simpleItemHeight; } - assert(name != nullptr); - if (UTF8Helper::CodePointIs(name, '_')) { - return false; - } - return true; + assert(m_displaySubtitles); + assert(cellType == k_subtitleCellType); + return k_subtitleRowHeight; } int VariableBoxController::numberOfRows() const { - assert(m_scriptNodesCount <= k_maxScriptNodesCount); - return m_scriptNodesCount; + int result = 0; + NodeOrigin origins[] = {NodeOrigin::CurrentScript, NodeOrigin::Builtins, NodeOrigin::Importation}; + for (NodeOrigin origin : origins) { + int nodeCount = nodesCountForOrigin(origin); + if (nodeCount > 0) { + result += nodeCount + (m_displaySubtitles ? 1 : 0); + } + } + return result; +} + +HighlightCell * VariableBoxController::reusableCell(int index, int type) { + assert(index >= 0 && index < reusableCellCount(type)); + if (type == k_itemCellType) { + return m_itemCells + index; + } + assert(m_displaySubtitles); + assert(type == k_subtitleCellType); + return m_subtitleCells + index; } int VariableBoxController::reusableCellCount(int type) { - assert(type == 0); - return k_maxNumberOfDisplayedRows; + if (type == k_subtitleCellType) { + assert(m_displaySubtitles); + return k_scriptOriginsCount; + } + assert(type == k_itemCellType); + return k_maxNumberOfDisplayedItems; } void VariableBoxController::willDisplayCellForIndex(HighlightCell * cell, int index) { - assert(index < m_scriptNodesCount); - assert(m_scriptNodesCount <= k_maxScriptNodesCount); - ScriptNodeCell * myCell = static_cast(cell); - myCell->setScriptNode(&m_scriptNodes[index]); + assert(index >= 0 && index < numberOfRows()); + NodeOrigin cellOrigin = NodeOrigin::CurrentScript; + int cumulatedOriginsCount = 0; + int cellType = typeAndOriginAtLocation(index, &cellOrigin, &cumulatedOriginsCount); + if (cellType == k_itemCellType) { + static_cast(cell)->setScriptNode(scriptNodeAtIndex(index - (m_displaySubtitles ? cumulatedOriginsCount : 0))); + return; + } + assert(m_displaySubtitles); + assert(cellType == k_subtitleCellType); + I18n::Message subtitleMessages[k_scriptOriginsCount] = { + I18n::Message::ScriptInProgress, + I18n::Message::BuiltinsAndKeywords, + I18n::Message::ImportedModulesAndScripts + }; + static_cast(cell)->setMessage(subtitleMessages[(int)cellOrigin]); +} + +void VariableBoxController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { + if (withinTemporarySelection || !m_displaySubtitles) { + return; + } + // Make sure subtitle cells cannot be selected + const int currentSelectedRow = selectedRow(); + if (currentSelectedRow >= 0 && typeAtLocation(0, currentSelectedRow) == k_subtitleCellType) { + if (currentSelectedRow == 0) { + // We scroll to the first cell, otherwise it will never appear again + t->scrollToCell(0, 0); + t->selectCellAtLocation(0, 1); + } else { + t->selectCellAtLocation(0, selectedRow() + (previousSelectedCellY < currentSelectedRow ? 1 : -1)); + } + } } int VariableBoxController::typeAtLocation(int i, int j) { - return 0; + assert(i == 0); + return typeAndOriginAtLocation(j); } -void VariableBoxController::loadFunctionsAndVariables() { - m_scriptNodesCount = 0; - m_scriptStore->scanScriptsForFunctionsAndVariables( - this, - [](void * context, const char * functionName, int scriptIndex) { - if (!shouldAddObject(functionName, k_maxScriptObjectNameSize)) { - return; - } - VariableBoxController * cvc = static_cast(context); - cvc->addFunctionAtIndex(functionName, scriptIndex);}, - [](void * context, const char * variableName, int scriptIndex) { - if (!shouldAddObject(variableName, k_maxScriptObjectNameSize)) { - return; - } - VariableBoxController * cvc = static_cast(context); - cvc->addVariableAtIndex(variableName, scriptIndex);}); +void VariableBoxController::loadFunctionsAndVariables(int scriptIndex, const char * textToAutocomplete, int textToAutocompleteLength) { + assert(scriptIndex >= 0); + + // Reset the node counts + empty(); + + if (textToAutocomplete != nullptr && textToAutocompleteLength < 0) { + textToAutocompleteLength = strlen(textToAutocomplete); + } + /* If we are autocompleting a text, we want the returned text to not include + * the beginning that is equal to the text to autocomplete. + * For instance, if we are displaying the variable box with the text "for" to + * autocomplete, when the user selects "forward", we want to insert the text + * "ward" only. + * While loading the functions and variables, we thus set + * m_shortenResultCharCount, the number of chars to cut from the text + * returned. */ + m_shortenResultCharCount = textToAutocomplete == nullptr ? 0 : textToAutocompleteLength; + + // Always load the builtin functions and variables + loadBuiltinNodes(textToAutocomplete, textToAutocompleteLength); + Script script = m_scriptStore->scriptAtIndex(scriptIndex); + assert(!script.isNull()); + + /* Handle the fetchedForVariableBox status: we will import the current script + * variables in loadCurrentVariablesInScript, so we do not want to import + * those variables before, if any imported script also imported the current + * script. */ + assert(!script.fetchedForVariableBox()); + script.setFetchedForVariableBox(true); + + // Load the imported and current variables + const char * scriptContent = script.content(); + assert(scriptContent != nullptr); + loadImportedVariablesInScript(scriptContent, textToAutocomplete, textToAutocompleteLength); + loadCurrentVariablesInScript(scriptContent, textToAutocomplete, textToAutocompleteLength); } -HighlightCell * VariableBoxController::leafCellAtIndex(int index) { - assert(index >= 0 && index < k_maxNumberOfDisplayedRows); - return &m_leafCells[index]; +const char * VariableBoxController::autocompletionAlternativeAtIndex(int textToAutocompleteLength, int * textToInsertLength, bool * addParentheses, int index, int * indexToUpdate) { + if (numberOfRows() == 0) { + return nullptr; + } + + int nodesCount = 0; // We cannot use numberOfRows as it contains the banners + NodeOrigin origins[] = {NodeOrigin::CurrentScript, NodeOrigin::Builtins, NodeOrigin::Importation}; + for (NodeOrigin origin : origins) { + nodesCount += nodesCountForOrigin(origin); + } + if (index < 0) { + assert(index == -1); + index = nodesCount - 1; + } else if (index >= nodesCount) { + assert(index == nodesCount); + index = 0; + } + + if (indexToUpdate != nullptr) { + *indexToUpdate = index; + } + + ScriptNode * node = scriptNodeAtIndex(index); + const char * currentName = node->name(); + int currentNameLength = node->nameLength(); + if (currentNameLength < 0) { + currentNameLength = strlen(currentName); + } + *addParentheses = node->type() == ScriptNode::Type::WithParentheses; + // Return the text without the beginning that matches the text to autocomplete + *textToInsertLength = currentNameLength - textToAutocompleteLength; + return currentName + textToAutocompleteLength; +} + +void VariableBoxController::loadVariablesImportedFromScripts() { + empty(); + const int scriptsCount = m_scriptStore->numberOfScripts(); + for (int i = 0; i < scriptsCount; i++) { + Script script = m_scriptStore->scriptAtIndex(i); + if (script.fetchedFromConsole()) { + loadGlobalAndImportedVariablesInScriptAsImported(script, nullptr, -1, false); + } + } +} + +void VariableBoxController::empty() { + m_builtinNodesCount = 0; + m_currentScriptNodesCount = 0; + m_importedNodesCount = 0; + m_shortenResultCharCount = 0; + m_scriptStore->clearVariableBoxFetchInformation(); +} + +void VariableBoxController::insertAutocompletionResultAtIndex(int index) { + ScriptNode * selectedScriptNode = scriptNodeAtIndex(index); + + /* We need to check now if we need to add parentheses: insertTextInCaller + * calls handleEventWithText, which will reload the autocompletion for the + * added text, which will probably change the script nodes and + * selectedScriptNode will become invalid. */ + const bool shouldAddParentheses = selectedScriptNode->type() == ScriptNode::Type::WithParentheses; + insertTextInCaller(selectedScriptNode->name() + m_shortenResultCharCount, selectedScriptNode->nameLength() - m_shortenResultCharCount); + // WARNING: selectedScriptNode is now invalid + + if (shouldAddParentheses) { + insertTextInCaller(ScriptNodeCell::k_parenthesesWithEmpty); + } +} + +// PRIVATE METHODS + +int VariableBoxController::NodeNameCompare(ScriptNode * node, const char * name, int nameLength, bool * strictlyStartsWith) { + assert(strictlyStartsWith == nullptr || *strictlyStartsWith == false); + assert(nameLength > 0); + const char * nodeName = node->name(); + const int nodeNameLength = node->nameLength() < 0 ? strlen(nodeName) : node->nameLength(); + const int comparisonLength = std::min(nameLength, nodeNameLength); + int result = strncmp(nodeName, name, comparisonLength); + if (result != 0) { + return result; + } + if (nodeNameLength == nameLength) { + return 0; + } + bool nodeNameLengthStartsWithName = nodeNameLength > nameLength; + if (strictlyStartsWith != nullptr && nodeNameLengthStartsWithName) { + *strictlyStartsWith = true; + } + return nodeNameLengthStartsWithName ? *(nodeName + nameLength) : - *(name + nodeNameLength) ; +} + +int VariableBoxController::nodesCountForOrigin(NodeOrigin origin) const { + if (origin == NodeOrigin::Builtins) { + return static_cast(m_builtinNodesCount); + } + return static_cast(*(const_cast(this)->nodesCountPointerForOrigin(origin))); +} + +size_t * VariableBoxController::nodesCountPointerForOrigin(NodeOrigin origin) { + switch(origin) { + case NodeOrigin::CurrentScript: + return &m_currentScriptNodesCount; + case NodeOrigin::Builtins: + return &m_builtinNodesCount; + default: + assert(origin == NodeOrigin::Importation); + return &m_importedNodesCount; + } +} + +ScriptNode * VariableBoxController::nodesForOrigin(NodeOrigin origin) { + switch(origin) { + case NodeOrigin::CurrentScript: + return m_currentScriptNodes; + case NodeOrigin::Builtins: + return m_builtinNodes; + default: + assert(origin == NodeOrigin::Importation); + return m_importedNodes; + } +} + +ScriptNode * VariableBoxController::scriptNodeAtIndex(int index) { + assert(index >= 0 && index < numberOfRows()); + assert(m_currentScriptNodesCount <= k_maxScriptNodesCount); + assert(m_builtinNodesCount <= k_totalBuiltinNodesCount); + assert(m_importedNodesCount <= k_maxScriptNodesCount); + + NodeOrigin origins[] = {NodeOrigin::CurrentScript, NodeOrigin::Builtins, NodeOrigin::Importation}; + for (NodeOrigin origin : origins) { + const int nodesCount = nodesCountForOrigin(origin); + if (index < nodesCount) { + return nodesForOrigin(origin) + index; + } + index -= nodesCount; + } + assert(false); + return nullptr; +} + +int VariableBoxController::typeAndOriginAtLocation(int i, NodeOrigin * resultOrigin, int * cumulatedOriginsCount) const { + int cellIndex = 0; + int originsCount = 0; + NodeOrigin origins[] = {NodeOrigin::CurrentScript, NodeOrigin::Builtins, NodeOrigin::Importation}; + for (NodeOrigin origin : origins) { + int nodeCount = nodesCountForOrigin(origin); + if (nodeCount > 0) { + originsCount++; + int result = -1; + if (m_displaySubtitles && i == cellIndex) { + result = k_subtitleCellType; + } else { + cellIndex += nodeCount + (m_displaySubtitles ? 1 : 0); + if (i < cellIndex) { + result = k_itemCellType; + } + } + if (result != -1) { + if (resultOrigin != nullptr) { + *resultOrigin = origin; + } + if (cumulatedOriginsCount != nullptr) { + *cumulatedOriginsCount = originsCount; + } + assert(result != k_subtitleCellType || m_displaySubtitles); + return result; + } + } + } + assert(false); + return k_itemCellType; + } bool VariableBoxController::selectLeaf(int rowIndex) { - assert(rowIndex >= 0 && rowIndex < m_scriptNodesCount); - assert(m_scriptNodesCount <= k_maxScriptNodesCount); + assert(rowIndex >= 0 && rowIndex < numberOfRows()); m_selectableTableView.deselectTable(); - ScriptNode selectedScriptNode = m_scriptNodes[rowIndex]; - insertTextInCaller(selectedScriptNode.name()); - if (selectedScriptNode.type() == ScriptNode::Type::Function) { - insertTextInCaller(ScriptNodeCell::k_parenthesesWithEmpty); - } + + int cumulatedOriginsCount = 0; + int cellType = typeAndOriginAtLocation(rowIndex, nullptr, &cumulatedOriginsCount); + assert(cellType == k_itemCellType); + (void)cellType; // Silence warnings + + insertAutocompletionResultAtIndex(rowIndex - (m_displaySubtitles ? cumulatedOriginsCount : 0)); + Container::activeApp()->dismissModalViewController(); return true; } -void VariableBoxController::insertTextInCaller(const char * text) { - int commandBufferMaxSize = strlen(text)+1; +void VariableBoxController::insertTextInCaller(const char * text, int textLength) { + int textLen = textLength < 0 ? strlen(text) : textLength; + constexpr int k_maxScriptObjectNameSize = 100; // Ad hoc value + int commandBufferMaxSize = std::min(k_maxScriptObjectNameSize, textLen + 1); char commandBuffer[k_maxScriptObjectNameSize]; - assert(commandBufferMaxSize <= k_maxScriptObjectNameSize); - Shared::ToolboxHelpers::TextToInsertForCommandText(text, commandBuffer, commandBufferMaxSize, true); + Shared::ToolboxHelpers::TextToInsertForCommandText(text, textLen, commandBuffer, commandBufferMaxSize, true); sender()->handleEventWithText(commandBuffer); } -void VariableBoxController::addFunctionAtIndex(const char * functionName, int scriptIndex) { - if (m_scriptNodesCount < k_maxScriptNodesCount) { - m_scriptNodes[m_scriptNodesCount] = ScriptNode::FunctionNode(functionName, scriptIndex); - m_scriptNodesCount++; +void VariableBoxController::loadBuiltinNodes(const char * textToAutocomplete, int textToAutocompleteLength) { + //TODO Could be great to use strings defined in STATIC const char *const tok_kw[] in python/lexer.c + /* The commented values do not work with our current MicroPython but might + * work later, which is why we keep them. */ + static const struct { const char * name; ScriptNode::Type type; } builtinNames[] = { + {"False", ScriptNode::Type::WithoutParentheses}, + {"None", ScriptNode::Type::WithoutParentheses}, + {"True", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_abs), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_all), ScriptNode::Type::WithParentheses}, + {"and", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_any), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_append), ScriptNode::Type::WithParentheses}, + {"as", ScriptNode::Type::WithoutParentheses}, + //{qstr_str(MP_QSTR_ascii), ScriptNode::Type::WithParentheses}, + {"assert", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_bin), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_bool), ScriptNode::Type::WithParentheses}, + {"break", ScriptNode::Type::WithoutParentheses}, + //{qstr_str(MP_QSTR_breakpoint), ScriptNode::Type::WithParentheses}, + //{qstr_str(MP_QSTR_bytearray), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_bytes), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_callable), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_chr), ScriptNode::Type::WithParentheses}, + {"class", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_classmethod), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_clear), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_cmath), ScriptNode::Type::WithoutParentheses}, + //{qstr_str(MP_QSTR_compile), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_complex), ScriptNode::Type::WithParentheses}, + {"continue", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_count), ScriptNode::Type::WithParentheses}, + {"def", ScriptNode::Type::WithoutParentheses}, + {"del", ScriptNode::Type::WithoutParentheses}, + //{qstr_str(MP_QSTR_delattr), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_dict), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_dir), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_divmod), ScriptNode::Type::WithParentheses}, + {"elif", ScriptNode::Type::WithoutParentheses}, + {"else", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_enumerate), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_eval), ScriptNode::Type::WithParentheses}, + {"except", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_exec), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_filter), ScriptNode::Type::WithParentheses}, + {"finally", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_float), ScriptNode::Type::WithParentheses}, + {"for", ScriptNode::Type::WithoutParentheses}, + //{qstr_str(MP_QSTR_format), ScriptNode::Type::WithParentheses}, + {"from", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_frozenset), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_getattr), ScriptNode::Type::WithParentheses}, + {"global", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_globals), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_hasattr), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_hash), ScriptNode::Type::WithParentheses}, + //{qstr_str(MP_QSTR_help), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_hex), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_id), ScriptNode::Type::WithParentheses}, + {"if", ScriptNode::Type::WithoutParentheses}, + {"import", ScriptNode::Type::WithoutParentheses}, + {"in", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_index), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_input), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_insert), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_int), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_ion), ScriptNode::Type::WithoutParentheses}, + {"is", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_isinstance), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_issubclass), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_iter), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_kandinsky), ScriptNode::Type::WithoutParentheses}, + {"lambda", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_len), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_list), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_locals), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_map), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_math), ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_matplotlib_dot_pyplot), ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_max), ScriptNode::Type::WithParentheses}, + //{qstr_str(MP_QSTR_memoryview), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_min), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_next), ScriptNode::Type::WithParentheses}, + {"nonlocal", ScriptNode::Type::WithoutParentheses}, + {"not", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_object), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_oct), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_open), ScriptNode::Type::WithParentheses}, + {"or", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_ord), ScriptNode::Type::WithParentheses}, + {"pass", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_pow), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_pop), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_print), ScriptNode::Type::WithParentheses}, + //{qstr_str(MP_QSTR_property), ScriptNode::Type::WithParentheses}, + {"raise", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_random), ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_range), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_remove), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_repr), ScriptNode::Type::WithParentheses}, + {"return", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_reverse), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_reversed), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_round), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_set), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_setattr), ScriptNode::Type::WithParentheses}, + //{qstr_str(MP_QSTR_slice), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_sort), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_sorted), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_staticmethod), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_str), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_sum), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_super), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_time), ScriptNode::Type::WithoutParentheses}, + {"try", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_tuple), ScriptNode::Type::WithParentheses}, + {qstr_str(MP_QSTR_turtle), ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_type), ScriptNode::Type::WithParentheses}, + //{qstr_str(MP_QSTR_vars), ScriptNode::Type::WithParentheses}, + {"while", ScriptNode::Type::WithoutParentheses}, + {"with", ScriptNode::Type::WithoutParentheses}, + {"yield", ScriptNode::Type::WithoutParentheses}, + {qstr_str(MP_QSTR_zip), ScriptNode::Type::WithParentheses} + }; + assert(sizeof(builtinNames) / sizeof(builtinNames[0]) == k_totalBuiltinNodesCount); + for (int i = 0; i < k_totalBuiltinNodesCount; i++) { + if (addNodeIfMatches(textToAutocomplete, textToAutocompleteLength, builtinNames[i].type, NodeOrigin::Builtins, builtinNames[i].name)) { + /* We can leverage on the fact that buitin nodes are stored in + * alphabetical order. */ + return; + } } } -void VariableBoxController::addVariableAtIndex(const char * variableName, int scriptIndex) { - if (m_scriptNodesCount < k_maxScriptNodesCount) { - m_scriptNodes[m_scriptNodesCount] = ScriptNode::VariableNode(variableName, scriptIndex); - m_scriptNodesCount++; +/* WARNING: This is very dirty. + * This is done to get the lexer position during lexing. As the _mp_reader_mem_t + * struct is private and declared in python/src/py/reader.c, we copy-paste it + * here to be able to use it. */ +typedef struct _mp_reader_mem_t { + size_t free_len; // if >0 mem is freed on close by: m_free(beg, free_len) + const byte *beg; + const byte *cur; + const byte *end; +} mp_reader_mem_t; + +void VariableBoxController::loadImportedVariablesInScript(const char * scriptContent, const char * textToAutocomplete, int textToAutocompleteLength) { + /* Load the imported variables and functions: lex and the parse on a line per + * line basis until parsing fails, while detecting import structures. */ + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + const char * parseStart = scriptContent; + // Skip new lines at the beginning of the script + while (*parseStart == '\n' && *parseStart != 0) { + parseStart++; + } + const char * parseEnd = UTF8Helper::CodePointSearch(parseStart, '\n'); + + while (parseStart != parseEnd) { + mp_lexer_t *lex = mp_lexer_new_from_str_len(0, parseStart, parseEnd - parseStart, 0); + mp_parse_tree_t parseTree = mp_parse(lex, MP_PARSE_SINGLE_INPUT); + mp_parse_node_t pn = parseTree.root; + + if (MP_PARSE_NODE_IS_STRUCT(pn)) { + addNodesFromImportMaybe((mp_parse_node_struct_t *) pn, textToAutocomplete, textToAutocompleteLength); + } + + mp_parse_tree_clear(&parseTree); + + if (*parseEnd == 0) { + // End of file + nlr_pop(); + return; + } + + parseStart = parseEnd; + // Skip the following \n + while (*parseStart == '\n' && *parseStart != 0) { + parseStart++; + } + parseEnd = UTF8Helper::CodePointSearch(parseStart, '\n'); + } + nlr_pop(); } } +void VariableBoxController::loadCurrentVariablesInScript(const char * scriptContent, const char * textToAutocomplete, int textToAutocompleteLength) { + /* To find variable and funtion names: we lex the script and keep all + * MP_TOKEN_NAME that complete the text to autocomplete and are not already in + * the builtins or imported scripts. */ + + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + + // Lex the script + _mp_lexer_t *lex = mp_lexer_new_from_str_len(0, scriptContent, strlen(scriptContent), false); + + // Keep track of DEF tokens to differentiate between variables and functions + bool defToken = false; + const char * beginningLine = scriptContent; + size_t beginningLineIndex = 0; + + while (lex->tok_kind != MP_TOKEN_END) { + // Keep only MP_TOKEN_NAME tokens + if (lex->tok_kind == MP_TOKEN_NAME) { + + int nameLength = lex->vstr.len; + + /* If the token autocompletes the text and it is not already in the + * variable box, add it. */ + const NodeOrigin origin = NodeOrigin::CurrentScript; + + // Find the token position in the text + size_t line = lex->tok_line - 1; // tok_line starts at 1, not 0 + if (beginningLineIndex < line) { + while (beginningLineIndex < line) { + beginningLine = UTF8Helper::CodePointSearch(beginningLine, '\n') + 1; + beginningLineIndex++; + assert(*beginningLine != 0); // We should not get to the end of the text + } + } + assert(beginningLineIndex == line); + const char * tokenInText = beginningLine + lex->tok_column - 1; // tok_column starts at 1, not 0 + assert(strncmp(tokenInText, lex->vstr.buf, nameLength) == 0); + + ScriptNode::Type nodeType = (defToken || *(tokenInText + nameLength) == '(')? ScriptNode::Type::WithParentheses : ScriptNode::Type::WithoutParentheses; + if (addNodeIfMatches(textToAutocomplete, textToAutocompleteLength, nodeType, origin, tokenInText, nameLength)) { + break; + } + } + + defToken = lex->tok_kind == MP_TOKEN_KW_DEF; + mp_lexer_to_next(lex); + } + + mp_lexer_free(lex); + nlr_pop(); + } +} + +void VariableBoxController::loadGlobalAndImportedVariablesInScriptAsImported(Script script, const char * textToAutocomplete, int textToAutocompleteLength, bool importFromModules) { + if (script.fetchedForVariableBox()) { + // We already fetched these script variables + return; + } + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + const char * scriptName = script.fullName(); + const char * scriptContent = script.content(); + mp_lexer_t *lex = mp_lexer_new_from_str_len(0, scriptContent, strlen(scriptContent), false); + mp_parse_tree_t parseTree = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_parse_node_t pn = parseTree.root; + + if (MP_PARSE_NODE_IS_STRUCT(pn)) { + mp_parse_node_struct_t * pns = (mp_parse_node_struct_t *)pn; + uint structKind = (uint)MP_PARSE_NODE_STRUCT_KIND(pns); + if (structKind == PN_funcdef || structKind == PN_expr_stmt) { + // The script is only a single function or variable definition + addImportStructFromScript(pns, structKind, scriptName, textToAutocomplete, textToAutocompleteLength); + } else if (addNodesFromImportMaybe(pns, textToAutocomplete, textToAutocompleteLength, importFromModules)) { + // The script is is only an import, handled in addNodesFromImportMaybe + } else if (structKind == PN_file_input_2) { + /* At this point, if the script node is not of type "file_input_2", it + * will not have main structures of the wanted type. + * We look for structures at first level (not inside nested scopes) that + * are either dunction definitions, variables statements or imports. */ + size_t n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns); + for (size_t i = 0; i < n; i++) { + mp_parse_node_t child = pns->nodes[i]; + if (MP_PARSE_NODE_IS_STRUCT(child)) { + mp_parse_node_struct_t *child_pns = (mp_parse_node_struct_t*)(child); + structKind = (uint)MP_PARSE_NODE_STRUCT_KIND(child_pns); + if (structKind == PN_funcdef || structKind == PN_expr_stmt) { + if (addImportStructFromScript(child_pns, structKind, scriptName, textToAutocomplete, textToAutocompleteLength)) { + break; + } + } else { + addNodesFromImportMaybe(child_pns, textToAutocomplete, textToAutocompleteLength, importFromModules); + } + } + } + } + } + mp_parse_tree_clear(&parseTree); + nlr_pop(); + } + // Mark that we already fetched these script variables + script.setFetchedForVariableBox(true); +} + +bool VariableBoxController::addNodesFromImportMaybe(mp_parse_node_struct_t * parseNode, const char * textToAutocomplete, int textToAutocompleteLength, bool importFromModules) { + // Determine if the node is an import structure + uint structKind = (uint) MP_PARSE_NODE_STRUCT_KIND(parseNode); + bool structKindIsImportWithoutFrom = structKind == PN_import_name; + if (!structKindIsImportWithoutFrom + && structKind != PN_import_from + && structKind != PN_import_as_names + && structKind != PN_import_as_name) + { + // This was not an import structure + return false; + } + + /* loadAllSourceContent will be True if the struct imports all the content + * from a script / module (for instance, "import math"), instead of single + * items (for instance, "from math import sin"). */ + bool loadAllSourceContent = structKindIsImportWithoutFrom; + + size_t childNodesCount = MP_PARSE_NODE_STRUCT_NUM_NODES(parseNode); + for (size_t i = 0; i < childNodesCount; i++) { + mp_parse_node_t child = parseNode->nodes[i]; + if (MP_PARSE_NODE_IS_LEAF(child) && MP_PARSE_NODE_LEAF_KIND(child) == MP_PARSE_NODE_ID) { + // Parsing something like "import xyz" + const char * id = qstr_str(MP_PARSE_NODE_LEAF_ARG(child)); + + /* xyz might be: + * - a module name -> in which case we want no importation source on the + * node. The node will not be added if it is already in the builtins. + * - a script name -> we want to have xyz.py as the importation source + * - a non-existing identifier -> we want no source */ + const char * sourceId = nullptr; + if (importationSourceIsModule(id)) { + if (!importFromModules) { + return true; + } + } else { + /* If a module and a script have the same name, the micropython + * importation algorithm first looks for a module then for a script. We + * should thus check that the id is not a module name before retreiving + * a script name to put it as source. */ + if (!importationSourceIsScript(id, &sourceId) && !importFromModules) { // Warning : must be done in this order + /* We call importationSourceIsScript to load the script name in + * sourceId. We also use it to make sure, if importFromModules is + * false, that we are not importing variables from something else than + * scripts. */ + return true; + } + } + if (addNodeIfMatches(textToAutocomplete, textToAutocompleteLength, ScriptNode::Type::WithoutParentheses, NodeOrigin::Importation, id, -1, sourceId)) { + break; + } + } else if (MP_PARSE_NODE_IS_STRUCT(child)) { + // Parsing something like "from math import sin" + addNodesFromImportMaybe((mp_parse_node_struct_t *)child, textToAutocomplete, textToAutocompleteLength, importFromModules); + } else if (MP_PARSE_NODE_IS_TOKEN(child) && MP_PARSE_NODE_IS_TOKEN_KIND(child, MP_TOKEN_OP_STAR)) { + /* Parsing something like "from math import *" + * -> Load all the module content */ + loadAllSourceContent = true; + } + } + + // Fetch a script / module content if needed + if (loadAllSourceContent) { + assert(childNodesCount > 0); + const char * importationSourceName = importationSourceNameFromNode(parseNode->nodes[0]); + if (importationSourceName == nullptr) { + // For instance, the name is a "dotted name" but not matplotlib.pyplot + return true; + } + int numberOfModuleChildren = 0; + const ToolboxMessageTree * moduleChildren = nullptr; + if (importationSourceIsModule(importationSourceName, &moduleChildren, &numberOfModuleChildren)) { + if (!importFromModules) { + return true; + } + if (moduleChildren != nullptr) { + /* The importation source is a module that we display in the toolbox: + * get the nodes from the toolbox + * We skip the 3 first nodes, which are "import ...", "from ... import *" + * and "....function". */ + constexpr int numberOfNodesToSkip = 3; + assert(numberOfModuleChildren > numberOfNodesToSkip); + for (int i = numberOfNodesToSkip; i < numberOfModuleChildren; i++) { + const char * name = I18n::translate((moduleChildren + i)->label()); + if (addNodeIfMatches(textToAutocomplete, textToAutocompleteLength, ScriptNode::Type::WithoutParentheses, NodeOrigin::Importation, name, -1, importationSourceName, I18n::translate((moduleChildren + i)->text()))) { + break; + } + } + } else { + //TODO get module variables that are not in the toolbox + } + } else { + // Try fetching the nodes from a script + Script importedScript; + const char * scriptFullName; + if (importationSourceIsScript(importationSourceName, &scriptFullName, &importedScript)) { + loadGlobalAndImportedVariablesInScriptAsImported(importedScript, textToAutocomplete, textToAutocompleteLength); + } + } + } + return true; +} + +const char * VariableBoxController::importationSourceNameFromNode(mp_parse_node_t & node) { + if (MP_PARSE_NODE_IS_LEAF(node) && MP_PARSE_NODE_LEAF_KIND(node) == MP_PARSE_NODE_ID) { + // The importation source is "simple", for instance: from math import * + return qstr_str(MP_PARSE_NODE_LEAF_ARG(node)); + } + if (MP_PARSE_NODE_IS_STRUCT(node)) { //TODO replace this with an assert? + mp_parse_node_struct_t * nodePNS = (mp_parse_node_struct_t *)node; + uint nodeStructKind = MP_PARSE_NODE_STRUCT_KIND(nodePNS); + if (nodeStructKind != PN_dotted_name) { + return nullptr; + } + /* The importation source is "complex", for instance: + * from matplotlib.pyplot import * + * FIXME The solution would be to build a single qstr for this name, + * such as in python/src/compile.c, function do_import_name, from line + * 1117 (found by searching PN_dotted_name). + * We might do this later, for now the only dotted name we might want to + * find is matplolib.pyplot, so we do a very specific search. */ + int numberOfSplitNames = MP_PARSE_NODE_STRUCT_NUM_NODES(nodePNS); + if (numberOfSplitNames != 2) { + return nullptr; + } + const char * nodeSubName = qstr_str(MP_PARSE_NODE_LEAF_ARG(nodePNS->nodes[0])); + if (strcmp(nodeSubName, qstr_str(MP_QSTR_matplotlib)) == 0) { + nodeSubName = qstr_str(MP_PARSE_NODE_LEAF_ARG(nodePNS->nodes[1])); + if (strcmp(nodeSubName, qstr_str(MP_QSTR_pyplot)) == 0) { + return qstr_str(MP_QSTR_matplotlib_dot_pyplot); + } + } + } + return nullptr; +} + +bool VariableBoxController::importationSourceIsModule(const char * sourceName, const ToolboxMessageTree * * moduleChildren, int * numberOfModuleChildren) { + const ToolboxMessageTree * children = static_cast(App::app()->toolboxForInputEventHandler(nullptr))->moduleChildren(sourceName, numberOfModuleChildren); + if (moduleChildren != nullptr) { + *moduleChildren = children; + } + if (children != nullptr) { + return true; + } + // The sourceName might be a module that is not in the toolbox + return mp_module_get(qstr_from_str(sourceName)) != MP_OBJ_NULL; +} + +bool VariableBoxController::importationSourceIsScript(const char * sourceName, const char * * scriptFullName, Script * retreivedScript) { + // Try fetching the nodes from a script + Script importedScript = ScriptStore::ScriptBaseNamed(sourceName); + if (importedScript.isNull()) { + return false; + } + *scriptFullName = importedScript.fullName(); + if (retreivedScript != nullptr) { + *retreivedScript = importedScript; + } + return true; +} + +const char * structName(mp_parse_node_struct_t * structNode) { + // Find the id child node, which stores the struct's name + size_t childNodesCount = MP_PARSE_NODE_STRUCT_NUM_NODES(structNode); + if (childNodesCount < 1) { + return nullptr; + } + mp_parse_node_t child = structNode->nodes[0]; + if (MP_PARSE_NODE_IS_LEAF(child) + && MP_PARSE_NODE_LEAF_KIND(child) == MP_PARSE_NODE_ID) + { + uintptr_t arg = MP_PARSE_NODE_LEAF_ARG(child); + return qstr_str(arg); + } + return nullptr; +} + +bool VariableBoxController::addImportStructFromScript(mp_parse_node_struct_t * pns, uint structKind, const char * scriptName, const char * textToAutocomplete, int textToAutocompleteLength) { + assert(structKind == PN_funcdef || structKind == PN_expr_stmt); + // Find the id child node, which stores the struct's name + const char * name = structName(pns); + if (name == nullptr) { + return false; + } + return addNodeIfMatches(textToAutocomplete, textToAutocompleteLength, structKind == PN_funcdef ? ScriptNode::Type::WithParentheses : ScriptNode::Type::WithoutParentheses, NodeOrigin::Importation, name, -1, scriptName); +} + +// The returned boolean means we should escape the process +bool VariableBoxController::addNodeIfMatches(const char * textToAutocomplete, int textToAutocompleteLength, ScriptNode::Type nodeType, NodeOrigin nodeOrigin, const char * nodeName, int nodeNameLength, const char * nodeSourceName, const char * nodeDescription) { + assert(nodeName != nullptr); + if (nodeNameLength < 0) { + nodeNameLength = strlen(nodeName); + } + // Step 1: Check if the node matches the textToAutocomplete + + // Step 1.1: Few escape cases + /* If the node will go to imported, do not add it if it starts with an + * underscore : such identifiers are meant to be private. */ + if (nodeOrigin == NodeOrigin::Importation && UTF8Helper::CodePointIs(nodeName, '_')) { + return false; + } + /* If the node is extracted from the current script, escape the current + * autocompleted word. */ + if (nodeOrigin == NodeOrigin::CurrentScript && nodeName == textToAutocomplete) { + return false; + } + bool nodeInLexicographicalOrder = nodeOrigin == NodeOrigin::Builtins; + + ScriptNode node(nodeType, nodeName, nodeNameLength, nodeSourceName, nodeDescription); + + // Step 1.2: check if textToAutocomplete matches the node + if (textToAutocomplete != nullptr) { + /* Check that nodeName autocompletes the text to autocomplete + * - The start of nodeName must be equal to the text to autocomplete */ + bool strictlyStartsWith = false; + int cmp = NodeNameCompare(&node, textToAutocomplete, textToAutocompleteLength, &strictlyStartsWith); + if (cmp == 0) { + // We don't accept the node if it has no parentheses + if (node.type() != ScriptNode::Type::WithParentheses) { + return false; + } + } else { + // We don't accept the node if it doesn't start as the textToAutocomplete + if (!strictlyStartsWith) { + if (nodeInLexicographicalOrder && cmp > 0) { + /* Signal to end the nodes scanning because we went past the + * textToAutocomplete in lexicographical order. */ + return true; + } + return false; + } + } + } + + // Step 2: Add Node + + // Step 2.1: don't overflow the node list + size_t * currentNodeCount = nodesCountPointerForOrigin(nodeOrigin); + ScriptNode * nodes = nodesForOrigin(nodeOrigin); + if (*currentNodeCount >= MaxNodesCountForOrigin(nodeOrigin)) { + // There is no room to add another node + return true; + } + + // Step 2.2: find where to add the node (and check that it doesn't exist yet) + size_t insertionIndex = *currentNodeCount; + if (nodeOrigin == NodeOrigin::Builtins) { + /* For builtin nodes, we don't need to check whether the node was already + * added because they're added first in lexicographical order. Plus, we + * want to add it at the end of list to respect the lexicographical order. */ + assert(nodeInLexicographicalOrder); + } else { + // Look where to add + bool alreadyInVarBox = false; + // This could be faster with dichotomia, but there is no speed problem for now + NodeOrigin origins[] = {NodeOrigin::CurrentScript, NodeOrigin::Builtins, NodeOrigin::Importation}; + for (NodeOrigin origin : origins) { + const int nodesCount = nodesCountForOrigin(origin); + ScriptNode * nodes = nodesForOrigin(origin); + for (int i = 0; i < nodesCount; i++) { + ScriptNode * matchingNode = nodes + i; + int comparisonResult = NodeNameCompare(matchingNode, nodeName, nodeNameLength); + if (comparisonResult == 0 || (comparisonResult == '(' && nodeType == ScriptNode::Type::WithParentheses)) { + alreadyInVarBox = true; + break; + } + if (comparisonResult > 0) { + if (nodeOrigin == origin) { + insertionIndex = i; + } + break; + } + } + if (alreadyInVarBox) { + return false; + } + } + } + + // Step 2.3: Shift all the following nodes + for (size_t i = *currentNodeCount; i > insertionIndex; i--) { + nodes[i] = nodes[i - 1]; + } + + // Step 2.4: Check if the node source name fits, if not, do not use it + if (!ScriptNodeCell::CanDisplayNameAndSource(nodeNameLength, nodeSourceName)) { + nodeSourceName = nullptr; + } + // Step 2.5: Add the node + nodes[insertionIndex] = ScriptNode(nodeType, nodeName, nodeNameLength, nodeSourceName, nodeDescription); + // Increase the node count + *currentNodeCount = *currentNodeCount + 1; + return false; +} + } diff --git a/apps/code/variable_box_controller.h b/apps/code/variable_box_controller.h index 79d107f00..f8968b41b 100644 --- a/apps/code/variable_box_controller.h +++ b/apps/code/variable_box_controller.h @@ -1,14 +1,17 @@ #ifndef CODE_VARIABLE_BOX_CONTROLLER_H #define CODE_VARIABLE_BOX_CONTROLLER_H -#include +#include +#include +#include #include "script_node.h" #include "script_node_cell.h" #include "script_store.h" +#include "variable_box_empty_controller.h" namespace Code { -class VariableBoxController : public NestedMenuController { +class VariableBoxController : public AlternateEmptyNestedMenuController { public: VariableBoxController(ScriptStore * scriptStore); @@ -16,28 +19,98 @@ public: bool handleEvent(Ion::Events::Event event) override; void didEnterResponderChain(Responder * previousFirstResponder) override; - /* ListViewDataSource */ + /* TableViewDataSource */ + KDCoordinate rowHeight(int j) override; int numberOfRows() const override; + HighlightCell * reusableCell(int index, int type) override; int reusableCellCount(int type) override; - void willDisplayCellForIndex(HighlightCell * cell, int index) override; int typeAtLocation(int i, int j) override; + /* ListViewDataSource */ + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + /* SelectableTableViewDelegate */ + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false) override; + + //AlternateEmptyNestedMenuController + ViewController * emptyViewController() override { return &m_variableBoxEmptyController; } /* VariableBoxController */ - void loadFunctionsAndVariables(); + void setDisplaySubtitles(bool display) { m_displaySubtitles = display; } + void loadFunctionsAndVariables(int scriptIndex, const char * textToAutocomplete, int textToAutocompleteLength); + const char * autocompletionAlternativeAtIndex(int textToAutocompleteLength, int * textToInsertLength, bool * addParentheses, int index, int * indexToUpdate = nullptr); + void loadVariablesImportedFromScripts(); + void empty(); + void insertAutocompletionResultAtIndex(int index); + private: - constexpr static int k_maxScriptObjectNameSize = 100; - constexpr static int k_maxNumberOfDisplayedRows = 6; //240/40 - constexpr static int k_maxScriptNodesCount = 32; - HighlightCell * leafCellAtIndex(int index) override; - HighlightCell * nodeCellAtIndex(int index) override { return nullptr; } + constexpr static size_t k_maxNumberOfDisplayedItems = (Ion::Display::Height - Metric::TitleBarHeight - Metric::PopUpTopMargin) / ScriptNodeCell::k_simpleItemHeight + 2; // +2 if the cells are cropped on top and at the bottom + constexpr static size_t k_maxScriptNodesCount = 32; // Chosen without particular reasons + constexpr static int k_totalBuiltinNodesCount = 107; + constexpr static uint8_t k_scriptOriginsCount = 3; + constexpr static uint8_t k_subtitleCellType = NodeCellType; // We don't care as it is not selectable + constexpr static uint8_t k_itemCellType = LeafCellType; // So that upper class NestedMenuController knows it's a leaf + constexpr static KDCoordinate k_subtitleRowHeight = 23; + + enum class NodeOrigin : uint8_t { + CurrentScript = 0, + Builtins = 1, + Importation = 2 + }; + + /* Returns: + * - a negative int if the node name is before name in alphabetical + * order + * - 0 if they are equal + * - a positive int if it is after in alphabetical order. + * strictlyStartsWith is set to True if the node name starts with name but + * they are not equal.*/ + static int NodeNameCompare(ScriptNode * node, const char * name, int nameLength, bool * strictlyStartsWith = nullptr); + + // Nodes and nodes count + static size_t MaxNodesCountForOrigin(NodeOrigin origin) { + return origin == NodeOrigin::Builtins ? k_totalBuiltinNodesCount : k_maxScriptNodesCount; + } + int nodesCountForOrigin(NodeOrigin origin) const; + size_t * nodesCountPointerForOrigin(NodeOrigin origin); + ScriptNode * nodesForOrigin(NodeOrigin origin); + ScriptNode * scriptNodeAtIndex(int index); + + // Cell getters + int typeAndOriginAtLocation(int i, NodeOrigin * resultOrigin = nullptr, int * cumulatedOriginsCount = nullptr) const; + + // NestedMenuController + HighlightCell * leafCellAtIndex(int index) override { assert(false); return nullptr; } + HighlightCell * nodeCellAtIndex(int index) override { assert(false); return nullptr; } bool selectLeaf(int rowIndex) override; - void insertTextInCaller(const char * text); - void addFunctionAtIndex(const char * functionName, int scriptIndex); - void addVariableAtIndex(const char * variableName, int scriptIndex); - ScriptNode m_scriptNodes[k_maxScriptNodesCount]; - int m_scriptNodesCount; + void insertTextInCaller(const char * text, int textLength = -1); + + // Loading + void loadBuiltinNodes(const char * textToAutocomplete, int textToAutocompleteLength); + void loadImportedVariablesInScript(const char * scriptContent, const char * textToAutocomplete, int textToAutocompleteLength); + void loadCurrentVariablesInScript(const char * scriptContent, const char * textToAutocomplete, int textToAutocompleteLength); + void loadGlobalAndImportedVariablesInScriptAsImported(Script script, const char * textToAutocomplete, int textToAutocompleteLength, bool importFromModules = true); + // Returns true if this was an import structure + bool addNodesFromImportMaybe(mp_parse_node_struct_t * parseNode, const char * textToAutocomplete, int textToAutocompleteLength, bool importFromModules = true); + const char * importationSourceNameFromNode(mp_parse_node_t & node); + bool importationSourceIsModule(const char * sourceName, const ToolboxMessageTree * * moduleChildren = nullptr, int * numberOfModuleChildren = nullptr); + bool importationSourceIsScript(const char * sourceName, const char * * scriptFullName, Script * retreivedScript = nullptr); + bool addImportStructFromScript(mp_parse_node_struct_t * pns, uint structKind, const char * scriptName, const char * textToAutocomplete, int textToAutocompleteLength); + /* Add a node if it completes the text to autocomplete and if it is not + * already contained in the variable box. The returned boolean means we + * should escape the node scanning process (due to the lexicographical order + * or full node table). */ + bool addNodeIfMatches(const char * textToAutocomplete, int textToAutocompleteLength, ScriptNode::Type type, NodeOrigin origin, const char * nodeName, int nodeNameLength = -1, const char * nodeSourceName = nullptr, const char * description = nullptr); + VariableBoxEmptyController m_variableBoxEmptyController; + ScriptNode m_currentScriptNodes[k_maxScriptNodesCount]; + ScriptNode m_builtinNodes[k_totalBuiltinNodesCount]; + ScriptNode m_importedNodes[k_maxScriptNodesCount]; + ScriptNodeCell m_itemCells[k_maxNumberOfDisplayedItems]; + MessageTableCell m_subtitleCells[k_scriptOriginsCount]; ScriptStore * m_scriptStore; - ScriptNodeCell m_leafCells[k_maxNumberOfDisplayedRows]; + size_t m_currentScriptNodesCount; + size_t m_builtinNodesCount; + size_t m_importedNodesCount; + int m_shortenResultCharCount; // This is used to send only the completing text when we are autocompleting + bool m_displaySubtitles; }; } diff --git a/apps/code/variable_box_empty_controller.cpp b/apps/code/variable_box_empty_controller.cpp new file mode 100644 index 000000000..83bd42471 --- /dev/null +++ b/apps/code/variable_box_empty_controller.cpp @@ -0,0 +1,13 @@ +#include "variable_box_empty_controller.h" +#include + +namespace Code { + +VariableBoxEmptyController::VariableBoxEmptyView::VariableBoxEmptyView() : + ::ModalViewEmptyController::ModalViewEmptyView() +{ + initMessageViews(); + m_message.setMessage(I18n::Message::NoWordAvailableHere); +} + +} diff --git a/apps/code/variable_box_empty_controller.h b/apps/code/variable_box_empty_controller.h new file mode 100644 index 000000000..d78cea504 --- /dev/null +++ b/apps/code/variable_box_empty_controller.h @@ -0,0 +1,34 @@ +#ifndef APPS_CODE_VARIABLE_BOX_EMPTY_CONTROLLER_H +#define APPS_CODE_VARIABLE_BOX_EMPTY_CONTROLLER_H + +#include + +namespace Code { + +class VariableBoxEmptyController : public ModalViewEmptyController { +public: + VariableBoxEmptyController() : + ModalViewEmptyController(), + m_view() + {} + // View Controller + View * view() override { return &m_view; } +private: + class VariableBoxEmptyView : public ModalViewEmptyController::ModalViewEmptyView { + public: + constexpr static int k_numberOfMessages = 1; + VariableBoxEmptyView(); + private: + int numberOfMessageTextViews() const override { return k_numberOfMessages; } + MessageTextView * messageTextViewAtIndex(int index) override { + assert(index >= 0 && index < k_numberOfMessages); + return &m_message; + } + MessageTextView m_message; + }; + VariableBoxEmptyView m_view; +}; + +} + +#endif diff --git a/apps/exam_mode_configuration_official.cpp b/apps/exam_mode_configuration_official.cpp index 860b2f65c..7640d21e6 100644 --- a/apps/exam_mode_configuration_official.cpp +++ b/apps/exam_mode_configuration_official.cpp @@ -6,7 +6,10 @@ constexpr Shared::SettingsMessageTree ExamModeConfiguration::s_modelExamChildren[2] = {Shared::SettingsMessageTree(I18n::Message::ActivateExamMode), Shared::SettingsMessageTree(I18n::Message::ActivateDutchExamMode)}; int ExamModeConfiguration::numberOfAvailableExamMode() { - if (GlobalPreferences::sharedGlobalPreferences()->language() != I18n::Language::EN || GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { + if ((GlobalPreferences::sharedGlobalPreferences()->language() != I18n::Language::EN + && GlobalPreferences::sharedGlobalPreferences()->language() != I18n::Language::NL) + || GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) + { return 1; } return 2; diff --git a/apps/graph/Makefile b/apps/graph/Makefile index 48e920ccb..4f440fea8 100644 --- a/apps/graph/Makefile +++ b/apps/graph/Makefile @@ -33,13 +33,6 @@ app_graph_src = $(addprefix apps/graph/,\ apps_src += $(app_graph_src) -i18n_files += $(addprefix apps/graph/,\ - base.de.i18n\ - base.en.i18n\ - base.es.i18n\ - base.fr.i18n\ - base.pt.i18n\ - base.hu.i18n\ -) +i18n_files += $(call i18n_without_universal_for,graph/base) $(eval $(call depends_on_image,apps/graph/app.cpp,apps/graph/graph_icon.png)) diff --git a/apps/graph/app.cpp b/apps/graph/app.cpp index 264e699da..c440a74ca 100644 --- a/apps/graph/app.cpp +++ b/apps/graph/app.cpp @@ -81,9 +81,9 @@ CodePoint App::XNT() { } NestedMenuController * App::variableBoxForInputEventHandler(InputEventHandler * textInput) { - VariableBoxController * varBox = AppsContainer::sharedAppsContainer()->variableBoxController(); + MathVariableBoxController * varBox = AppsContainer::sharedAppsContainer()->variableBoxController(); varBox->setSender(textInput); - varBox->lockDeleteEvent(VariableBoxController::Page::Function); + varBox->lockDeleteEvent(MathVariableBoxController::Page::Function); return varBox; } diff --git a/apps/graph/base.it.i18n b/apps/graph/base.it.i18n new file mode 100644 index 000000000..656d79726 --- /dev/null +++ b/apps/graph/base.it.i18n @@ -0,0 +1,33 @@ +FunctionApp = "Funzioni" +FunctionAppCapital = "FUNZIONI" +FunctionTab = "Funzioni" +AddFunction = "Aggiungi funzione" +DeleteFunction = "Cancella funzione" +CurveType = "Tipo di curva" +CartesianType = "Cartesiana " +PolarType = "Polare " +ParametricType = "Parametrica " +IntervalT = "Intervallo t" +IntervalTheta = "Intervallo θ" +IntervalX = "Intervallo x" +FunctionDomain = "Int. tracciamento" +FunctionColor = "Colore della funzione" +NoFunction = "Nessuna funzione" +NoActivatedFunction = "Nessuna funzione attivata" +PlotOptions = "Opzioni della curva" +Compute = "Calcolare" +Zeros = "Zeri" +Tangent = "Tangente" +Intersection = "Intersezione" +Preimage = "Controimmagine" +SelectLowerBound = "Scegliere il limite inferiore" +SelectUpperBound = "Scegliere il limite superiore" +NoMaximumFound = "Nessun massimo trovato" +NoMinimumFound = "Nessun minimo trovato" +NoZeroFound = "Nessuno zero trovato" +NoIntersectionFound = "Nessuna intersezione trovata" +NoPreimageFound = "Nessuna immagine trovata" +DerivativeFunctionColumn = "Colonna della funzione derivata" +HideDerivativeColumn = "Nascondere la funzione derivata" +AllowedCharactersAZaz09 = "Caratteri consentiti : A-Z, a-z, 0-9, _" +ReservedName = "Nome riservato" diff --git a/apps/graph/base.nl.i18n b/apps/graph/base.nl.i18n new file mode 100644 index 000000000..a809429e5 --- /dev/null +++ b/apps/graph/base.nl.i18n @@ -0,0 +1,33 @@ +FunctionApp = "Functies" +FunctionAppCapital = "FUNCTIES" +FunctionTab = "Functies" +AddFunction = "Functie toevoegen" +DeleteFunction = "Functie verwijderen" +CurveType = "Kromme type" +CartesianType = "Cartesiaans " +PolarType = "Polair " +ParametricType = "Parametrisch " +IntervalT = "t interval" +IntervalTheta = "θ interval" +IntervalX = "x interval" +FunctionDomain = "Plotbereik" +FunctionColor = "Functiekleur" +NoFunction = "Geen functie" +NoActivatedFunction = "Geen functie is ingeschakelt" +PlotOptions = "Plot opties" +Compute = "Bereken" +Zeros = "Nulpunten" +Tangent = "Tangens" +Intersection = "Snijpunt" +Preimage = "Inverse beeld" +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" +DerivativeFunctionColumn = "Afgeleide functie kolom" +HideDerivativeColumn = "Verberg de afgeleide functie" +AllowedCharactersAZaz09 = "Toegestane tekens: A-Z, a-z, 0-9, _" +ReservedName = "Voorbehouden naam" diff --git a/apps/graph/base.pt.i18n b/apps/graph/base.pt.i18n index 449b4da90..707a10013 100644 --- a/apps/graph/base.pt.i18n +++ b/apps/graph/base.pt.i18n @@ -1,5 +1,5 @@ -FunctionApp = "Função" -FunctionAppCapital = "FUNÇÃO" +FunctionApp = "Funções" +FunctionAppCapital = "FUNÇÕES" FunctionTab = "Funções" AddFunction = "Adicionar uma função" DeleteFunction = "Eliminar a função" @@ -16,16 +16,16 @@ NoFunction = "Nenhuma função" NoActivatedFunction = "Sem função activada" PlotOptions = "Opções da curva" Compute = "Calcular" -Zeros = "Raízes" +Zeros = "Zeros" Tangent = "Tangente" -Intersection = "Intersecção" +Intersection = "Interseção" Preimage = "Imagem inversa" -SelectLowerBound = "Selecionar limite superior " -SelectUpperBound = "Selecionar limite inferior " +SelectLowerBound = "Selecionar limite inferior" +SelectUpperBound = "Selecionar limite superior" NoMaximumFound = "Nenhum máximo encontrado" NoMinimumFound = "Nenhum mínimo encontrado" NoZeroFound = "Nenhuma raiz encontrada" -NoIntersectionFound = "Nenhuma intersecção encontrada" +NoIntersectionFound = "Nenhuma interseção encontrada" NoPreimageFound = "Nenhuma imagem inversa encontrada" DerivativeFunctionColumn = "Coluna da função derivada" HideDerivativeColumn = "Esconder função derivada" diff --git a/apps/graph/list/text_field_function_title_cell.cpp b/apps/graph/list/text_field_function_title_cell.cpp index 55c7f6676..38f0284f2 100644 --- a/apps/graph/list/text_field_function_title_cell.cpp +++ b/apps/graph/list/text_field_function_title_cell.cpp @@ -27,8 +27,8 @@ void TextFieldFunctionTitleCell::setEditing(bool editing) { int extensionLength = UTF8Helper::HasCodePoint(previousText, UCodePointGreekSmallLetterTheta) ? Shared::Function::k_parenthesedThetaArgumentByteLength : Shared::Function::k_parenthesedXNTArgumentByteLength; m_textField.setExtensionLength(extensionLength); m_textField.setEditing(true); - m_textField.setText(previousText); m_textField.setDraftTextBufferSize(Poincare::SymbolAbstract::k_maxNameSize+extensionLength); + m_textField.setText(previousText); } bool TextFieldFunctionTitleCell::isEditing() const { diff --git a/apps/helpers.mk b/apps/helpers.mk new file mode 100644 index 000000000..1d86bb88c --- /dev/null +++ b/apps/helpers.mk @@ -0,0 +1,19 @@ +# i18n helpers + +# Should be called as: i18n_for_locale basename locale +define i18n_for_locale +$(addprefix apps/, $(addprefix $(1), $(addprefix ., $(addsuffix .i18n,$(2))))) +endef + +# Should be called as: i18n_without_universal_for basename +# Adds the basename.**.i18n for all locales in EPSILON_I18N +define i18n_without_universal_for +$(foreach LOCALE,$(EPSILON_I18N),$(call i18n_for_locale, $(1), $(LOCALE))) +endef + +# Should be called as: i18n_with_universal_for basename +# Adds basename.**.i18n for all EPSILON_I18N locales + basename.universal.i18n +define i18n_with_universal_for +$(call i18n_without_universal_for,$(1)) +$(call i18n_for_locale,$(1),universal) +endef diff --git a/apps/home/Makefile b/apps/home/Makefile index c3b8550f8..fc45cb479 100644 --- a/apps/home/Makefile +++ b/apps/home/Makefile @@ -6,11 +6,4 @@ app_home_src = $(addprefix apps/home/,\ apps_src += $(app_home_src) -i18n_files += $(addprefix apps/home/,\ - base.de.i18n \ - base.en.i18n \ - base.es.i18n \ - base.fr.i18n \ - base.pt.i18n \ - base.hu.i18n \ -) +i18n_files += $(call i18n_without_universal_for,home/base) diff --git a/apps/home/base.it.i18n b/apps/home/base.it.i18n new file mode 100644 index 000000000..cfd37f3df --- /dev/null +++ b/apps/home/base.it.i18n @@ -0,0 +1,4 @@ +Apps = "Applicazioni" +AppsCapital = "APPLICAZIONI" +ForbidenAppInExamMode1 = "Questa applicazione è" +ForbidenAppInExamMode2 = "proibita nella modalità d'esame" diff --git a/apps/home/base.nl.i18n b/apps/home/base.nl.i18n new file mode 100644 index 000000000..94900c4b1 --- /dev/null +++ b/apps/home/base.nl.i18n @@ -0,0 +1,4 @@ +Apps = "Applicaties" +AppsCapital = "APPLICATIES" +ForbidenAppInExamMode1 = "Deze applicatie is" +ForbidenAppInExamMode2 = "uitgesloten in examenstand" diff --git a/apps/home/base.pt.i18n b/apps/home/base.pt.i18n index 5e84c0ba1..f3efc8ffd 100644 --- a/apps/home/base.pt.i18n +++ b/apps/home/base.pt.i18n @@ -1,4 +1,4 @@ Apps = "Aplicações" AppsCapital = "OMEGA" -ForbidenAppInExamMode1 = "This application is" -ForbidenAppInExamMode2 = "forbidden in exam mode" +ForbidenAppInExamMode1 = "Esta aplicação é" +ForbidenAppInExamMode2 = "proibida no Modo de Exame" diff --git a/apps/home/controller.cpp b/apps/home/controller.cpp index 7a1640696..4ddb50038 100644 --- a/apps/home/controller.cpp +++ b/apps/home/controller.cpp @@ -2,6 +2,8 @@ #include "app.h" #include "../apps_container.h" #include "../global_preferences.h" +#include "../exam_mode_configuration.h" + extern "C" { #include } @@ -65,7 +67,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 @@ -93,21 +94,16 @@ bool Controller::handleEvent(Ion::Events::Event event) { return true; } } - } else { -#endif - - ::App::Snapshot * selectedSnapshot = container->appSnapshotAtIndex(index); - if (((GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::Dutch || GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::NoSymNoText) && selectedSnapshot->descriptor()->examinationLevel() < 2) || - ((GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::Standard || GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::NoSym) && selectedSnapshot->descriptor()->examinationLevel() < 1)) { - App::app()->displayWarning(I18n::Message::ForbidenAppInExamMode1, I18n::Message::ForbidenAppInExamMode2); - } else { - bool switched = container->switchTo(selectedSnapshot); - assert(switched); - (void) switched; // Silence compilation warning about unused variable. - } -#ifdef HOME_DISPLAY_EXTERNALS } #endif + ::App::Snapshot * selectedSnapshot = container->appSnapshotAtIndex(index); + if (ExamModeConfiguration::appIsForbiddenInExamMode(selectedSnapshot->descriptor()->name(), GlobalPreferences::sharedGlobalPreferences()->examMode())) { + App::app()->displayWarning(I18n::Message::ForbidenAppInExamMode1, I18n::Message::ForbidenAppInExamMode2); + } else { + bool switched = container->switchTo(selectedSnapshot); + assert(switched); + (void) switched; // Silence compilation warning about unused variable. + } return true; } @@ -227,6 +223,18 @@ void Controller::tableViewDidChangeSelection(SelectableTableView * t, int previo if (withinTemporarySelection) { return; } + /* To prevent the selectable table view to select cells that are unvisible, + * we reselect the previous selected cell as soon as the selected cell is + * unvisible. This trick does not create an endless loop as we ensure not to + * stay on a unvisible cell and to initialize the first cell on a visible one + * (so the previous one is always visible). */ + int appIndex = (t->selectedColumn()+t->selectedRow()*k_numberOfColumns)+1; + if (appIndex >= AppsContainer::sharedAppsContainer()->numberOfApps()) { + t->selectCellAtLocation(previousSelectedCellX, previousSelectedCellY); + } +} + +void Controller::tableViewDidChangeSelectionAndDidScroll(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { /* If the number of apps (including home) is != 3*n+1, when we display the * lowest icons, the other(s) are empty. As no icon is thus redrawn on the * previous ones, the cell is not cleaned. We need to redraw a white rect on @@ -236,16 +244,7 @@ void Controller::tableViewDidChangeSelection(SelectableTableView * t, int previo * background complete redrawing but the code is a bit * clumsy. */ if (t->selectedRow() == numberOfRows()-1) { - m_view.reloadBottomRow(this, this->numberOfIcons(), k_numberOfColumns); - } - /* To prevent the selectable table view to select cells that are unvisible, - * we reselect the previous selected cell as soon as the selected cell is - * unvisible. This trick does not create an endless loop as we ensure not to - * stay on a unvisible cell and to initialize the first cell on a visible one - * (so the previous one is always visible). */ - int appIndex = (t->selectedColumn()+t->selectedRow()*k_numberOfColumns)+1; - if (appIndex >= this->numberOfIcons() + 1) { - t->selectCellAtLocation(previousSelectedCellX, previousSelectedCellY); + m_view.reloadBottomRow(this, AppsContainer::sharedAppsContainer()->numberOfApps()-1, k_numberOfColumns); } } diff --git a/apps/home/controller.h b/apps/home/controller.h index f7c4b1e72..90a5604a3 100644 --- a/apps/home/controller.h +++ b/apps/home/controller.h @@ -25,6 +25,7 @@ public: int reusableCellCount() const override; void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; + void tableViewDidChangeSelectionAndDidScroll(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; private: int numberOfIcons() const; SelectableTableViewDataSource * selectionDataSource() const; diff --git a/apps/language_it.universal.i18n b/apps/language_it.universal.i18n new file mode 100644 index 000000000..5914a13e6 --- /dev/null +++ b/apps/language_it.universal.i18n @@ -0,0 +1 @@ +LanguageIT = "Italiano " diff --git a/apps/language_it_iso6391.universal.i18n b/apps/language_it_iso6391.universal.i18n new file mode 100644 index 000000000..6a72c7216 --- /dev/null +++ b/apps/language_it_iso6391.universal.i18n @@ -0,0 +1 @@ +LanguageISO6391IT = "it" diff --git a/apps/language_nl.universal.i18n b/apps/language_nl.universal.i18n new file mode 100644 index 000000000..a509bf7b0 --- /dev/null +++ b/apps/language_nl.universal.i18n @@ -0,0 +1 @@ +LanguageNL = "Nederlands " diff --git a/apps/language_nl_iso6391.universal.i18n b/apps/language_nl_iso6391.universal.i18n new file mode 100644 index 000000000..0fef88fd0 --- /dev/null +++ b/apps/language_nl_iso6391.universal.i18n @@ -0,0 +1 @@ +LanguageISO6391NL = "nl" diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index 1bc80bd1a..6a282996c 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -738,7 +738,7 @@ MathToolbox::MathToolbox() : } bool MathToolbox::selectLeaf(int selectedRow) { - ToolboxMessageTree * messageTree = (ToolboxMessageTree *)m_messageTreeModel->children(selectedRow); + ToolboxMessageTree * messageTree = (ToolboxMessageTree *)m_messageTreeModel->childAtIndex(selectedRow); m_selectableTableView.deselectTable(); // Translate the message @@ -748,7 +748,7 @@ bool MathToolbox::selectLeaf(int selectedRow) { // Remove the arguments if we kept one message for both inserted and displayed message int maxTextToInsertLength = strlen(text) + 1; assert(maxTextToInsertLength <= k_maxMessageSize); - Shared::ToolboxHelpers::TextToInsertForCommandText(text, textToInsert, maxTextToInsertLength, true); + Shared::ToolboxHelpers::TextToInsertForCommandText(text, -1, textToInsert, maxTextToInsertLength, true); text = textToInsert; } sender()->handleEventWithText(text); diff --git a/apps/variable_box_controller.cpp b/apps/math_variable_box_controller.cpp similarity index 77% rename from apps/variable_box_controller.cpp rename to apps/math_variable_box_controller.cpp index 5b5f41dce..1d6c968ce 100644 --- a/apps/variable_box_controller.cpp +++ b/apps/math_variable_box_controller.cpp @@ -1,4 +1,4 @@ -#include "variable_box_controller.h" +#include "math_variable_box_controller.h" #include "shared/global_context.h" #include "shared/continuous_function.h" #include @@ -14,8 +14,8 @@ using namespace Poincare; using namespace Shared; using namespace Ion; -VariableBoxController::VariableBoxController() : - NestedMenuController(nullptr, I18n::Message::Variables), +MathVariableBoxController::MathVariableBoxController() : + AlternateEmptyNestedMenuController(I18n::Message::Variables), m_currentPage(Page::RootMenu), m_lockPageDelete(Page::RootMenu), m_firstMemoizedLayoutIndex(0) @@ -25,24 +25,20 @@ VariableBoxController::VariableBoxController() : } } -void VariableBoxController::viewWillAppear() { +void MathVariableBoxController::viewWillAppear() { assert(m_currentPage == Page::RootMenu); - NestedMenuController::viewWillAppear(); + AlternateEmptyNestedMenuController::viewWillAppear(); } -void VariableBoxController::viewDidDisappear() { - if (isDisplayingEmptyController()) { - pop(); - } - - NestedMenuController::viewDidDisappear(); +void MathVariableBoxController::viewDidDisappear() { + AlternateEmptyNestedMenuController::viewDidDisappear(); /* NestedMenuController::viewDidDisappear might need cell heights, which would - * use the VariableBoxController cell heights memoization. We thus reset the - * VariableBoxController layouts only after calling the parent's + * use the MathVariableBoxController cell heights memoization. We thus reset the + * MathVariableBoxController layouts only after calling the parent's * viewDidDisappear. */ - // Tidy the layouts displayed in the VariableBoxController to clean TreePool + // Tidy the layouts displayed in the MathVariableBoxController to clean TreePool for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) { m_leafCells[i].setLayout(Layout()); m_leafCells[i].setAccessoryLayout(Layout()); @@ -53,7 +49,7 @@ void VariableBoxController::viewDidDisappear() { setPage(Page::RootMenu); } -bool VariableBoxController::handleEvent(Ion::Events::Event event) { +bool MathVariableBoxController::handleEvent(Ion::Events::Event event) { /* We do not want to handle backspace event if: * - On the root menu page * The deletion on the current page is locked @@ -66,13 +62,13 @@ bool VariableBoxController::handleEvent(Ion::Events::Event event) { int newSelectedRow = rowIndex >= numberOfRows() ? numberOfRows()-1 : rowIndex; selectCellAtLocation(selectedColumn(), newSelectedRow); m_selectableTableView.reloadData(); - displayEmptyController(); + displayEmptyControllerIfNeeded(); return true; } - return NestedMenuController::handleEvent(event); + return AlternateEmptyNestedMenuController::handleEvent(event); } -int VariableBoxController::numberOfRows() const { +int MathVariableBoxController::numberOfRows() const { switch (m_currentPage) { case Page::RootMenu: return k_numberOfMenuRows; @@ -85,7 +81,7 @@ int VariableBoxController::numberOfRows() const { } } -int VariableBoxController::reusableCellCount(int type) { +int MathVariableBoxController::reusableCellCount(int type) { assert(type < 2); if (type == 0) { return k_maxNumberOfDisplayedRows; @@ -93,7 +89,7 @@ int VariableBoxController::reusableCellCount(int type) { return k_numberOfMenuRows; } -void VariableBoxController::willDisplayCellForIndex(HighlightCell * cell, int index) { +void MathVariableBoxController::willDisplayCellForIndex(HighlightCell * cell, int index) { if (m_currentPage == Page::RootMenu) { I18n::Message label = nodeLabelAtIndex(index); MessageTableCell * myCell = (MessageTableCell *)cell; @@ -123,64 +119,64 @@ void VariableBoxController::willDisplayCellForIndex(HighlightCell * cell, int in myCell->reloadCell(); } -KDCoordinate VariableBoxController::rowHeight(int index) { +KDCoordinate MathVariableBoxController::rowHeight(int index) { if (m_currentPage != Page::RootMenu) { Layout layoutR = expressionLayoutForRecord(recordAtIndex(index), index); if (!layoutR.isUninitialized()) { return std::max(layoutR.layoutSize().height()+k_leafMargin, Metric::ToolboxRowHeight); } } - return NestedMenuController::rowHeight(index); + return AlternateEmptyNestedMenuController::rowHeight(index); } -int VariableBoxController::typeAtLocation(int i, int j) { +int MathVariableBoxController::typeAtLocation(int i, int j) { if (m_currentPage == Page::RootMenu) { return 1; } return 0; } -ExpressionTableCellWithExpression * VariableBoxController::leafCellAtIndex(int index) { +ExpressionTableCellWithExpression * MathVariableBoxController::leafCellAtIndex(int index) { assert(index >= 0 && index < k_maxNumberOfDisplayedRows); return &m_leafCells[index]; } -MessageTableCellWithChevron * VariableBoxController::nodeCellAtIndex(int index) { +MessageTableCellWithChevron * MathVariableBoxController::nodeCellAtIndex(int index) { assert(index >= 0 && index < k_numberOfMenuRows); return &m_nodeCells[index]; } -VariableBoxController::Page VariableBoxController::pageAtIndex(int index) { +MathVariableBoxController::Page MathVariableBoxController::pageAtIndex(int index) { Page pages[2] = {Page::Expression, Page::Function}; return pages[index]; } -void VariableBoxController::setPage(Page page) { +void MathVariableBoxController::setPage(Page page) { m_currentPage = page; resetMemoization(); } -bool VariableBoxController::selectSubMenu(int selectedRow) { +bool MathVariableBoxController::selectSubMenu(int selectedRow) { m_selectableTableView.deselectTable(); setPage(pageAtIndex(selectedRow)); - bool selectSubMenu = NestedMenuController::selectSubMenu(selectedRow); - if (displayEmptyController()) { + bool selectSubMenu = AlternateEmptyNestedMenuController::selectSubMenu(selectedRow); + if (displayEmptyControllerIfNeeded()) { return true; } return selectSubMenu; } -bool VariableBoxController::returnToPreviousMenu() { +bool MathVariableBoxController::returnToPreviousMenu() { if (isDisplayingEmptyController()) { pop(); } else { m_selectableTableView.deselectTable(); } setPage(Page::RootMenu); - return NestedMenuController::returnToPreviousMenu(); + return AlternateEmptyNestedMenuController::returnToPreviousMenu(); } -bool VariableBoxController::selectLeaf(int selectedRow) { +bool MathVariableBoxController::selectLeaf(int selectedRow) { if (isDisplayingEmptyController()) { /* We do not want to handle OK/EXE events in that case. */ return false; @@ -214,13 +210,13 @@ bool VariableBoxController::selectLeaf(int selectedRow) { return true; } -I18n::Message VariableBoxController::nodeLabelAtIndex(int index) { +I18n::Message MathVariableBoxController::nodeLabelAtIndex(int index) { assert(m_currentPage == Page::RootMenu); I18n::Message labels[2] = {I18n::Message::Expressions, I18n::Message::Functions}; return labels[index]; } -Layout VariableBoxController::expressionLayoutForRecord(Storage::Record record, int index) { +Layout MathVariableBoxController::expressionLayoutForRecord(Storage::Record record, int index) { assert(m_currentPage != Page::RootMenu); assert(index >= 0); if (index >= m_firstMemoizedLayoutIndex+k_maxNumberOfDisplayedRows || index < m_firstMemoizedLayoutIndex) { @@ -250,36 +246,30 @@ Layout VariableBoxController::expressionLayoutForRecord(Storage::Record record, return m_layouts[index-m_firstMemoizedLayoutIndex]; } -const char * VariableBoxController::extension() const { +const char * MathVariableBoxController::extension() const { assert(m_currentPage != Page::RootMenu); return m_currentPage == Page::Function ? Ion::Storage::funcExtension : Ion::Storage::expExtension; } -Storage::Record VariableBoxController::recordAtIndex(int rowIndex) { +Storage::Record MathVariableBoxController::recordAtIndex(int rowIndex) { assert(m_currentPage != Page::RootMenu); assert(!Storage::sharedStorage()->recordWithExtensionAtIndex(extension(), rowIndex).isNull()); return Storage::sharedStorage()->recordWithExtensionAtIndex(extension(), rowIndex); } -bool VariableBoxController::displayEmptyController() { - assert(!isDisplayingEmptyController()); - // If the content is empty, we push above an empty controller. - if (numberOfRows() == 0) { - m_emptyViewController.setType((VariableBoxEmptyController::Type)m_currentPage); - push(&m_emptyViewController); - return true; - } - return false; +ViewController * MathVariableBoxController::emptyViewController() { + m_emptyViewController.setType((MathVariableBoxEmptyController::Type)m_currentPage); + return &m_emptyViewController; } -void VariableBoxController::resetMemoization() { +void MathVariableBoxController::resetMemoization() { for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) { m_layouts[i] = Layout(); } m_firstMemoizedLayoutIndex = 0; } -void VariableBoxController::destroyRecordAtRowIndex(int rowIndex) { +void MathVariableBoxController::destroyRecordAtRowIndex(int rowIndex) { // Destroy the record recordAtIndex(rowIndex).destroy(); // Shift the memoization if needed diff --git a/apps/variable_box_controller.h b/apps/math_variable_box_controller.h similarity index 82% rename from apps/variable_box_controller.h rename to apps/math_variable_box_controller.h index 95824e29d..8e6052a11 100644 --- a/apps/variable_box_controller.h +++ b/apps/math_variable_box_controller.h @@ -1,15 +1,13 @@ -#ifndef APPS_VARIABLE_BOX_CONTROLLER_H -#define APPS_VARIABLE_BOX_CONTROLLER_H +#ifndef APPS_MATH_VARIABLE_BOX_CONTROLLER_H +#define APPS_MATH_VARIABLE_BOX_CONTROLLER_H -#define MATRIX_VARIABLES 1 - -#include -#include "variable_box_empty_controller.h" +#include "alternate_empty_nested_menu_controller.h" +#include "math_variable_box_empty_controller.h" #include -class VariableBoxController : public NestedMenuController { +class MathVariableBoxController : public AlternateEmptyNestedMenuController { public: - VariableBoxController(); + MathVariableBoxController(); // View Controller void viewWillAppear() override; @@ -32,6 +30,7 @@ public: Function = 2 }; void lockDeleteEvent(Page page) { m_lockPageDelete = page; } + private: constexpr static int k_maxNumberOfDisplayedRows = (Ion::Display::Height - Metric::TitleBarHeight - Metric::PopUpTopMargin - Metric::StackTitleHeight) / Metric::ToolboxRowHeight + 2; // (240 - 18 - 50 - 20) / 40 = 3.8; the 0.8 cell can be above and below so we add +2 to get 5 constexpr static int k_numberOfMenuRows = 2; @@ -47,15 +46,14 @@ private: Poincare::Layout expressionLayoutForRecord(Ion::Storage::Record record, int index); const char * extension() const; Ion::Storage::Record recordAtIndex(int rowIndex); - bool displayEmptyController(); - bool isDisplayingEmptyController() { return StackViewController::depth() == 2; } + ViewController * emptyViewController() override; void resetMemoization(); void destroyRecordAtRowIndex(int rowIndex); Page m_currentPage; Page m_lockPageDelete; ExpressionTableCellWithExpression m_leafCells[k_maxNumberOfDisplayedRows]; MessageTableCellWithChevron m_nodeCells[k_numberOfMenuRows]; - VariableBoxEmptyController m_emptyViewController; + MathVariableBoxEmptyController m_emptyViewController; // Layout memoization // TODO: make a helper doing the RingMemoizationOfConsecutiveObjets to factorize this code and ExpressionModelStore code int m_firstMemoizedLayoutIndex; diff --git a/apps/math_variable_box_empty_controller.cpp b/apps/math_variable_box_empty_controller.cpp new file mode 100644 index 000000000..f5ad187f7 --- /dev/null +++ b/apps/math_variable_box_empty_controller.cpp @@ -0,0 +1,53 @@ +#include "math_variable_box_empty_controller.h" +#include +#include +#include + +MathVariableBoxEmptyController::MathVariableBoxEmptyView::MathVariableBoxEmptyView() : + ModalViewEmptyView(), + m_layoutExample(0.5f, 0.5f, KDColorBlack, Palette::WallScreen) +{ + initMessageViews(); +} + +void MathVariableBoxEmptyController::MathVariableBoxEmptyView::setLayout(Poincare::Layout layout) { + m_layoutExample.setLayout(layout); +} + +void MathVariableBoxEmptyController::viewDidDisappear() { + m_view.setLayout(Poincare::Layout()); +} + +void MathVariableBoxEmptyController::setType(Type type) { + I18n::Message messages[MathVariableBoxEmptyView::k_numberOfMessages] = { + I18n::Message::Default, + I18n::Message::Default, + I18n::Message::Default, + I18n::Message::EnableCharacters + }; + Poincare::Layout layout; + switch (type) { + case Type::Expressions: + { + messages[0] = I18n::Message::EmptyExpressionBox0; + messages[1] = I18n::Message::EmptyExpressionBox1; + messages[2] = I18n::Message::EmptyExpressionBox2; + const char * storeExpression = "3→A"; + layout = Poincare::LayoutHelper::String(storeExpression, strlen(storeExpression), MathVariableBoxEmptyView::k_font); + break; + } + case Type::Functions: + { + messages[0] = I18n::Message::EmptyFunctionBox0; + messages[1] = I18n::Message::EmptyFunctionBox1; + messages[2] = I18n::Message::EmptyFunctionBox2; + const char * storeFunction = "3+x→f(x)"; + layout = Poincare::LayoutHelper::String(storeFunction, strlen(storeFunction), MathVariableBoxEmptyView::k_font); + break; + } + default: + assert(false); + } + m_view.setMessages(messages); + m_view.setLayout(layout); +} diff --git a/apps/math_variable_box_empty_controller.h b/apps/math_variable_box_empty_controller.h new file mode 100644 index 000000000..120ff42b6 --- /dev/null +++ b/apps/math_variable_box_empty_controller.h @@ -0,0 +1,41 @@ +#ifndef APPS_MATH_VARIABLE_BOX_EMPTY_CONTROLLER_H +#define APPS_MATH_VARIABLE_BOX_EMPTY_CONTROLLER_H + +#include +#include + +class MathVariableBoxEmptyController : public ModalViewEmptyController { +public: + MathVariableBoxEmptyController() : + ModalViewEmptyController(), + m_view() + {} + enum class Type { + None = 0, + Expressions = 1, + Functions = 2 + }; + void setType(Type type); + // View Controller + View * view() override { return &m_view; } + void viewDidDisappear() override; +private: + class MathVariableBoxEmptyView : public ModalViewEmptyController::ModalViewEmptyView { + public: + constexpr static int k_numberOfMessages = 4; + MathVariableBoxEmptyView(); + void setLayout(Poincare::Layout layout); + private: + int numberOfMessageTextViews() const override { return k_numberOfMessages; } + MessageTextView * messageTextViewAtIndex(int index) override { + assert(index >= 0 && index < k_numberOfMessages); + return &m_messages[index]; + } + ExpressionView * expressionView() override { return &m_layoutExample; } + MessageTextView m_messages[k_numberOfMessages]; + ExpressionView m_layoutExample; + }; + MathVariableBoxEmptyView m_view; +}; + +#endif diff --git a/apps/on_boarding/Makefile b/apps/on_boarding/Makefile index 52566e6cc..42d22f508 100644 --- a/apps/on_boarding/Makefile +++ b/apps/on_boarding/Makefile @@ -9,13 +9,6 @@ app_on_boarding_src = $(addprefix apps/on_boarding/,\ apps_src += $(app_on_boarding_src) -i18n_files += $(addprefix apps/on_boarding/,\ - base.de.i18n\ - base.en.i18n\ - base.es.i18n\ - base.fr.i18n\ - base.pt.i18n\ - base.hu.i18n\ -) +i18n_files += $(call i18n_without_universal_for,on_boarding/base) $(eval $(call depends_on_image,apps/on_boarding/logo_view.cpp,apps/on_boarding/logo_icon.png)) diff --git a/apps/on_boarding/base.it.i18n b/apps/on_boarding/base.it.i18n new file mode 100644 index 000000000..33ff2bd3c --- /dev/null +++ b/apps/on_boarding/base.it.i18n @@ -0,0 +1,13 @@ +UpdateAvailable = "AGGIORNAMENTO DISPONIBILE" +UpdateMessage1 = "Esistono miglioramenti significativi" +UpdateMessage2 = "per la vostra calcolatrice." +UpdateMessage3 = "Connettetevi dal vostro computer" +UpdateMessage4 = "www.numworks.com/update" +BetaVersion = "VERSIONE BETA" +BetaVersionMessage1 = "Il vostro dispositivo dispone" +BetaVersionMessage2 = "di una versione beta del software." +BetaVersionMessage3 = "Possono comparire alcuni bugs." +BetaVersionMessage4 = "Per comunicarci un riscontro" +BetaVersionMessage5 = "potete scriverci a" +BetaVersionMessage6 = "contact@numworks.com" +Skip = "Saltare" diff --git a/apps/on_boarding/base.nl.i18n b/apps/on_boarding/base.nl.i18n new file mode 100644 index 000000000..7d9ce05b7 --- /dev/null +++ b/apps/on_boarding/base.nl.i18n @@ -0,0 +1,13 @@ +UpdateAvailable = "UPDATE BESCHIKBAAR" +UpdateMessage1 = "Er zijn belangrijke updates" +UpdateMessage2 = "voor je rekenmachine." +UpdateMessage3 = "Ga vanaf je computer naar onze pagina" +UpdateMessage4 = "www.numworks.com/update" +BetaVersion = "BÈTAVERSIE" +BetaVersionMessage1 = "" +BetaVersionMessage2 = "Je apparaat draait op een bèta-software." +BetaVersionMessage3 = "Je komt mogelijk bugs of glitches tegen." +BetaVersionMessage4 = "" +BetaVersionMessage5 = "Feedback kun je sturen naar" +BetaVersionMessage6 = "contact@numworks.nl" +Skip = "Verdergaan" diff --git a/apps/on_boarding/base.pt.i18n b/apps/on_boarding/base.pt.i18n index db822f4b6..bd16512a0 100644 --- a/apps/on_boarding/base.pt.i18n +++ b/apps/on_boarding/base.pt.i18n @@ -1,13 +1,13 @@ UpdateAvailable = "ATUALIZAÇÃO DISPONÍVEL" UpdateMessage1 = "Existem melhorias significativas" UpdateMessage2 = "para a sua calculadora." -UpdateMessage3 = "Navegue na nossa página do seu computador" +UpdateMessage3 = "Navegue na nossa página no seu computador" UpdateMessage4 = "www.numworks.com/update" BetaVersion = "BETA VERSION" -BetaVersionMessage1 = "" -BetaVersionMessage2 = "Your device runs a beta software." -BetaVersionMessage3 = "You might run into bugs or glitches." +BetaVersionMessage1 = "O seu dispositivo está a executar" +BetaVersionMessage2 = "um software beta." +BetaVersionMessage3 = "Pode encontrar bugs ou falhas." BetaVersionMessage4 = "" -BetaVersionMessage5 = "Please send any feedback to" +BetaVersionMessage5 = "Por favor envie-nos o seu feedback para" BetaVersionMessage6 = "contact@numworks.com" -Skip = "Pular" +Skip = "Saltar" diff --git a/apps/on_boarding/language_controller.cpp b/apps/on_boarding/language_controller.cpp index 3319b507c..d4a3ae679 100644 --- a/apps/on_boarding/language_controller.cpp +++ b/apps/on_boarding/language_controller.cpp @@ -1,12 +1,20 @@ #include "language_controller.h" #include "../global_preferences.h" #include "../apps_container.h" +#include +#include namespace OnBoarding { LanguageController::LanguageController(Responder * parentResponder) : - Shared::LanguageController(parentResponder, (Ion::Display::Height - I18n::NumberOfLanguages*Metric::ParameterCellHeight)/2) + Shared::LanguageController( + parentResponder, + std::max(static_cast(Metric::CommonLeftMargin), + (Ion::Display::Height - I18n::NumberOfLanguages*Metric::ParameterCellHeight)/2)) { + static_cast(m_selectableTableView.decorator()->indicatorAtIndex(1))->setMargin( + std::max(static_cast(Metric::CommonLeftMargin), + (Ion::Display::Height - I18n::NumberOfLanguages*Metric::ParameterCellHeight)/2)); } bool LanguageController::handleEvent(Ion::Events::Event event) { diff --git a/apps/probability/Makefile b/apps/probability/Makefile index aa28babed..d22f88d48 100644 --- a/apps/probability/Makefile +++ b/apps/probability/Makefile @@ -40,15 +40,7 @@ app_probability_src = $(addprefix apps/probability/,\ app_probability_src += $(app_probability_test_src) apps_src += $(app_probability_src) -i18n_files += $(addprefix apps/probability/,\ - base.de.i18n\ - base.en.i18n\ - base.es.i18n\ - base.fr.i18n\ - base.pt.i18n\ - base.hu.i18n\ - base.universal.i18n\ -) +i18n_files += $(call i18n_with_universal_for,probability/base) tests_src += $(addprefix apps/probability/test/,\ hypergeometric_function.cpp\ diff --git a/apps/probability/app.cpp b/apps/probability/app.cpp index 5387997ee..283b5a5d5 100644 --- a/apps/probability/app.cpp +++ b/apps/probability/app.cpp @@ -28,14 +28,11 @@ App::Snapshot::Snapshot() : m_calculation{}, m_activePage(Page::Distribution) { - new(m_distribution) BinomialDistribution(); - new(m_calculation) LeftIntegralCalculation(); - calculation()->setDistribution(distribution()); + initializeDistributionAndCalculation(); } App::Snapshot::~Snapshot() { - distribution()->~Distribution(); - calculation()->~Calculation(); + deleteDistributionAndCalculation(); } App * App::Snapshot::unpack(Container * container) { @@ -48,10 +45,8 @@ App::Descriptor * App::Snapshot::descriptor() { } void App::Snapshot::reset() { - distribution()->~Distribution(); - new(m_distribution) BinomialDistribution(); - calculation()->~Calculation(); - new(m_calculation) LeftIntegralCalculation(); + deleteDistributionAndCalculation(); + initializeDistributionAndCalculation(); m_activePage = Page::Distribution; } @@ -63,6 +58,16 @@ Calculation * App::Snapshot::calculation() { return (Calculation *)m_calculation; } +void App::Snapshot::deleteDistributionAndCalculation() { + distribution()->~Distribution(); + calculation()->~Calculation(); +} + +void App::Snapshot::initializeDistributionAndCalculation() { + new(m_distribution) BinomialDistribution(); + new(m_calculation) LeftIntegralCalculation(distribution()); +} + void App::Snapshot::setActivePage(Page activePage) { m_activePage = activePage; } diff --git a/apps/probability/app.h b/apps/probability/app.h index fcae7f321..d669d1254 100644 --- a/apps/probability/app.h +++ b/apps/probability/app.h @@ -46,9 +46,14 @@ public: Calculation * calculation(); Page activePage(); void setActivePage(Page activePage); + private: constexpr static int k_distributionSizes[] = {sizeof(BinomialDistribution),sizeof(ExponentialDistribution), sizeof(NormalDistribution), sizeof(PoissonDistribution), sizeof(UniformDistribution), 0}; constexpr static size_t k_distributionSize = max(k_distributionSizes); + + void deleteDistributionAndCalculation(); + void initializeDistributionAndCalculation(); + char m_distribution[k_distributionSize]; constexpr static int k_calculationSizes[] = {sizeof(LeftIntegralCalculation),sizeof(FiniteIntegralCalculation), sizeof(RightIntegralCalculation), 0}; constexpr static size_t k_calculationSize = max(k_calculationSizes); diff --git a/apps/probability/base.it.i18n b/apps/probability/base.it.i18n new file mode 100644 index 000000000..443229b53 --- /dev/null +++ b/apps/probability/base.it.i18n @@ -0,0 +1,27 @@ +ProbaApp = "Probabilità" +ProbaAppCapital = "PROBABILITA" +ChooseDistribution = "Scegliere il tipo di distribuzione" +Binomial = "Binomiale" +Geometric = "Geometrica" +Uniforme = "Uniforme" +Normal = "Normale" +ChiSquared = "Chi2" +UniformDistribution = "Distribuzione uniforme" +ExponentialDistribution = "Distribuzione esponenziale" +GeometricDistribution = "Distribuzione geometrica" +PoissonDistribution = "Distribuzione di Poisson" +ChiSquaredDistribution = "Distribuzione chi2" +StudentDistribution = "Distribuzione Student" +FisherDistribution = "Distribuzione di Fisher" +ChooseParameters = "Scegliere i parametri" +RepetitionNumber = "n : Numero di prove" +SuccessProbability = "p : Probabilità di successo" +IntervalDefinition = "[a,b] : Intervallo" +LambdaExponentialDefinition = "λ : Parametro" +MeanDefinition = "μ : Media" +DeviationDefinition = "σ : Deviazione standard" +LambdaPoissonDefinition = "λ : Parametro" +DegreesOfFreedomDefinition = "k : Gradi di libertà" +D1FisherDefinition = "d1 : Gradi di libertà del numeratore" +D2FisherDefinition = "d2 : Gradi di libertà del denominatore" +ComputeProbability = "Calcolare le probabilità" diff --git a/apps/probability/base.nl.i18n b/apps/probability/base.nl.i18n new file mode 100644 index 000000000..07ace7455 --- /dev/null +++ b/apps/probability/base.nl.i18n @@ -0,0 +1,27 @@ +ProbaApp = "Kansrekenen" +ProbaAppCapital = "KANSREKENEN" +ChooseDistribution = "Kies de kansverdeling" +Binomial = "Binomiaal" +Geometric = "Geometrisch" +Uniforme = "Uniform" +Normal = "Normaal" +ChiSquared = "Chi-kwadraat" +UniformDistribution = "Uniforme verdeling" +ExponentialDistribution = "Exponentiële verdeling" +GeometricDistribution = "Geometrische verdeling" +PoissonDistribution = "Poissonverdeling" +ChiSquaredDistribution = "Chi-kwadraatverdeling" +StudentDistribution = "Studentverdeling" +FisherDistribution = "F-verdeling" +ChooseParameters = "Bepaal de parameters" +RepetitionNumber = "n: Aantal proeven" +SuccessProbability = "p: Kans op succes" +IntervalDefinition = "[a,b]: Interval" +LambdaExponentialDefinition = "λ: Snelheidsparameter" +MeanDefinition = "μ: Gemiddelde" +DeviationDefinition = "σ: Standaardafwijking" +LambdaPoissonDefinition = "λ: Parameter" +DegreesOfFreedomDefinition = "k: Vrijheidsgraden" +D1FisherDefinition = "d1: Vrijheidsgraden in de teller" +D2FisherDefinition = "d2: Vrijheidsgraden in de noemer" +ComputeProbability = "Bereken kansen" diff --git a/apps/probability/base.pt.i18n b/apps/probability/base.pt.i18n index 6e9071d74..f37757390 100644 --- a/apps/probability/base.pt.i18n +++ b/apps/probability/base.pt.i18n @@ -1,5 +1,5 @@ -ProbaApp = "Probabilidade" -ProbaAppCapital = "PROBABILIDADE" +ProbaApp = "Probabilidades" +ProbaAppCapital = "PROBABILIDADES" ChooseDistribution = "Selecionar a distribuição" Binomial = "Binomial" Geometric = "Geométrica" diff --git a/apps/probability/calculation/calculation.cpp b/apps/probability/calculation/calculation.cpp index f45f31460..a059a938e 100644 --- a/apps/probability/calculation/calculation.cpp +++ b/apps/probability/calculation/calculation.cpp @@ -4,11 +4,6 @@ namespace Probability { -void Calculation::setDistribution(Distribution * distribution) { - m_distribution = distribution; - compute(0); -} - double Calculation::lowerBound() { return -INFINITY; } diff --git a/apps/probability/calculation/calculation.h b/apps/probability/calculation/calculation.h index 215daece6..f7847dba4 100644 --- a/apps/probability/calculation/calculation.h +++ b/apps/probability/calculation/calculation.h @@ -13,10 +13,11 @@ public: RightIntegral, Discrete, }; - Calculation() : m_distribution(nullptr) {} + Calculation(Distribution * distribution) : m_distribution(distribution) { + assert(distribution != nullptr); + } virtual ~Calculation() = default; virtual Type type() = 0; - void setDistribution(Distribution * distribution); virtual int numberOfParameters() = 0; virtual I18n::Message legendForParameterAtIndex(int index) = 0; virtual void setParameterAtIndex(double f, int index) = 0; diff --git a/apps/probability/calculation/discrete_calculation.cpp b/apps/probability/calculation/discrete_calculation.cpp index 075038512..798281e70 100644 --- a/apps/probability/calculation/discrete_calculation.cpp +++ b/apps/probability/calculation/discrete_calculation.cpp @@ -5,10 +5,9 @@ namespace Probability { -DiscreteCalculation::DiscreteCalculation() : - Calculation(), - m_abscissa(0.0), - m_result(0.0) +DiscreteCalculation::DiscreteCalculation(Distribution * distribution) : + Calculation(distribution), + m_abscissa(distribution->defaultComputedValue()) { compute(0); } diff --git a/apps/probability/calculation/discrete_calculation.h b/apps/probability/calculation/discrete_calculation.h index 6bf1b332a..9ca655e94 100644 --- a/apps/probability/calculation/discrete_calculation.h +++ b/apps/probability/calculation/discrete_calculation.h @@ -7,7 +7,7 @@ namespace Probability { class DiscreteCalculation final : public Calculation { public: - DiscreteCalculation(); + DiscreteCalculation(Distribution * distribution); Type type() override { return Type::Discrete; } int numberOfParameters() override { return 2; } I18n::Message legendForParameterAtIndex(int index) override; diff --git a/apps/probability/calculation/finite_integral_calculation.cpp b/apps/probability/calculation/finite_integral_calculation.cpp index a1ac5d9c8..2341bc15b 100644 --- a/apps/probability/calculation/finite_integral_calculation.cpp +++ b/apps/probability/calculation/finite_integral_calculation.cpp @@ -6,11 +6,10 @@ namespace Probability { -FiniteIntegralCalculation::FiniteIntegralCalculation() : - Calculation(), - m_lowerBound(0.0), - m_upperBound(1.0), - m_result(0.0) +FiniteIntegralCalculation::FiniteIntegralCalculation(Distribution * distribution) : + Calculation(distribution), + m_lowerBound(distribution->defaultComputedValue()), + m_upperBound(m_lowerBound + 1.0) { compute(0); } diff --git a/apps/probability/calculation/finite_integral_calculation.h b/apps/probability/calculation/finite_integral_calculation.h index ccbd2afd0..fbbb867ac 100644 --- a/apps/probability/calculation/finite_integral_calculation.h +++ b/apps/probability/calculation/finite_integral_calculation.h @@ -7,7 +7,7 @@ namespace Probability { class FiniteIntegralCalculation : public Calculation { public: - FiniteIntegralCalculation(); + FiniteIntegralCalculation(Distribution * distribution); Type type() override { return Type::FiniteIntegral; } int numberOfParameters() override { return 3; } I18n::Message legendForParameterAtIndex(int index) override; diff --git a/apps/probability/calculation/left_integral_calculation.cpp b/apps/probability/calculation/left_integral_calculation.cpp index f589ce4e8..d24430b74 100644 --- a/apps/probability/calculation/left_integral_calculation.cpp +++ b/apps/probability/calculation/left_integral_calculation.cpp @@ -5,10 +5,9 @@ namespace Probability { -LeftIntegralCalculation::LeftIntegralCalculation() : - Calculation(), - m_upperBound(0.0), - m_result(0.0) +LeftIntegralCalculation::LeftIntegralCalculation(Distribution * distribution) : + Calculation(distribution), + m_upperBound(distribution->defaultComputedValue()) { compute(0); } diff --git a/apps/probability/calculation/left_integral_calculation.h b/apps/probability/calculation/left_integral_calculation.h index f998cdb12..032a52581 100644 --- a/apps/probability/calculation/left_integral_calculation.h +++ b/apps/probability/calculation/left_integral_calculation.h @@ -7,7 +7,7 @@ namespace Probability { class LeftIntegralCalculation final : public Calculation { public: - LeftIntegralCalculation(); + LeftIntegralCalculation(Distribution * distribution); Type type() override { return Type::LeftIntegral; } int numberOfParameters() override { return 2; } I18n::Message legendForParameterAtIndex(int index) override; diff --git a/apps/probability/calculation/right_integral_calculation.cpp b/apps/probability/calculation/right_integral_calculation.cpp index 08903df47..068fe3552 100644 --- a/apps/probability/calculation/right_integral_calculation.cpp +++ b/apps/probability/calculation/right_integral_calculation.cpp @@ -5,10 +5,9 @@ namespace Probability { -RightIntegralCalculation::RightIntegralCalculation() : - Calculation(), - m_lowerBound(0.0), - m_result(0.0) +RightIntegralCalculation::RightIntegralCalculation(Distribution * distribution) : + Calculation(distribution), + m_lowerBound(distribution->defaultComputedValue()) { compute(0); } diff --git a/apps/probability/calculation/right_integral_calculation.h b/apps/probability/calculation/right_integral_calculation.h index 71f1e5530..9cb8f001d 100644 --- a/apps/probability/calculation/right_integral_calculation.h +++ b/apps/probability/calculation/right_integral_calculation.h @@ -7,7 +7,7 @@ namespace Probability { class RightIntegralCalculation final : public Calculation { public: - RightIntegralCalculation(); + RightIntegralCalculation(Distribution * distribution); Type type() override { return Type::RightIntegral; } int numberOfParameters() override { return 2; } I18n::Message legendForParameterAtIndex(int index) override; diff --git a/apps/probability/calculation_controller.cpp b/apps/probability/calculation_controller.cpp index 5a954fd62..2d4ddd1b6 100644 --- a/apps/probability/calculation_controller.cpp +++ b/apps/probability/calculation_controller.cpp @@ -251,21 +251,20 @@ void CalculationController::setCalculationAccordingToIndex(int index, bool force m_calculation->~Calculation(); switch (index) { case 0: - new(m_calculation) LeftIntegralCalculation(); - break; + new(m_calculation) LeftIntegralCalculation(m_distribution); + return; case 1: - new(m_calculation) FiniteIntegralCalculation(); - break; + new(m_calculation) FiniteIntegralCalculation(m_distribution); + return; case 2: - new(m_calculation) RightIntegralCalculation(); - break; + new(m_calculation) RightIntegralCalculation(m_distribution); + return; case 3: - new(m_calculation) DiscreteCalculation(); - break; + new(m_calculation) DiscreteCalculation(m_distribution); + return; default: return; } - m_calculation->setDistribution(m_distribution); } void CalculationController::updateTitle() { diff --git a/apps/probability/distribution/distribution.h b/apps/probability/distribution/distribution.h index 95575ed43..e7e9d5332 100644 --- a/apps/probability/distribution/distribution.h +++ b/apps/probability/distribution/distribution.h @@ -39,6 +39,7 @@ public: virtual double rightIntegralInverseForProbability(double * probability); virtual double evaluateAtDiscreteAbscissa(int k) const; constexpr static int k_maxNumberOfOperations = 1000000; + virtual double defaultComputedValue() const { return 0.0f; } protected: static_assert(Poincare::Preferences::LargeNumberOfSignificantDigits == 7, "k_maxProbability is ill-defined compared to LargeNumberOfSignificantDigits"); constexpr static double k_maxProbability = 0.9999995; diff --git a/apps/probability/distribution/geometric_distribution.cpp b/apps/probability/distribution/geometric_distribution.cpp index 1ac3559cd..ed64b23ab 100644 --- a/apps/probability/distribution/geometric_distribution.cpp +++ b/apps/probability/distribution/geometric_distribution.cpp @@ -10,13 +10,12 @@ float GeometricDistribution::xMin() const { } float GeometricDistribution::xMax() const { - assert(m_parameter1 != 0); + assert(m_parameter1 != 0.0f); return 5/m_parameter1 * (1.0f + k_displayRightMarginRatio); } float GeometricDistribution::yMax() const { - int maxAbscissa = 0; - float result = evaluateAtAbscissa(maxAbscissa); + float result = evaluateAtAbscissa(1.0); // Tha probability is max for x == 1 return result * (1.0f + k_displayTopMarginRatio); } @@ -29,16 +28,18 @@ bool GeometricDistribution::authorizedValueAtIndex(float x, int index) const { } template -T GeometricDistribution::templatedApproximateAtAbscissa(T x) const { - if (x < 0) { - return NAN; +T GeometricDistribution::templatedApproximateAtAbscissa(T k) const { + constexpr T castedOne = static_cast(1.0); + if (k < castedOne) { + return static_cast(0.0); } - T p = (T)m_parameter1; - if (p == (T)1.0) { - return (T)(x == 0 ? 1.0 : 0.0); + T p = static_cast(m_parameter1); + if (p == castedOne) { + return k == castedOne ? castedOne : static_cast(0.0); } - T lResult = x * std::log(((T)1.0) - p); - return p*std::exp(lResult); + // The result is p * (1-p)^{k-1} + T lResult = (k - castedOne) * std::log(castedOne - p); + return p * std::exp(lResult); } } diff --git a/apps/probability/distribution/geometric_distribution.h b/apps/probability/distribution/geometric_distribution.h index 035d2945e..e3323abfa 100644 --- a/apps/probability/distribution/geometric_distribution.h +++ b/apps/probability/distribution/geometric_distribution.h @@ -7,9 +7,7 @@ namespace Probability { /* We chose the definition: * 0 < p <= 1 for success probability - * k failures where k ∈ {0, 1, 2, ... } - * The distribution follows the probability distribution of the number of failures before - * the first success. */ + * k number of trials needed to get one success, where k ∈ {1, 2, 3, ...}. */ class GeometricDistribution final : public OneParameterDistribution { public: @@ -32,6 +30,7 @@ public: return templatedApproximateAtAbscissa(x); } bool authorizedValueAtIndex(float x, int index) const override; + double defaultComputedValue() const override { return 1.0f; } private: double evaluateAtDiscreteAbscissa(int k) const override { return templatedApproximateAtAbscissa((double)k); diff --git a/apps/probability/test/distributions.cpp b/apps/probability/test/distributions.cpp index 20bd9d9f9..f28c1c9ba 100644 --- a/apps/probability/test/distributions.cpp +++ b/apps/probability/test/distributions.cpp @@ -110,19 +110,19 @@ QUIZ_CASE(geometric_distribution) { // Geometric distribution with probability of success 0.5 Probability::GeometricDistribution distribution; distribution.setParameterAtIndex(0.5, 0); - assert_cumulative_distributive_function_direct_and_inverse_is(&distribution, 2.0, 0.875); - assert_cumulative_distributive_function_direct_and_inverse_is(&distribution, 3.0, 0.9375); + assert_cumulative_distributive_function_direct_and_inverse_is(&distribution, 1.0, 0.5); + assert_cumulative_distributive_function_direct_and_inverse_is(&distribution, 2.0, 0.75); // Geometric distribution with probability of success 0.2 distribution.setParameterAtIndex(0.2, 0); - assert_cumulative_distributive_function_direct_and_inverse_is(&distribution, 7.0, 0.8322278399999998299563230830244719982147216796875); - assert_cumulative_distributive_function_direct_and_inverse_is(&distribution, 3.0, 0.5904); + assert_cumulative_distributive_function_direct_and_inverse_is(&distribution, 6.0, 0.737856); + assert_cumulative_distributive_function_direct_and_inverse_is(&distribution, 2.0, 0.36); // Geometric distribution with probability of success 0.4 distribution.setParameterAtIndex(0.4, 0); - assert_finite_integral_between_abscissas_is(&distribution, 1.0, 1.0, 0.24); + assert_finite_integral_between_abscissas_is(&distribution, 1.0, 1.0, 0.4); assert_finite_integral_between_abscissas_is(&distribution, 2.0, 1.0, 0.0); - assert_finite_integral_between_abscissas_is(&distribution, 1.0, 2.0, 0.384); + assert_finite_integral_between_abscissas_is(&distribution, 2.0, 3.0, 0.384); } QUIZ_CASE(fisher_distribution) { diff --git a/apps/regression/Makefile b/apps/regression/Makefile index d5726035f..2211e3192 100644 --- a/apps/regression/Makefile +++ b/apps/regression/Makefile @@ -40,15 +40,7 @@ app_regression_src = $(addprefix apps/regression/,\ app_regression_src += $(app_regression_test_src) apps_src += $(app_regression_src) -i18n_files += $(addprefix apps/regression/,\ - base.de.i18n\ - base.en.i18n\ - base.es.i18n\ - base.fr.i18n\ - base.pt.i18n\ - base.hu.i18n\ - base.universal.i18n\ -) +i18n_files += $(call i18n_with_universal_for,regression/base) tests_src += $(addprefix apps/regression/test/,\ model.cpp\ diff --git a/apps/regression/base.it.i18n b/apps/regression/base.it.i18n new file mode 100644 index 000000000..06976e196 --- /dev/null +++ b/apps/regression/base.it.i18n @@ -0,0 +1,21 @@ +RegressionApp = "Regressione" +RegressionAppCapital = "REGRESSIONE" +Regression = "Regressione" +Reg = "reg" +MeanDot = "media" +RegressionCurve = "Curva di regressione" +XPrediction = "Previsione data X" +YPrediction = "Previsione data Y" +ValueNotReachedByRegression = "Valore non raggiunto in questa finestra" +NumberOfDots = "Numero di punti" +Covariance = "Covarianza" +Linear = "Lineare" +Proportional = "Proporzionale" +Quadratic = "Quadratica" +Cubic = "Cubica" +Quartic = "Quartica" +Logarithmic = "Logaritmica" +Power = "Potenza" +Trigonometrical = "Trigonometrica" +Logistic = "Logistica" +DataNotSuitableForRegression = "I dati non sono adeguati" diff --git a/apps/regression/base.nl.i18n b/apps/regression/base.nl.i18n new file mode 100644 index 000000000..f5411a0a0 --- /dev/null +++ b/apps/regression/base.nl.i18n @@ -0,0 +1,21 @@ +RegressionApp = "Regressie" +RegressionAppCapital = "REGRESSIE" +Regression = "Regressie" +Reg = "reg" +MeanDot = "gemiddelde" +RegressionCurve = "Regressielijn" +XPrediction = "Voorspelling gegeven X" +YPrediction = "Voorspelling gegeven Y" +ValueNotReachedByRegression = "Waarde niet gevonden in dit venster" +NumberOfDots = "Aantal punten" +Covariance = "Covariantie" +Linear = "Lineair" +Proportional = "Proportioneel" +Quadratic = "Kwadratisch" +Cubic = "Kubiek" +Quartic = "Quartic" +Logarithmic = "Logaritmisch" +Power = "Macht" +Trigonometrical = "Trigonometrisch" +Logistic = "Logistiek" +DataNotSuitableForRegression = " Data niet geschikt voor dit regressiemodel" diff --git a/apps/regression/base.pt.i18n b/apps/regression/base.pt.i18n index 4ff5a3eaf..e5db50f79 100644 --- a/apps/regression/base.pt.i18n +++ b/apps/regression/base.pt.i18n @@ -2,13 +2,13 @@ RegressionApp = "Regressão" RegressionAppCapital = "REGRESSÃO" Regression = "Regressão" Reg = "reg" -MeanDot = "media" +MeanDot = "média" RegressionCurve = "Curva de regressão" XPrediction = "Predição dado X" YPrediction = "Predição dado Y" ValueNotReachedByRegression = "Valor não alcançado nesta janela" NumberOfDots = "Número de pontos" -Covariance = "Covariancia" +Covariance = "Covariância" Linear = "Linear" Proportional = "Proporcional" Quadratic = "Quadrática" diff --git a/apps/regression/model/proportional_model.cpp b/apps/regression/model/proportional_model.cpp index 22f62e5a4..3ae682488 100644 --- a/apps/regression/model/proportional_model.cpp +++ b/apps/regression/model/proportional_model.cpp @@ -27,10 +27,6 @@ double ProportionalModel::levelSet(double * modelCoefficients, double xMin, doub return y/a; } -void ProportionalModel::fit(Store * store, int series, double * modelCoefficients, Poincare::Context * context) { - modelCoefficients[0] = store->slope(series); -} - double ProportionalModel::partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const { assert(derivateCoefficientIndex == 0); // Derivate: x diff --git a/apps/regression/model/proportional_model.h b/apps/regression/model/proportional_model.h index b2eca094f..8c2ff44d3 100644 --- a/apps/regression/model/proportional_model.h +++ b/apps/regression/model/proportional_model.h @@ -12,7 +12,6 @@ public: I18n::Message formulaMessage() const override { return I18n::Message::ProportionalRegressionFormula; } double evaluate(double * modelCoefficients, double x) const override; double levelSet(double * modelCoefficients, double xMin, double step, double xMax, double y, Poincare::Context * context) override; - void fit(Store * store, int series, double * modelCoefficients, Poincare::Context * context) override; double partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const override; int numberOfCoefficients() const override { return 1; } int bannerLinesCount() const override { return 2; } diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index ff07521e7..0abbad61a 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -50,6 +50,14 @@ QUIZ_CASE(proportional_regression) { assert_regression_is(x, y, 5, Model::Type::Proportional, coefficients); } +QUIZ_CASE(proportional_regression2) { + constexpr int numberOfPoints = 4; + double x[numberOfPoints] = {5.0, 2.0, 3.0, 4.0}; + double y[numberOfPoints] = {10.0, 6.0, 7.0, 8.0}; + double coefficients[] = {2.12963963}; + assert_regression_is(x, y, numberOfPoints, Model::Type::Proportional, coefficients); +} + QUIZ_CASE(quadratic_regression) { double x[] = {-34.0, -12.0, 5.0, 86.0, -2.0}; double y[] = {-8241.389, -1194.734, -59.163, - 46245.39, -71.774}; diff --git a/apps/sequence/Makefile b/apps/sequence/Makefile index eacfec20f..f36277bca 100644 --- a/apps/sequence/Makefile +++ b/apps/sequence/Makefile @@ -28,14 +28,7 @@ app_sequence_src = $(addprefix apps/sequence/,\ app_sequence_src += $(app_sequence_test_src) apps_src += $(app_sequence_src) -i18n_files += $(addprefix apps/sequence/,\ - base.de.i18n\ - base.en.i18n\ - base.es.i18n\ - base.fr.i18n\ - base.pt.i18n\ - base.hu.i18n\ -) +i18n_files += $(call i18n_without_universal_for,sequence/base) tests_src += $(addprefix apps/sequence/test/,\ sequence.cpp\ diff --git a/apps/sequence/base.it.i18n b/apps/sequence/base.it.i18n new file mode 100644 index 000000000..d9ddac4a1 --- /dev/null +++ b/apps/sequence/base.it.i18n @@ -0,0 +1,22 @@ +SequenceApp = "Successioni" +SequenceAppCapital = "SUCCESSIONI" +SequenceTab = "Successioni" +AddSequence = "Aggiungi successione" +ChooseSequenceType = "Scegliere il tipo di successione" +SequenceType = "Tipo di successione" +Explicit = "Esplicita" +SingleRecurrence = "Ricorrente d'ordine 1" +DoubleRecurrence = "Ricorrente d'ordine 2" +SequenceOptions = "Opzioni della successione" +SequenceColor = "Colore della successione" +DeleteSequence = "Cancella la successione" +NoSequence = "Nessuna successione" +NoActivatedSequence = "Nessuna successione attiva" +NStart = "N iniziale" +NEnd = "N finale" +TermSum = "Somma dei termini" +SelectFirstTerm = "Selezionare il primo termine " +SelectLastTerm = "Selezionare l'ultimo termine " +ValueNotReachedBySequence = "Valore non raggiunto dalla successione" +NColumn = "Colonna n" +FirstTermIndex = "Indice del primo termine" diff --git a/apps/sequence/base.nl.i18n b/apps/sequence/base.nl.i18n new file mode 100644 index 000000000..507923183 --- /dev/null +++ b/apps/sequence/base.nl.i18n @@ -0,0 +1,22 @@ +SequenceApp = "Rijen" +SequenceAppCapital = "RIJEN" +SequenceTab = "Rijen" +AddSequence = "Rij toevoegen" +ChooseSequenceType = "Kies type rij" +SequenceType = "Rij-type" +Explicit = "Expliciete uitdrukking" +SingleRecurrence = "Recursief eerste orde" +DoubleRecurrence = "Recursief tweede orde" +SequenceOptions = "Rij-opties" +SequenceColor = "Rij-kleur" +DeleteSequence = "Rij verwijderen" +NoSequence = "Geen rij ingevoerd" +NoActivatedSequence = "Geen rij is ingeschakelt" +NStart = "N begin" +NEnd = "N einde" +TermSum = "Som van termen" +SelectFirstTerm = "Selecteer eerste term " +SelectLastTerm = "Selecteer laatste term " +ValueNotReachedBySequence = "Waarde niet bereikt door de rij" +NColumn = "n-kolom" +FirstTermIndex = "Eerste termindex" diff --git a/apps/sequence/base.pt.i18n b/apps/sequence/base.pt.i18n index cb0d9caec..97f6580f8 100644 --- a/apps/sequence/base.pt.i18n +++ b/apps/sequence/base.pt.i18n @@ -1,5 +1,5 @@ -SequenceApp = "Sequência" -SequenceAppCapital = "SEQUÊNCIA" +SequenceApp = "Sequências" +SequenceAppCapital = "SEQUÊNCIAS" SequenceTab = "Sequências" AddSequence = "Adicionar uma sequência" ChooseSequenceType = "Escolha o tipo de sequência" @@ -11,7 +11,7 @@ SequenceOptions = "Opções de sequência" SequenceColor = "Cor da sequência" DeleteSequence = "Eliminar a sequência" NoSequence = "Sem sequência" -NoActivatedSequence = "Sem sequência activada" +NoActivatedSequence = "Sem sequência ativada" NStart = "N início" NEnd = "N fim" TermSum = "Soma dos termos" diff --git a/apps/sequence/list/list_parameter_controller.cpp b/apps/sequence/list/list_parameter_controller.cpp index b40cab658..ebdd85905 100644 --- a/apps/sequence/list/list_parameter_controller.cpp +++ b/apps/sequence/list/list_parameter_controller.cpp @@ -87,7 +87,7 @@ bool ListParameterController::textFieldDidFinishEditing(TextField * textField, c return true; } -void ListParameterController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { +void ListParameterController::tableViewDidChangeSelectionAndDidScroll(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { if (withinTemporarySelection || (previousSelectedCellX == t->selectedColumn() && previousSelectedCellY == t->selectedRow())) { return; } diff --git a/apps/sequence/list/list_parameter_controller.h b/apps/sequence/list/list_parameter_controller.h index 6aaddb713..cc5ddae78 100644 --- a/apps/sequence/list/list_parameter_controller.h +++ b/apps/sequence/list/list_parameter_controller.h @@ -19,7 +19,7 @@ public: bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; + void tableViewDidChangeSelectionAndDidScroll(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; // ListViewDataSource HighlightCell * reusableCell(int index, int type) override; diff --git a/apps/settings/Makefile b/apps/settings/Makefile index af5775fd1..e5580265a 100644 --- a/apps/settings/Makefile +++ b/apps/settings/Makefile @@ -28,13 +28,6 @@ app_settings_src = $(addprefix apps/settings/,\ app_settings_src += $(app_settings_test_src) apps_src += $(app_settings_src) -i18n_files += $(addprefix apps/settings/,\ - base.de.i18n\ - base.en.i18n\ - base.es.i18n\ - base.fr.i18n\ - base.pt.i18n\ - base.hu.i18n\ -) +i18n_files += $(call i18n_without_universal_for,settings/base) $(eval $(call depends_on_image,apps/settings/app.cpp,apps/settings/settings_icon.png)) diff --git a/apps/settings/base.it.i18n b/apps/settings/base.it.i18n new file mode 100644 index 000000000..e21e01eca --- /dev/null +++ b/apps/settings/base.it.i18n @@ -0,0 +1,41 @@ +SettingsApp = "Impostazioni" +SettingsAppCapital = "IMPOSTAZIONI" +AngleUnit = "Misura angolo" +DisplayMode = "Forma risultato" +EditionMode = "Forma scrittura" +EditionLinear = "In linea " +Edition2D = "Naturale " +ComplexFormat = "Forma complessa" +ExamMode = "Modalità Esame" +ExamModeActive = "Riattivare modalità Esame" +ToDeactivateExamMode1 = "Per disattivare la modalità esame," +ToDeactivateExamMode2 = "collegare la calcolatrice a un" +ToDeactivateExamMode3 = "computer o a una presa di corrente." +# --------------------- Please do not edit these messages --------------------- +ExamModeWarning1 = "Attenzione, la conformità della modalità" +ExamModeWarning2 = "esame di questo software non ufficiale" +ExamModeWarning3 = "non è garantita da NumWorks." +AboutWarning1 = "Attenzione, voi utilizzate una versione" +AboutWarning2 = "non ufficiale del software. NumWorks" +AboutWarning3 = "non potrà essere ritenuto responsabile dei" +AboutWarning4 = "problemi che questo potrebbe comportare." +# ----------------------------------------------------------------------------- +About = "Informazioni su" +Degrees = "Gradi " +Gradians = "Gradienti " +Radian = "Radianti " +Decimal = "Decimale " +Scientific = "Scientifico " +Engineering = "Ingegneria " +SignificantFigures = "Cifre significative " +Real = "Reale " +Cartesian = "Algebrico " +Polar = "Esponenziale " +Brightness = "Luminosità" +FontSizes = "Carattere Python" +LargeFont = "Grande " +SmallFont = "Piccolo " +SoftwareVersion = "Versione software" +SerialNumber = "Numero di serie" +UpdatePopUp = "Promemoria aggiornamento" +BetaPopUp = "Promemoria beta" diff --git a/apps/settings/base.nl.i18n b/apps/settings/base.nl.i18n new file mode 100644 index 000000000..9e44db188 --- /dev/null +++ b/apps/settings/base.nl.i18n @@ -0,0 +1,41 @@ +SettingsApp = "Instellingen" +SettingsAppCapital = "INSTELLINGEN" +AngleUnit = "Hoekmaat" +DisplayMode = "Resultaat formaat" +EditionMode = "Schrijfformaat" +EditionLinear = "Lineair " +Edition2D = "Natuurlijk " +ComplexFormat = "Complex formaat" +ExamMode = "Examenstand" +ExamModeActive = "Herstart examenstand" +ToDeactivateExamMode1 = "Om de examenstand te verlaten," +ToDeactivateExamMode2 = "sluit de rekenmachine aan op" +ToDeactivateExamMode3 = "een computer of stopcontact." +# --------------------- Please do not edit these messages - Discuss with Leo (remove this later) --------------------- +ExamModeWarning1 = "Let op: naleving van de examenstand" +ExamModeWarning2 = "op deze onofficiële software wordt" +ExamModeWarning3 = "door NumWorks niet gegarandeerd." +AboutWarning1 = "Let op: je gebruikt een" +AboutWarning2 = "onofficiële softwareversie." +AboutWarning3 = "NumWorks is niet verantwoordelijk" +AboutWarning4 = "voor mogelijke resulterende schade" +# ----------------------------------------------------------------------------- +About = "Over deze rekenmachine" +Degrees = "Graden " +Gradians = "Decimale graden " +Radian = "Radialen " +Decimal = "Decimaal " +Scientific = "Wetenschappelijk " +Engineering = "Engineering " +SignificantFigures = "Significante cijfers " +Real = "Reëel " +Cartesian = "Cartesisch " +Polar = "Polair " +Brightness = "Helderheid" +FontSizes = "Python lettergrootte" +LargeFont = "Groot " +SmallFont = "Klein " +SoftwareVersion = "Softwareversie" +SerialNumber = "Serienummer" +UpdatePopUp = "Update pop-up" +BetaPopUp = "Bèta pop-up" diff --git a/apps/settings/base.pt.i18n b/apps/settings/base.pt.i18n index 0efe7863d..676d1b197 100644 --- a/apps/settings/base.pt.i18n +++ b/apps/settings/base.pt.i18n @@ -1,24 +1,24 @@ -SettingsApp = "Configuração" -SettingsAppCapital = "CONFIGURAÇÃO" -AngleUnit = "Valor do angulo" +SettingsApp = "Definições" +SettingsAppCapital = "DEFINIÇÕES" +AngleUnit = "Valor do ângulo" DisplayMode = "Formato numérico" EditionMode = "Formato escrita " EditionLinear = "Em linha " Edition2D = "Natural " ComplexFormat = "Complexos" ExamMode = "Modo de exame" -ExamModeActive = "Reactivar o modo de exame" -ToDeactivateExamMode1 = "Para desactivar o modo de exame," +ExamModeActive = "Reativar o modo de exame" +ToDeactivateExamMode1 = "Para desativar o modo de exame," ToDeactivateExamMode2 = "ligue a calculadora a um computador" -ToDeactivateExamMode3 = "ou a uma tomada eléctrica." +ToDeactivateExamMode3 = "ou a uma tomada elétrica." # --------------------- Please do not edit these messages --------------------- -ExamModeWarning1 = "Caution: compliance of this" -ExamModeWarning2 = "unofficial software's exam mode" -ExamModeWarning3 = "is not guaranteed by NumWorks." -AboutWarning1 = "Caution: you're using an" -AboutWarning2 = "unofficial software version." -AboutWarning3 = "NumWorks can't be held responsible" -AboutWarning4 = "for any resulting damage." +ExamModeWarning1 = "Cuidado: o software que está a utilizar" +ExamModeWarning2 = "não é oficial. A sua conformidade com o" +ExamModeWarning3 = "Modo de Exame não é garantida pela NumWorks." +AboutWarning1 = "Cuidado: está a usar uma" +AboutWarning2 = "versão não oficial do software." +AboutWarning3 = "A NumWorks não pode ser responsável" +AboutWarning4 = "por qualquer dano resultante." # ----------------------------------------------------------------------------- About = "Acerca" Degrees = "Graus " @@ -27,7 +27,7 @@ Radian = "Radianos " Decimal = "Decimal " Scientific = "Científico " Engineering = "Engenharia " -SignificantFigures = "Algarismo significativo " +SignificantFigures = "Alg. significativos " Real = "Real " Cartesian = "Cartesiana " Polar = "Polar " diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index 5dc14ffcb..fb3d33bbc 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -31,7 +31,7 @@ MainController::MainController(Responder * parentResponder, InputEventHandlerDel m_popUpCell(I18n::Message::Default, KDFont::LargeFont), m_selectableTableView(this), m_mathOptionsController(this, inputEventHandlerDelegate), - m_languageController(this, 13), + m_languageController(this, Metric::CommonTopMargin), m_accessibilityController(this), m_examModeController(this), m_aboutController(this), @@ -62,8 +62,8 @@ bool MainController::handleEvent(Ion::Events::Event event) { m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), 1); return true; } - if (model()->children(selectedRow())->numberOfChildren() == 0) { - if (model()->children(selectedRow())->label() == promptMessage()) { + if (model()->childAtIndex(selectedRow())->numberOfChildren() == 0) { + if (model()->childAtIndex(selectedRow())->label() == promptMessage()) { if (event == Ion::Events::OK || event == Ion::Events::EXE) { globalPreferences->setShowPopUp(!globalPreferences->showPopUp()); m_selectableTableView.reloadCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); @@ -71,7 +71,7 @@ bool MainController::handleEvent(Ion::Events::Event event) { } return false; } - if (model()->children(selectedRow())->label() == I18n::Message::Brightness) { + if (model()->childAtIndex(selectedRow())->label() == I18n::Message::Brightness) { if (event == Ion::Events::Right || event == Ion::Events::Left || event == Ion::Events::Plus || event == Ion::Events::Minus) { int delta = Ion::Backlight::MaxBrightness/GlobalPreferences::NumberOfBrightnessStates; int direction = (event == Ion::Events::Right || event == Ion::Events::Plus) ? delta : -delta; @@ -81,7 +81,7 @@ bool MainController::handleEvent(Ion::Events::Event event) { } return false; } - if (model()->children(selectedRow())->label() == I18n::Message::Language) { + if (model()->childAtIndex(selectedRow())->label() == I18n::Message::Language) { if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { stackController()->push(&m_languageController); return true; @@ -91,7 +91,7 @@ bool MainController::handleEvent(Ion::Events::Event event) { } if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { GenericSubController * subController = nullptr; - I18n::Message title = model()->children(selectedRow())->label(); + I18n::Message title = model()->childAtIndex(selectedRow())->label(); if (title == I18n::Message::Brightness || title == I18n::Message::Language) { assert(false); } else if (title == I18n::Message::ExamMode) { @@ -105,7 +105,7 @@ bool MainController::handleEvent(Ion::Events::Event event) { } else { subController = &m_preferencesController; } - subController->setMessageTreeModel(model()->children(selectedRow())); + subController->setMessageTreeModel(model()->childAtIndex(selectedRow())); StackViewController * stack = stackController(); stack->push(subController); return true; @@ -118,7 +118,7 @@ int MainController::numberOfRows() const { }; KDCoordinate MainController::rowHeight(int j) { - if (model()->children(j)->label() == I18n::Message::Brightness) { + if (model()->childAtIndex(j)->label() == I18n::Message::Brightness) { return Metric::ParameterCellHeight + CellWithSeparator::k_margin; } return Metric::ParameterCellHeight; @@ -161,10 +161,10 @@ int MainController::reusableCellCount(int type) { } int MainController::typeAtLocation(int i, int j) { - if (model()->children(j)->label() == I18n::Message::Brightness) { + if (model()->childAtIndex(j)->label() == I18n::Message::Brightness) { return 1; } - if (model()->children(j)->label() == I18n::Message::UpdatePopUp || model()->children(j)->label() == I18n::Message::BetaPopUp) { + if (model()->childAtIndex(j)->label() == I18n::Message::UpdatePopUp || model()->childAtIndex(j)->label() == I18n::Message::BetaPopUp) { return 2; } return 0; @@ -172,8 +172,8 @@ int MainController::typeAtLocation(int i, int j) { void MainController::willDisplayCellForIndex(HighlightCell * cell, int index) { GlobalPreferences * globalPreferences = GlobalPreferences::sharedGlobalPreferences(); - I18n::Message title = model()->children(index)->label(); - if (model()->children(index)->label() == I18n::Message::Brightness) { + I18n::Message title = model()->childAtIndex(index)->label(); + if (model()->childAtIndex(index)->label() == I18n::Message::Brightness) { MessageTableCellWithGaugeWithSeparator * myGaugeCell = (MessageTableCellWithGaugeWithSeparator *)cell; myGaugeCell->setMessage(title); GaugeView * myGauge = (GaugeView *)myGaugeCell->accessoryView(); @@ -182,12 +182,12 @@ void MainController::willDisplayCellForIndex(HighlightCell * cell, int index) { } MessageTableCell * myCell = (MessageTableCell *)cell; myCell->setMessage(title); - if (model()->children(index)->label() == I18n::Message::Language) { + if (model()->childAtIndex(index)->label() == I18n::Message::Language) { int index = (int)(globalPreferences->language()); static_cast(cell)->setSubtitle(I18n::LanguageNames[index]); return; } - if (model()->children(index)->label() == I18n::Message::UpdatePopUp || model()->children(index)->label() == I18n::Message::BetaPopUp) { + if (model()->childAtIndex(index)->label() == I18n::Message::UpdatePopUp || model()->childAtIndex(index)->label() == I18n::Message::BetaPopUp) { MessageTableCellWithSwitch * mySwitchCell = (MessageTableCellWithSwitch *)cell; SwitchView * mySwitch = (SwitchView *)mySwitchCell->accessoryView(); mySwitch->setState(globalPreferences->showPopUp()); @@ -195,14 +195,14 @@ void MainController::willDisplayCellForIndex(HighlightCell * cell, int index) { } MessageTableCellWithChevronAndMessage * myTextCell = (MessageTableCellWithChevronAndMessage *)cell; int childIndex = -1; - switch (model()->children(index)->label()) { + switch (model()->childAtIndex(index)->label()) { case I18n::Message::FontSizes: childIndex = GlobalPreferences::sharedGlobalPreferences()->font() == KDFont::LargeFont ? 0 : 1; break; default: break; } - I18n::Message message = childIndex >= 0 ? model()->children(index)->children(childIndex)->label() : I18n::Message::Default; + I18n::Message message = childIndex >= 0 ? model()->childAtIndex(index)->childAtIndex(childIndex)->label() : I18n::Message::Default; myTextCell->setSubtitle(message); } diff --git a/apps/settings/sub_menu/about_controller.cpp b/apps/settings/sub_menu/about_controller.cpp index f816bab68..fe64d7453 100644 --- a/apps/settings/sub_menu/about_controller.cpp +++ b/apps/settings/sub_menu/about_controller.cpp @@ -25,7 +25,7 @@ AboutController::AboutController(Responder * parentResponder) : } bool AboutController::handleEvent(Ion::Events::Event event) { - I18n::Message childLabel = m_messageTreeModel->children(selectedRow())->label(); + I18n::Message childLabel = m_messageTreeModel->childAtIndex(selectedRow())->label(); /* We hide here the activation hardware test app: in the menu "about", by * clicking on '6' on the last row. */ if ((event == Ion::Events::Six || event == Ion::Events::LowerT || event == Ion::Events::UpperT) && childLabel == I18n::Message::FccId) { @@ -35,7 +35,7 @@ bool AboutController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { if (childLabel == I18n::Message::Contributors) { GenericSubController * subController = &m_contributorsController; - subController->setMessageTreeModel(m_messageTreeModel->children(selectedRow())); + subController->setMessageTreeModel(m_messageTreeModel->childAtIndex(selectedRow())); StackViewController * stack = stackController(); stack->push(subController); return true; @@ -121,10 +121,10 @@ int AboutController::reusableCellCount(int type) { void AboutController::willDisplayCellForIndex(HighlightCell * cell, int index) { GenericSubController::willDisplayCellForIndex(cell, index); assert(index >= 0 && index < k_totalNumberOfCell); - if (m_messageTreeModel->children(index)->label() == I18n::Message::Contributors) { + if (m_messageTreeModel->childAtIndex(index)->label() == I18n::Message::Contributors) { MessageTableCellWithChevronAndMessage * myTextCell = (MessageTableCellWithChevronAndMessage *)cell; myTextCell->setSubtitle(I18n::Message::Default); - } else if (m_messageTreeModel->children(index)->label() == I18n::Message::MemUse) { + } else if (m_messageTreeModel->childAtIndex(index)->label() == I18n::Message::MemUse) { char memUseBuffer[15]; int len = Poincare::Integer((int)((float) (Ion::Storage::k_storageSize - Ion::Storage::sharedStorage()->availableSize()) / 1024.f)).serialize(memUseBuffer, 4); memUseBuffer[len] = 'k'; diff --git a/apps/settings/sub_menu/exam_mode_controller.cpp b/apps/settings/sub_menu/exam_mode_controller.cpp index e47766266..31a40c36a 100644 --- a/apps/settings/sub_menu/exam_mode_controller.cpp +++ b/apps/settings/sub_menu/exam_mode_controller.cpp @@ -32,15 +32,15 @@ ExamModeController::ExamModeController(Responder * parentResponder) : bool ExamModeController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { - if (m_messageTreeModel->children(selectedRow())->label() == I18n::Message::ExamModeMode) { - (&m_examModeModeController)->setMessageTreeModel(m_messageTreeModel->children(selectedRow())); + if (m_messageTreeModel->childAtIndex(selectedRow())->label() == I18n::Message::ExamModeMode) { + (&m_examModeModeController)->setMessageTreeModel(m_messageTreeModel->childAtIndex(selectedRow())); StackViewController * stack = stackController(); stack->push(&m_examModeModeController); return true; } #if LEDS_CHOICE - else if (m_messageTreeModel->children(selectedRow())->label() == I18n::Message::LEDColor) { - (&m_ledController)->setMessageTreeModel(m_messageTreeModel->children(selectedRow())); + else if (m_messageTreeModel->childAtIndex(selectedRow())->label() == I18n::Message::LEDColor) { + (&m_ledController)->setMessageTreeModel(m_messageTreeModel->childAtIndex(selectedRow())); StackViewController * stack = stackController(); stack->push(&m_ledController); return true; @@ -80,11 +80,11 @@ HighlightCell * ExamModeController::reusableCell(int index, int type) { assert(type == 0); assert(index >= 0 && index < 3); #if LEDS_CHOICE - if (m_messageTreeModel->children(index)->label() == I18n::Message::LEDColor) { + if (m_messageTreeModel->childAtIndex(index)->label() == I18n::Message::LEDColor) { return &m_ledColorCell; } #endif - if (m_messageTreeModel->children(index)->label() == I18n::Message::ExamModeMode) { + if (m_messageTreeModel->childAtIndex(index)->label() == I18n::Message::ExamModeMode) { return &m_examModeCell; } return &m_cell[index]; @@ -101,7 +101,7 @@ void ExamModeController::willDisplayCellForIndex(HighlightCell * cell, int index } Preferences * preferences = Preferences::sharedPreferences(); GenericSubController::willDisplayCellForIndex(cell, index); - I18n::Message thisLabel = m_messageTreeModel->children(index)->label(); + I18n::Message thisLabel = m_messageTreeModel->childAtIndex(index)->label(); if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode() && (thisLabel == I18n::Message::ActivateExamMode || thisLabel == I18n::Message::ExamModeActive)) { MessageTableCell * myCell = (MessageTableCell *)cell; @@ -110,13 +110,13 @@ void ExamModeController::willDisplayCellForIndex(HighlightCell * cell, int index #if LEDS_CHOICE if (thisLabel == I18n::Message::LEDColor) { MessageTableCellWithChevronAndMessage * myTextCell = (MessageTableCellWithChevronAndMessage *)cell; - I18n::Message message = (I18n::Message) m_messageTreeModel->children(index)->children((int)preferences->colorOfLED())->label(); + I18n::Message message = (I18n::Message) m_messageTreeModel->childAtIndex(index)->childAtIndex((int)preferences->colorOfLED())->label(); myTextCell->setSubtitle(message); } #endif if (thisLabel == I18n::Message::ExamModeMode) { MessageTableCellWithChevronAndMessage * myTextCell = (MessageTableCellWithChevronAndMessage *)cell; - I18n::Message message = (I18n::Message) m_messageTreeModel->children(index)->children((uint8_t)GlobalPreferences::sharedGlobalPreferences()->tempExamMode() - 1)->label(); + I18n::Message message = (I18n::Message) m_messageTreeModel->childAtIndex(index)->childAtIndex((uint8_t)GlobalPreferences::sharedGlobalPreferences()->tempExamMode() - 1)->label(); myTextCell->setSubtitle(message); } } diff --git a/apps/settings/sub_menu/generic_sub_controller.cpp b/apps/settings/sub_menu/generic_sub_controller.cpp index 40f658955..5286743d1 100644 --- a/apps/settings/sub_menu/generic_sub_controller.cpp +++ b/apps/settings/sub_menu/generic_sub_controller.cpp @@ -76,7 +76,7 @@ int GenericSubController::typeAtLocation(int i, int j) { void GenericSubController::willDisplayCellForIndex(HighlightCell * cell, int index) { MessageTableCell * myCell = (MessageTableCell *)cell; - myCell->setMessage(m_messageTreeModel->children(index)->label()); + myCell->setMessage(m_messageTreeModel->childAtIndex(index)->label()); } void GenericSubController::setMessageTreeModel(const MessageTree * messageTreeModel) { diff --git a/apps/settings/sub_menu/math_options_controller.cpp b/apps/settings/sub_menu/math_options_controller.cpp index ce8eb242c..b1a239506 100644 --- a/apps/settings/sub_menu/math_options_controller.cpp +++ b/apps/settings/sub_menu/math_options_controller.cpp @@ -19,11 +19,11 @@ MathOptionsController::MathOptionsController(Responder * parentResponder, InputE bool MathOptionsController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { GenericSubController * subController = nullptr; - if (m_messageTreeModel->children(selectedRow())->label() == I18n::Message::DisplayMode) + if (m_messageTreeModel->childAtIndex(selectedRow())->label() == I18n::Message::DisplayMode) subController = &m_displayModeController; else subController = &m_preferencesController; - subController->setMessageTreeModel(m_messageTreeModel->children(selectedRow())); + subController->setMessageTreeModel(m_messageTreeModel->childAtIndex(selectedRow())); StackViewController * stack = stackController(); stack->push(subController); return true; @@ -45,7 +45,7 @@ int MathOptionsController::reusableCellCount(int type) { void MathOptionsController::willDisplayCellForIndex(HighlightCell * cell, int index) { MessageTableCellWithChevronAndMessage * myTextCell = (MessageTableCellWithChevronAndMessage *)cell; Preferences * preferences = Preferences::sharedPreferences(); - I18n::Message thisLabel = m_messageTreeModel->children(index)->label(); + I18n::Message thisLabel = m_messageTreeModel->childAtIndex(index)->label(); myTextCell->setMessage(thisLabel); //add text for preferences @@ -72,7 +72,7 @@ void MathOptionsController::willDisplayCellForIndex(HighlightCell * cell, int in default: break; } - I18n::Message message = childIndex >= 0 ? m_messageTreeModel->children(index)->children(childIndex)->label() : I18n::Message::Default; + I18n::Message message = childIndex >= 0 ? m_messageTreeModel->childAtIndex(index)->childAtIndex(childIndex)->label() : I18n::Message::Default; myTextCell->setSubtitle(message); } diff --git a/apps/settings/sub_menu/preferences_controller.cpp b/apps/settings/sub_menu/preferences_controller.cpp index 188eea462..e9f23b5b6 100644 --- a/apps/settings/sub_menu/preferences_controller.cpp +++ b/apps/settings/sub_menu/preferences_controller.cpp @@ -228,7 +228,7 @@ Layout PreferencesController::layoutForPreferences(I18n::Message message) { void PreferencesController::willDisplayCellForIndex(HighlightCell * cell, int index) { GenericSubController::willDisplayCellForIndex(cell, index); MessageTableCellWithExpression * myCell = (MessageTableCellWithExpression *)cell; - myCell->setLayout(layoutForPreferences(m_messageTreeModel->children(index)->label())); + myCell->setLayout(layoutForPreferences(m_messageTreeModel->childAtIndex(index)->label())); } KDCoordinate PreferencesController::rowHeight(int j) { diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index e284128fd..866e69355 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -74,7 +74,6 @@ TStart = "T Startwert" ToZoom = "Zoom: " Trigonometric = "Trigonometrisch" UndefinedValue = "Nicht definierter Wert" -ValueNotReachedByFunction = "Der Wert wird von der Funktion nicht erreicht" ValuesTab = "Tabelle" Warning = "Achtung" XEnd = "X Endwert" diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index bbef03c83..55f57a25c 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -74,7 +74,6 @@ TStart = "T start" ToZoom = "Zoom: " Trigonometric = "Trigonometrical" UndefinedValue = "Undefined value" -ValueNotReachedByFunction = "Value not reached by function" ValuesTab = "Table" Warning = "Warning" XEnd = "X end" diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index 2ac9ff491..8257c8691 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -74,7 +74,6 @@ TStart = "T inicio" ToZoom = "Zoom : " Trigonometric = "Trigonométrico" UndefinedValue = "Valor indefinido" -ValueNotReachedByFunction = "No se alcanza este valor" ValuesTab = "Tabla" Warning = "Cuidado" XEnd = "X fin" diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index f711cbbec..32bb6aa3b 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -74,7 +74,6 @@ TStart = "T début" ToZoom = "Zoomer : " Trigonometric = "Trigonométrique" UndefinedValue = "Valeur non définie" -ValueNotReachedByFunction = "Valeur non atteinte par la fonction" ValuesTab = "Tableau" Warning = "Attention" XEnd = "X fin" diff --git a/apps/shared.it.i18n b/apps/shared.it.i18n new file mode 100644 index 000000000..cd058299b --- /dev/null +++ b/apps/shared.it.i18n @@ -0,0 +1,80 @@ +ActivateDeactivate = "Attivare/Disattivare" +ActivateExamMode = "Attivare modalità d'esame" +ActivateDutchExamMode = "Activate Dutch exam mode" +ActiveExamModeMessage1 = "Tutti i vostri dati saranno " +ActiveExamModeMessage2 = "cancellati se attivate " +ActiveExamModeMessage3 = "la modalità d'esame." +ActiveDutchExamModeMessage1 = "All your data will be deleted when" +ActiveDutchExamModeMessage2 = "you activate the exam mode. Python" +ActiveDutchExamModeMessage3 = "application will be unavailable." +Axis = "Assi" +Cancel = "Annullare" +ClearColumn = "Cancella la colonna" +ColumnOptions = "Opzioni colonna" +CopyColumnInList = "Copia colonna in una lista" +DataNotSuitable = "I dati non sono adeguati" +DataTab = "Dati" +DefaultSetting = "Impostazioni di base" +Deg = "deg" +Deviation = "Varianza" +DisplayValues = "Mostra valori" +Empty = "Vuoto" +Eng = "eng" +ExitExamMode1 = "Volete uscire " +ExitExamMode2 = "dalla modalità d'esame ?" +Exponential = "Esponenziale" +FillWithFormula = "Compilare con una formula" +ForbiddenValue = "Valore non consentito" +FunctionColumn = "Colonna 0(0)" +FunctionOptions = "Opzioni della funzione" +Goto = "Andare a" +GraphTab = "Grafico" +HardwareTestLaunch1 = "Farete il test hardware." +HardwareTestLaunch2 = "Per uscire dovrete" +HardwareTestLaunch3 = "premere il tasto reset" +HardwareTestLaunch4 = "che cancellerà i vostri dati." +Initialization = "Pre-regolazione" +IntervalSet = "Imposta l'intervallo" +Language = "Lingua" +LowBattery = "Batteria bassa" +Mean = "Media" +Move = " Spostare : " +NameCannotStartWithNumber = "Un nome non può cominciare con un numero" +NameTaken = "Questo nome è già utilizzato" +NameTooLong = "Questo nome è troppo lungo" +Next = "Successivo" +NEnd = "N finale" +NoDataToPlot = "Nessun dato da tracciare" +NoFunctionToDelete = "Nessuna funzione da cancellare" +NoValueToCompute = "Nessun valore da calcolare" +NStart = "N iniziale" +Ok = "Conferma" +Or = " o " +Orthonormal = "Ortogonale" +Plot = "Traccia grafico" +PoolMemoryFull1 = "La memoria di lavoro è piena." +PoolMemoryFull2 = "Riprovare." +Rad = "rad" +Rename = "Rinominare" +RoundAbscissa = "Ascisse intere" +Sci = "sci" +SquareSum = "Somma dei quadrati" +StandardDeviation = "Deviazione standard" +StatTab = "Stats" +Step = "Passo" +StorageMemoryFull1 = "La memoria è piena." +StorageMemoryFull2 = "Cancellate i dati e riprovate." +StoreExpressionNotAllowed = "'store' non è consentito" +SyntaxError = "Sintassi errata" +TEnd = "T finale" +ThetaEnd = "θ finale" +ThetaStart = "θ iniziale" +TStart = "T iniziale" +ToZoom = "Ingrandire : " +Trigonometric = "Trigonometrica" +UndefinedValue = "Valore non definito" +ValuesTab = "Tabella" +Warning = "Attenzione" +XEnd = "X finale" +XStart = "X iniziale" +Zoom = "Zoom" diff --git a/apps/shared.nl.i18n b/apps/shared.nl.i18n new file mode 100644 index 000000000..f8f1acd98 --- /dev/null +++ b/apps/shared.nl.i18n @@ -0,0 +1,80 @@ +ActivateDeactivate = "Zet aan/uit" +ActivateExamMode = "Internationale examenst." +ActivateDutchExamMode = "Nederlandse examenstand" +ActiveExamModeMessage1 = "Al je gegevens worden " +ActiveExamModeMessage2 = "gewist wanneer je de " +ActiveExamModeMessage3 = "examenstand activeert." +ActiveDutchExamModeMessage1 = "Al je gegevens worden gewist wanneer" +ActiveDutchExamModeMessage2 = "je de examenstand activeert. De Python" +ActiveDutchExamModeMessage3 = "applicatie wordt uitgeschakelt." +Axis = "Assen" +Cancel = "Annuleer" +ClearColumn = "Wis kolom" +ColumnOptions = "Kolomopties" +CopyColumnInList = "Exporteer de kolom naar een lijst" +DataNotSuitable = "Gegevens niet geschikt" +DataTab = "Gegevens" +DefaultSetting = "Standaardinstelling" +Deg = "deg" +Deviation = "Variantie" +DisplayValues = "Waarden weergeven" +Empty = "Leeg" +Eng = "eng" +ExitExamMode1 = "Verlaat de " +ExitExamMode2 = "examenstand?" +Exponential = "Exponentieel" +FillWithFormula = "Vul met een formule" +ForbiddenValue = "Verboden waarde" +FunctionColumn = "0(0) kolom" +FunctionOptions = "Functie-opties" +Goto = "Ga naar" +GraphTab = "Grafiek" +HardwareTestLaunch1 = "Je start de hardware test. " +HardwareTestLaunch2 = "Aan het eind van de test moet " +HardwareTestLaunch3 = "je de rekenmachine resetten en " +HardwareTestLaunch4 = "worden al je gegevens verwijderd." +Initialization = "Voorgedefinieerd" +IntervalSet = "Stel het interval in" +Language = "Taal" +LowBattery = "Batterij bijna leeg" +Mean = "Gemiddelde" +Move = " Verplaats: " +NameCannotStartWithNumber = "Een naam kan niet beginnen met een nummer" +NameTaken = "Deze naam is al in gebruik" +NameTooLong = "Deze naam is te lang" +Next = "Volgende" +NoDataToPlot = "Geen gegevens om te plotten" +NoFunctionToDelete = "Geen functie om te verwijderen" +NoValueToCompute = "Geen waarden om te berekenen" +NEnd = "N einde" +NStart = "N begin" +Ok = "Bevestig" +Or = " of " +Orthonormal = "Orthonormaal" +Plot = "Grafiek plotten" +PoolMemoryFull1 = "Het werkgeheugen is vol." +PoolMemoryFull2 = "Opnieuw proberen." +Rad = "rad" +Rename = "Hernoem" +RoundAbscissa = "Geheel getal" +Sci = "sci" +SquareSum = "Som van kwadraten" +StandardDeviation = "Standaardafwijking" +StoreExpressionNotAllowed = "'opslaan' is niet toegestaan" +StatTab = "Stats" +Step = "Stap" +StorageMemoryFull1 = "Het geheugen is vol." +StorageMemoryFull2 = "Wis de gegevens en probeer opnieuw." +SyntaxError = "Syntax error" +TEnd = "T einde" +ThetaEnd = "θ einde" +ThetaStart = "θ begin" +TStart = "T begin" +ToZoom = "Zoom: " +Trigonometric = "Goniometrisch" +UndefinedValue = "Ongedefinieerde waarde" +ValuesTab = "Tabel" +Warning = "Waarschuwing" +XEnd = "X einde" +XStart = "X begin" +Zoom = "Zoom" diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index 7ffa4a8b0..84f6967d4 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -1,8 +1,8 @@ -ActivateDeactivate = "Activar/Desactivar" -ActivateExamMode = "Activar o modo de exame" +ActivateDeactivate = "Ativar/Desativar" +ActivateExamMode = "Ativar o modo de exame" ActivateDutchExamMode = "Activate Dutch exam mode" ActiveExamModeMessage1 = "Todos os seus dados serão " -ActiveExamModeMessage2 = "apagados se você ligar " +ActiveExamModeMessage2 = "apagados se ativar " ActiveExamModeMessage3 = "o modo de exame." ActiveDutchExamModeMessage1 = "All your data will be deleted when" ActiveDutchExamModeMessage2 = "you activate the exam mode. Python" @@ -11,43 +11,43 @@ Axis = "Eixos" Cancel = "Cancelar" ClearColumn = "Excluir coluna" ColumnOptions = "Opções de coluna" -CopyColumnInList = "Copie a coluna em uma lista" +CopyColumnInList = "Copiar a coluna para uma lista" DataNotSuitable = "Dados não adequados" DataTab = "Dados" -DefaultSetting = "Configurações basicas" +DefaultSetting = "Configurações básicas" Deg = "gra" -Deviation = "Variancia" +Deviation = "Variância" DisplayValues = "Exibir os valores" -Empty = "Vácuo" +Empty = "Vazio" Eng = "eng" -ExitExamMode1 = "Você quer sair do modo de " +ExitExamMode1 = "Deseja sair do modo de " ExitExamMode2 = "exame ?" Exponential = "Exponencial" FillWithFormula = "Preencher com uma fórmula" ForbiddenValue = "Valor proibido" FunctionColumn = "Coluna 0(0)" FunctionOptions = "Opções de função" -Goto = "Ir a" +Goto = "Ir para" GraphTab = "Gráfico" -HardwareTestLaunch1 = "Você vai executar o teste da planta." -HardwareTestLaunch2 = "Para sair você tem que executar" -HardwareTestLaunch3 = "uma redefinição, que ira apagar" -HardwareTestLaunch4 = "seus dados." +HardwareTestLaunch1 = "Vai executar o teste da planta." +HardwareTestLaunch2 = "Para sair tem que executar" +HardwareTestLaunch3 = "uma redefinição, que irá apagar" +HardwareTestLaunch4 = "os seus dados." Initialization = "Inicialização" IntervalSet = "Ajustar o intervalo" Language = "Idioma" LowBattery = "Bateria fraca" Mean = "Média" Move = " Mover : " -NameCannotStartWithNumber = "Um nome não pode começar com um número" -NameTaken = "Este nome é já usado" +NameCannotStartWithNumber = "O nome não pode começar com um número" +NameTaken = "Este nome já está a ser usado" NameTooLong = "Este nome é muito longo" NEnd = "N fim" Next = "Seguinte" -NoDataToPlot = "Não ha dados para desenhar" +NoDataToPlot = "Não há dados para desenhar" NoFunctionToDelete = "Sem função para eliminar" -NoValueToCompute = "Nenhuma quantidade para calcular" -NStart = "N inicio" +NoValueToCompute = "Não há dados para calcular" +NStart = "N início" Ok = "Confirmar" Or = " ou " Orthonormal = "Ortonormado" @@ -62,23 +62,22 @@ SquareSum = "Soma dos quadrados" StandardDeviation = "Desvio padrão" StatTab = "Estat" Step = "Passo" -StorageMemoryFull1 = "A memoria esta cheia." +StorageMemoryFull1 = "A memória esta cheia." StorageMemoryFull2 = "Apage dados e tente novamente." StoreExpressionNotAllowed = "'store' não está permitido" SyntaxError = "Erro de sintaxe" Sym = "sim" TEnd = "T fim" ThetaEnd = "θ fim" -ThetaStart = "θ inicio" -TStart = "T inicio" +ThetaStart = "θ início" +TStart = "T início" ToZoom = "Zoom : " Trigonometric = "Trigonométrico" UndefinedValue = "Valor indefinido" -ValueNotReachedByFunction = "O valor não é alcançado pela função" ValuesTab = "Tabela" Warning = "Atenção" XEnd = "X fim" -XStart = "X inicio" +XStart = "X início" Zoom = "Zoom" Developers = "Desenvolvedores" BetaTesters = "Testadores beta" diff --git a/apps/shared/Makefile b/apps/shared/Makefile index 4056c9dbd..4ffcd50df 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -14,6 +14,7 @@ app_shared_test_src = $(addprefix apps/shared/,\ labeled_curve_view.cpp \ memoized_curve_view_range.cpp \ range_1D.cpp \ + toolbox_helpers.cpp \ zoom_and_pan_curve_view_controller.cpp \ zoom_curve_view_controller.cpp \ ) @@ -74,7 +75,6 @@ app_shared_src = $(addprefix apps/shared/,\ text_field_delegate_app.cpp \ text_field_with_extension.cpp \ text_helpers.cpp \ - toolbox_helpers.cpp \ values_controller.cpp \ values_function_parameter_controller.cpp \ values_parameter_controller.cpp \ diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index b1ca44cfc..4d0e17b12 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -134,6 +134,22 @@ float CurveView::floatToPixel(Axis axis, float f) const { } } +float CurveView::floatLengthToPixelLength(Axis axis, float f) const { + float dist = floatToPixel(axis, f) - floatToPixel(axis, 0.0f); + return axis == Axis::Vertical ? - dist : dist; +} + +float CurveView::floatLengthToPixelLength(float dx, float dy) const { + float dxPixel = floatLengthToPixelLength(Axis::Horizontal, dx); + float dyPixel = floatLengthToPixelLength(Axis::Vertical, dy); + return std::sqrt(dxPixel*dxPixel+dyPixel*dyPixel); +} + +float CurveView::pixelLengthToFloatLength(Axis axis, float f) const { + f = axis == Axis::Vertical ? -f : f; + return pixelToFloat(axis, floatToPixel(axis, 0.0f) + f); +} + void CurveView::drawGridLines(KDContext * ctx, KDRect rect, Axis axis, float step, KDColor boldColor, KDColor lightColor) const { Axis otherAxis = (axis == Axis::Horizontal) ? Axis::Vertical : Axis::Horizontal; /* We translate the pixel coordinates into floats, adding/subtracting 1 to @@ -450,48 +466,66 @@ void CurveView::drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor } -void CurveView::drawArrow(KDContext * ctx, KDRect rect, float x, float y, float dx, float dy, KDColor color, KDCoordinate pixelArrowLength, float angle) const { - /* Let's call the following variables L and l: - * - * / | - * / | - * / l - * / | - * / | - * <-------------------------------------------------- - * \ - * \ - * \ - * \ - * \ - * - * ----- L ----- - * - **/ - assert(angle >= 0.0f); +void CurveView::drawArrow(KDContext * ctx, KDRect rect, float x, float y, float dx, float dy, KDColor color, float arrowWidth, float tanAngle) const { + assert(tanAngle >= 0.0f); if (std::fabs(dx) < FLT_EPSILON && std::fabs(dy) < FLT_EPSILON) { // We can't draw an arrow without any orientation return; } - /* We compute the arrow segments in pixels in order to correctly size the - * arrow without depending on the displayed range. - * Warning: the computed values are relative so we need to add/subtract the - * pixel position of 0s. */ - float x0Pixel = floatToPixel(Axis::Horizontal, 0.0f); - float y0Pixel = floatToPixel(Axis::Vertical, 0.0f); - float dxPixel = floatToPixel(Axis::Horizontal, dx) - x0Pixel; - float dyPixel = y0Pixel - floatToPixel(Axis::Vertical, dy); - float dx2dy2 = std::sqrt(dxPixel*dxPixel+dyPixel*dyPixel); - float L = pixelArrowLength; - float l = angle*L; - float arrow1dx = pixelToFloat(Axis::Horizontal, x0Pixel + L*dxPixel/dx2dy2 + l*dyPixel/dx2dy2); - float arrow1dy = pixelToFloat(Axis::Vertical, y0Pixel - (L*dyPixel/dx2dy2 - l*dxPixel/dx2dy2)); - drawSegment(ctx, rect, x, y, x - arrow1dx, y - arrow1dy, color, false); + // Translate arrowWidth in pixel length + float pixelArrowWidth = 8.0f; // default value in pixels + if (arrowWidth > 0.0f) { + float dxdyFloat = std::sqrt(dx * dx + dy * dy); + float dxArrowFloat = arrowWidth * std::fabs(dy) / dxdyFloat; + float dyArrowFloat = arrowWidth * std::fabs(dx) / dxdyFloat; + pixelArrowWidth = floatLengthToPixelLength(dxArrowFloat, dyArrowFloat); + assert(pixelArrowWidth > 0.0f); + } - float arrow2dx = pixelToFloat(Axis::Horizontal, x0Pixel + L*dxPixel/dx2dy2 - l*dyPixel/dx2dy2); - float arrow2dy = pixelToFloat(Axis::Vertical, y0Pixel - (L*dyPixel/dx2dy2 + l*dxPixel/dx2dy2)); - drawSegment(ctx, rect, x, y, x - arrow2dx, y - arrow2dy, color, false); + /* Let's call the following variables L and l: + * + * /arrow2 | + * / | + * / l + * / | + * / B | + * <---------+---------------------------------------- + * \ + * \ + * \ + * \ + * \arrow1 + * + * ----- L ----- + * + */ + + float lPixel = pixelArrowWidth / 2.0; + float LPixel = lPixel / tanAngle; + + float xPixel = floatToPixel(Axis::Horizontal, x); + float yPixel = floatToPixel(Axis::Vertical, y); + + // We compute the arrow segments in pixels + float dxPixel = floatLengthToPixelLength(Axis::Horizontal, dx); + float dyPixel = floatLengthToPixelLength(Axis::Vertical, dy); + float dx2dy2Pixel = floatLengthToPixelLength(dx, dy); + + // Point B is the orthogonal projection of the arrow tips on the arrow body + float bxPixel = xPixel - LPixel * dxPixel / dx2dy2Pixel; + float byPixel = yPixel + LPixel * dyPixel / dx2dy2Pixel; + + float dxArrowPixel = - lPixel * dyPixel / dx2dy2Pixel; + float dyArrowPixel = lPixel * dxPixel / dx2dy2Pixel; + + float arrow1xPixel = bxPixel + dxArrowPixel; + float arrow1yPixel = byPixel - dyArrowPixel; + float arrow2xPixel = bxPixel - dxArrowPixel; + float arrow2yPixel = byPixel + dyArrowPixel; + + straightJoinDots(ctx, rect, xPixel, yPixel, arrow1xPixel, arrow1yPixel, color, true); + straightJoinDots(ctx, rect, xPixel, yPixel, arrow2xPixel, arrow2yPixel, color, true); } void CurveView::drawGrid(KDContext * ctx, KDRect rect) const { diff --git a/apps/shared/curve_view.h b/apps/shared/curve_view.h index 434b2ec1e..18985cb54 100644 --- a/apps/shared/curve_view.h +++ b/apps/shared/curve_view.h @@ -57,6 +57,9 @@ protected: constexpr static int k_externRectMargin = 2; float pixelToFloat(Axis axis, KDCoordinate p) const; float floatToPixel(Axis axis, float f) const; + float floatLengthToPixelLength(Axis axis, float f) const; + float pixelLengthToFloatLength(Axis axis, float f) const; + float floatLengthToPixelLength(float dx, float dy) const; void drawLine(KDContext * ctx, KDRect rect, Axis axis, float coordinate, KDColor color, KDCoordinate thickness = 1, KDCoordinate dashSize = -1) const { return drawHorizontalOrVerticalSegment(ctx, rect, axis, coordinate, -INFINITY, INFINITY, color, @@ -77,29 +80,29 @@ protected: void drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor color, Size size = Size::Small) const; /* 'drawArrow' draws the edge of an arrow pointing to (x,y) with the * orientation (dx,dy). - * The parameters defining the shape of the arrow are the length in pixel of - * the projection of the arrow on the segment -'pixelArrowLength'- and the - * tangent of the angle between the segment and each wing of the arrow called - * 'angle'. + * The parameters defining the shape of the arrow are the length of + * the base of the arrow triangle - 'pixelArrowWith' - and the tangent of the + * angle between the segment and each wing of the arrow called 'tanAngle'. * * / | * / | - * / L - * / | - * / | - * <-------------------------------------------------- - * \ - * \ - * \ - * \ - * \ + * / | + * / \ | + * / \ angle | + * <----------------------------l--------------------- + * \ | + * \ | + * \ | + * \ | + * \ | * - * <--- pl ---> + * <--- L ---> * - * pl = pixelArrowLength - * tan(angle) = L/pl + * l = pixelArrowWith + * tanAngle = tan(angle) = l/2L */ - void drawArrow(KDContext * ctx, KDRect rect, float x, float y, float dx, float dy, KDColor color, KDCoordinate pixelArrowLength = 10, float angle = 0.4f) const; + + void drawArrow(KDContext * ctx, KDRect rect, float x, float y, float dx, float dy, KDColor color, float arrowWidth, float tanAngle = 1.0f/3.0f) const; void drawGrid(KDContext * ctx, KDRect rect) const; void drawAxes(KDContext * ctx, KDRect rect) const; void drawAxis(KDContext * ctx, KDRect rect, Axis axis) const; diff --git a/apps/shared/input_event_handler_delegate_app.cpp b/apps/shared/input_event_handler_delegate_app.cpp index cc9b5d525..53c49047c 100644 --- a/apps/shared/input_event_handler_delegate_app.cpp +++ b/apps/shared/input_event_handler_delegate_app.cpp @@ -20,9 +20,9 @@ Toolbox * InputEventHandlerDelegateApp::toolboxForInputEventHandler(InputEventHa } NestedMenuController * InputEventHandlerDelegateApp::variableBoxForInputEventHandler(InputEventHandler * textInput) { - VariableBoxController * varBox = AppsContainer::sharedAppsContainer()->variableBoxController(); + MathVariableBoxController * varBox = AppsContainer::sharedAppsContainer()->variableBoxController(); varBox->setSender(textInput); - varBox->lockDeleteEvent(VariableBoxController::Page::RootMenu); + varBox->lockDeleteEvent(MathVariableBoxController::Page::RootMenu); return varBox; } diff --git a/apps/shared/language_controller.cpp b/apps/shared/language_controller.cpp index 97f81123d..5c0897f53 100644 --- a/apps/shared/language_controller.cpp +++ b/apps/shared/language_controller.cpp @@ -5,12 +5,12 @@ namespace Shared { -LanguageController::LanguageController(Responder * parentResponder, KDCoordinate topMargin) : +LanguageController::LanguageController(Responder * parentResponder, KDCoordinate verticalMargin) : ViewController(parentResponder), m_selectableTableView(this, this, this) { - m_selectableTableView.setTopMargin(topMargin); - m_selectableTableView.setBottomMargin(0); + m_selectableTableView.setTopMargin(verticalMargin); + m_selectableTableView.setBottomMargin(verticalMargin); for (int i = 0; i < I18n::NumberOfLanguages; i++) { m_cells[i].setMessageFont(KDFont::LargeFont); } diff --git a/apps/shared/language_controller.h b/apps/shared/language_controller.h index fac81fb7c..a3ab284e2 100644 --- a/apps/shared/language_controller.h +++ b/apps/shared/language_controller.h @@ -8,7 +8,7 @@ namespace Shared { class LanguageController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { public: - LanguageController(Responder * parentResponder, KDCoordinate topMargin); + LanguageController(Responder * parentResponder, KDCoordinate verticalMargin); void resetSelection(); View * view() override; @@ -23,8 +23,11 @@ public: int reusableCellCount() const override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; -private: + +protected: SelectableTableView m_selectableTableView; + +private: MessageTableCell m_cells[I18n::NumberOfLanguages]; }; diff --git a/apps/shared/poincare_helpers.h b/apps/shared/poincare_helpers.h index 31010f61f..774547e17 100644 --- a/apps/shared/poincare_helpers.h +++ b/apps/shared/poincare_helpers.h @@ -62,11 +62,18 @@ inline Poincare::Expression ParseAndSimplify(const char * text, Poincare::Contex return Poincare::Expression::ParseAndSimplify(text, context, complexFormat, preferences->angleUnit(), symbolicComputation); } -inline void Simplify(Poincare::Expression * e, Poincare::Context * context, Poincare::ExpressionNode::ReductionTarget target, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition) { +inline void Simplify(Poincare::Expression * e, Poincare::Context * context, Poincare::ExpressionNode::ReductionTarget target, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion unitConversion = Poincare::ExpressionNode::UnitConversion::Default) { Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); Poincare::Preferences::ComplexFormat complexFormat = Poincare::Expression::UpdatedComplexFormatWithExpressionInput(preferences->complexFormat(), *e, context); - *e = e->simplify(Poincare::ExpressionNode::ReductionContext(context, complexFormat, preferences->angleUnit(), target, symbolicComputation)); + *e = e->simplify(Poincare::ExpressionNode::ReductionContext(context, complexFormat, preferences->angleUnit(), target, symbolicComputation, unitConversion)); +} + +inline void Reduce(Poincare::Expression * e, Poincare::Context * context, Poincare::ExpressionNode::ReductionTarget target, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion unitConversion = Poincare::ExpressionNode::UnitConversion::Default) { + Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); + Poincare::Preferences::ComplexFormat complexFormat = Poincare::Expression::UpdatedComplexFormatWithExpressionInput(preferences->complexFormat(), *e, context); + + *e = e->reduce(Poincare::ExpressionNode::ReductionContext(context, complexFormat, preferences->angleUnit(), target, symbolicComputation, unitConversion)); } inline void ParseAndSimplifyAndApproximate(const char * text, Poincare::Expression * simplifiedExpression, Poincare::Expression * approximateExpression, Poincare::Context * context, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition) { diff --git a/apps/shared/range_1D.h b/apps/shared/range_1D.h index a528540f0..1557e82b6 100644 --- a/apps/shared/range_1D.h +++ b/apps/shared/range_1D.h @@ -16,6 +16,8 @@ namespace Shared { class __attribute__((packed)) Range1D final { public: + /* If m_min and m_max are too close, we cannot divide properly the range by + * the number of pixels, which creates a drawing problem. */ constexpr static float k_minFloat = 1E-4f; constexpr static float k_default = 10.0f; Range1D(float min = -k_default, float max = k_default) : diff --git a/apps/shared/round_cursor_view.cpp b/apps/shared/round_cursor_view.cpp index 6e98fad98..fbd9a68eb 100644 --- a/apps/shared/round_cursor_view.cpp +++ b/apps/shared/round_cursor_view.cpp @@ -68,37 +68,9 @@ bool RoundCursorView::eraseCursorIfPossible() { // Erase the cursor KDColor cursorWorkingBuffer[k_cursorSize * k_cursorSize]; KDContext * ctx = KDIonContext::sharedContext(); - ctx->setOrigin(currentFrame.origin()); + ctx->setOrigin(absoluteOrigin()); ctx->setClippingRect(currentFrame); KDSize cursorSize = KDSize(k_cursorSize, k_cursorSize); - - /* We assert that the visible frame is not cropped (indeed a cursor is always - * fully inside the window, thanks to panToMakeCursorVisible). Otherwise, we - * would need to change this algorithm. - * - * +---+ - * | |<- frame m_underneathPixelBuffer: +---+ - * +----+---+--------+ |000| - * | |xxx| |<- parentVisibleFrame |xxx| - * | +---+ | +---+ - * | | - * +-----------------+ - * - * +---+ - * |xxx|: absoluteVisibleFrame - * +---+ - * - * What we would draw with the current algorithm: - * +---+ - * | |<- frame - * +----+---+--------+ - * | |000| |<- parentVisibleFrame - * | +---+ | - * | | - * +-----------------+ - * - * */ - assert(currentFrame.size() == cursorSize); ctx->fillRectWithPixels(KDRect(0, 0, cursorSize), m_underneathPixelBuffer, cursorWorkingBuffer); // TODO Restore the context to previous values? return true; diff --git a/apps/shared/scrollable_multiple_expressions_view.cpp b/apps/shared/scrollable_multiple_expressions_view.cpp index 802c7d7f6..933f04021 100644 --- a/apps/shared/scrollable_multiple_expressions_view.cpp +++ b/apps/shared/scrollable_multiple_expressions_view.cpp @@ -244,7 +244,10 @@ void AbstractScrollableMultipleExpressionsView::setLayouts(Poincare::Layout left contentCell()->layoutSubviews(); // Reload the scroll content view layout (the content size might have changed) layoutSubviews(); - // Do no reload scroll here as 'setLayouts' is called every time the table is re-layout (when scrolling for instance) + /* TODO revert commit 87e48361961d1? + * We can call reloadScroll here instead of in didBecome firstResponder, + * because we fixed setLayouts (updateLeftLayout, updateCenterLayout and + * updateRightLayout are now sometimes false). */ } } diff --git a/apps/shared/settings_message_tree.h b/apps/shared/settings_message_tree.h index f3c3bf364..b37122b7a 100644 --- a/apps/shared/settings_message_tree.h +++ b/apps/shared/settings_message_tree.h @@ -19,7 +19,7 @@ public: m_children(children) {} - const MessageTree * children(int index) const override { return &m_children[index]; } + const MessageTree * childAtIndex(int index) const override { return &m_children[index]; } private: const SettingsMessageTree * m_children; diff --git a/apps/shared/toolbox_helpers.cpp b/apps/shared/toolbox_helpers.cpp index 589615b89..8758ca763 100644 --- a/apps/shared/toolbox_helpers.cpp +++ b/apps/shared/toolbox_helpers.cpp @@ -29,10 +29,10 @@ int CursorIndexInCommandText(const char * text) { } void TextToInsertForCommandMessage(I18n::Message message, char * buffer, int bufferSize, bool replaceArgsWithEmptyChar) { - TextToInsertForCommandText(I18n::translate(message), buffer, bufferSize, replaceArgsWithEmptyChar); + TextToInsertForCommandText(I18n::translate(message), -1, buffer, bufferSize, replaceArgsWithEmptyChar); } -void TextToInsertForCommandText(const char * command, char * buffer, int bufferSize, bool replaceArgsWithEmptyChar) { +void TextToInsertForCommandText(const char * command, int commandLength, char * buffer, int bufferSize, bool replaceArgsWithEmptyChar) { int index = 0; int numberOfOpenParentheses = 0; int numberOfOpenBrackets = 0; @@ -41,7 +41,7 @@ void TextToInsertForCommandText(const char * command, char * buffer, int bufferS UTF8Decoder decoder(command); CodePoint codePoint = decoder.nextCodePoint(); - while (codePoint != UCodePointNull) { + while (codePoint != UCodePointNull && (commandLength < 0 || (decoder.stringPosition() - command <= commandLength))) { if (codePoint == ')') { numberOfOpenParentheses--; } else if (codePoint == ']') { @@ -62,6 +62,7 @@ void TextToInsertForCommandText(const char * command, char * buffer, int bufferS index += UTF8Decoder::CodePointToChars(codePoint, buffer + index, bufferSize - index); } else { if (replaceArgsWithEmptyChar && !argumentAlreadyReplaced) { + assert(index < bufferSize); index += UTF8Decoder::CodePointToChars(UCodePointEmpty, buffer + index, bufferSize - index); argumentAlreadyReplaced = true; } @@ -75,6 +76,7 @@ void TextToInsertForCommandText(const char * command, char * buffer, int bufferS } codePoint = decoder.nextCodePoint(); } + assert(index < bufferSize); buffer[index] = 0; } diff --git a/apps/shared/toolbox_helpers.h b/apps/shared/toolbox_helpers.h index 16233ffe6..5c63a9560 100644 --- a/apps/shared/toolbox_helpers.h +++ b/apps/shared/toolbox_helpers.h @@ -14,7 +14,7 @@ int CursorIndexInCommandText(const char * text); void TextToInsertForCommandMessage(I18n::Message message, char * buffer, int bufferSize, bool replaceArgsWithEmptyChar = false); -void TextToInsertForCommandText(const char * command, char * buffer, int bufferSize, bool replaceArgsWithEmptyChar = false); +void TextToInsertForCommandText(const char * command, int commandLength, char * buffer, int bufferSize, bool replaceArgsWithEmptyChar = false); /* Removes the arguments from a command: * - Removes text between parentheses or brackets, except commas */ diff --git a/apps/shared/zoom_parameter_controller.cpp b/apps/shared/zoom_parameter_controller.cpp index 0e3299508..7eaafc276 100644 --- a/apps/shared/zoom_parameter_controller.cpp +++ b/apps/shared/zoom_parameter_controller.cpp @@ -58,11 +58,9 @@ int ZoomParameterController::ContentView::numberOfSubviews() const { View * ZoomParameterController::ContentView::subviewAtIndex(int index) { assert(index >= 0 && index < 2); - /* The order of subview is important here : - * If we redraw the curveView before the legendView, that can have some display issue, when exiting sleep mode, which - can be visible, if the redraw of curveView is long (with complicated curve), so we prefer to have legendView - at first subview. - */ + /* The order of subviews matters here: redrawing curve view can be long and + * if it was redraw before the legend view, you could see noise when + * switching the device on and off. */ if (index == 0) { return &m_legendView; } diff --git a/apps/solver/Makefile b/apps/solver/Makefile index d4a9b93db..63b222173 100644 --- a/apps/solver/Makefile +++ b/apps/solver/Makefile @@ -18,14 +18,7 @@ app_solver_src = $(addprefix apps/solver/,\ app_solver_src += $(app_solver_test_src) apps_src += $(app_solver_src) -i18n_files += $(addprefix apps/solver/,\ - base.de.i18n\ - base.en.i18n\ - base.es.i18n\ - base.fr.i18n\ - base.pt.i18n\ - base.hu.i18n\ -) +i18n_files += $(call i18n_without_universal_for,solver/base) tests_src += $(addprefix apps/solver/test/,\ equation_store.cpp \ diff --git a/apps/solver/base.it.i18n b/apps/solver/base.it.i18n new file mode 100644 index 000000000..5b6f79dbf --- /dev/null +++ b/apps/solver/base.it.i18n @@ -0,0 +1,29 @@ +SolverApp = "Equazioni" +SolverAppCapital = "EQUAZIONI" +AddEquation = "Aggiungi equazione" +ResolveEquation = "Risolvi l'equazione" +ResolveSystem = "Risolvi il sistema" +UseEquationModel = "Utilizza un modello di equazione" +RequireEquation = "L'input deve essere un'equazione" +UndefinedEquation = "Un'equazione è indefinita'" +UnrealEquation = "Un'equazione non è reale" +TooManyVariables = "Il numero di incognite è troppo elevato" +NonLinearSystem = "Il sistema non è lineare" +Solution = "Soluzione" +ApproximateSolution = "Soluzione approssimata" +SearchInverval = "Intervallo di ricerca" +NoSolutionSystem = "Il sistema non ammette nessuna soluzione" +NoSolutionEquation = "L'equazione non ammette nessuna soluzione" +NoSolutionInterval = "Nessuna soluzione trovata dentro questo intervallo" +EnterEquation = "Inserire un'equazione" +InfiniteNumberOfSolutions = "Il sistema ammette un'infinità di soluzioni" +ApproximateSolutionIntervalInstruction0= "Inserire l'intervallo dentro al quale" +ApproximateSolutionIntervalInstruction1= "ricercare una soluzione approssimata" +OnlyFirstSolutionsDisplayed0 = "Solamente le prime 10" +OnlyFirstSolutionsDisplayed1 = "soluzioni sono mostrate" +PolynomeHasNoRealSolution0 = "Il polinomio non ammette" +PolynomeHasNoRealSolution1 = "una radice reale" +PredefinedVariablesUsedLeft = "Variabili" +PredefinedVariablesUsedRight = " predefinite utilizzate" +PredefinedVariablesIgnoredLeft = "Variabil" +PredefinedVariablesIgnoredRight = "i predefinite ignorate" diff --git a/apps/solver/base.nl.i18n b/apps/solver/base.nl.i18n new file mode 100644 index 000000000..adcfadc87 --- /dev/null +++ b/apps/solver/base.nl.i18n @@ -0,0 +1,29 @@ +SolverApp = "Vergelijking" +SolverAppCapital = "VERGELIJKING" +AddEquation = "Vergelijking toevoegen" +ResolveEquation = "Vergelijking oplossen" +ResolveSystem = "Stelsel oplossen" +UseEquationModel = "Gebruik een vergelijkingstemplate" +RequireEquation = "De invoer moet een vergelijking zijn" +UnrealEquation = "Vergelijking is niet reëel" +UndefinedEquation = "Ongedefinieerde vergelijking" +TooManyVariables = "Er zijn te veel onbekenden" +NonLinearSystem = "Het stelsel is niet lineair" +Solution = "Oplossing" +ApproximateSolution = "Benaderende oplossing" +SearchInverval = "Zoekinterval" +NoSolutionSystem = "Het stelsel heeft geen oplossing" +NoSolutionEquation = "De vergelijking heeft geen oplossing" +NoSolutionInterval = "Geen oplossing gevonden binnen het interval" +EnterEquation = "Voer een vergelijking in" +InfiniteNumberOfSolutions = "Er is een oneindig aantal oplossingen" +ApproximateSolutionIntervalInstruction0= "Voer het interval in om in te zoeken" +ApproximateSolutionIntervalInstruction1= "naar een benaderende oplossing" +OnlyFirstSolutionsDisplayed0 = "Alleen de eerste tien oplossingen" +OnlyFirstSolutionsDisplayed1 = "worden weergegeven" +PolynomeHasNoRealSolution0 = "De polynoom heeft geen" +PolynomeHasNoRealSolution1 = "reële wortel" +PredefinedVariablesUsedLeft = "Gebruikte " +PredefinedVariablesUsedRight = "opgeslagen variabelen" +PredefinedVariablesIgnoredLeft = "Genegeerde" +PredefinedVariablesIgnoredRight = " opgeslagen variabelen" diff --git a/apps/solver/base.pt.i18n b/apps/solver/base.pt.i18n index 94b3df314..356eaa24f 100644 --- a/apps/solver/base.pt.i18n +++ b/apps/solver/base.pt.i18n @@ -11,17 +11,17 @@ TooManyVariables = "Existem muitas incógnitas" NonLinearSystem = "O sistema não é linear" Solution = "Solução" ApproximateSolution = "Solução aproximada" -SearchInverval = "Intervalo de busca" +SearchInverval = "Intervalo de pesquisa" NoSolutionSystem = "O sistema não tem solução" NoSolutionEquation = "A equação não tem solução" -NoSolutionInterval = "Nenhuma solução encontrada em el intervalo" +NoSolutionInterval = "Nenhuma solução encontrada no intervalo" EnterEquation = "Digite uma equação" InfiniteNumberOfSolutions = "Existe uma infinidade de soluções" ApproximateSolutionIntervalInstruction0= "Digite o intervalo para procurar" ApproximateSolutionIntervalInstruction1= "uma solução aproximada" -OnlyFirstSolutionsDisplayed0 = "Somente as 10 primeiras" +OnlyFirstSolutionsDisplayed0 = "Apenas as 10 primeiras" OnlyFirstSolutionsDisplayed1 = "soluções são exibidas" -PolynomeHasNoRealSolution0 = "O polinômio não tem" +PolynomeHasNoRealSolution0 = "O polinómio não tem" PolynomeHasNoRealSolution1 = "nenhuma raiz real" PredefinedVariablesUsedLeft = "Variáveis " PredefinedVariablesUsedRight = "pré-definidas utilizadas" diff --git a/apps/solver/equation.cpp b/apps/solver/equation.cpp index ef578eac7..80d2a4cab 100644 --- a/apps/solver/equation.cpp +++ b/apps/solver/equation.cpp @@ -15,13 +15,17 @@ using namespace Shared; namespace Solver { bool Equation::containsIComplex(Context * context) const { - return expressionClone().recursivelyMatches([](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Constant && static_cast(e).isIComplex(); }, context, true); + return expressionClone().recursivelyMatches([](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Constant && static_cast(e).isIComplex(); }, context); } Expression Equation::Model::standardForm(const Storage::Record * record, Context * context, bool replaceFunctionsButNotSymbols) const { Expression * returnedExpression = replaceFunctionsButNotSymbols ? &m_standardFormWithReplacedFunctionsButNotSymbols : &m_standardFormWithReplacedFunctionsAndSymbols; if (returnedExpression->isUninitialized()) { - const Expression expressionInputWithoutFunctions = Expression::ExpressionWithoutSymbols(expressionClone(record), context, replaceFunctionsButNotSymbols); + Expression expressionInputWithoutFunctions = Expression::ExpressionWithoutSymbols(expressionClone(record), context, replaceFunctionsButNotSymbols); + if (expressionInputWithoutFunctions.isUninitialized()) { + // The expression is circularly-defined + expressionInputWithoutFunctions = Undefined::Builder(); + } EmptyContext emptyContext; Context * contextToUse = replaceFunctionsButNotSymbols ? &emptyContext : context; @@ -40,8 +44,7 @@ Expression Equation::Model::standardForm(const Storage::Record * record, Context [](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Undefined || e.type() == ExpressionNode::Type::Infinity || Expression::IsMatrix(e, context); }, - contextToUse, - true)) + contextToUse)) { *returnedExpression = Undefined::Builder(); } else if (expressionRed.type() == ExpressionNode::Type::Equal) { diff --git a/apps/solver/equation_store.cpp b/apps/solver/equation_store.cpp index a490f4ad7..867a19d7c 100644 --- a/apps/solver/equation_store.cpp +++ b/apps/solver/equation_store.cpp @@ -124,7 +124,7 @@ EquationStore::Error EquationStore::exactSolve(Poincare::Context * context, bool assert(replaceFunctionsButNotSymbols != nullptr); *replaceFunctionsButNotSymbols = false; Error e = privateExactSolve(context, false); - if (e == Error::NoError && numberOfSolutions() == 0 && m_numberOfUserVariables > 0) { + if (m_numberOfUserVariables > 0 && (e != Error::NoError || numberOfSolutions() == 0)) { *replaceFunctionsButNotSymbols = true; e = privateExactSolve(context, true); } @@ -136,12 +136,27 @@ EquationStore::Error EquationStore::privateExactSolve(Poincare::Context * contex m_userVariablesUsed = !replaceFunctionsButNotSymbols; - // Step 0. Get unknown variables + // Step 1. Get unknown and user-defined variables m_variables[0][0] = 0; int numberOfVariables = 0; + + // TODO we look twice for variables but not the same, is there a way to not do the same work twice? + m_userVariables[0][0] = 0; + m_numberOfUserVariables = 0; + for (int i = 0; i < numberOfDefinedModels(); i++) { - const Expression e = modelForRecord(definedRecordAtIndex(i))->standardForm(context, replaceFunctionsButNotSymbols); - if (e.isUninitialized() || e.type() == ExpressionNode::Type::Undefined) { + Shared::ExpiringPointer eq = modelForRecord(definedRecordAtIndex(i)); + + /* Start by looking for user variables, so that if we escape afterwards, we + * know if it might be due to a user variable. */ + if (m_numberOfUserVariables < Expression::k_maxNumberOfVariables) { + const Expression eWithSymbols = eq->standardForm(context, true); + int varCount = eWithSymbols.getVariables(context, [](const char * symbol, Poincare::Context * context) { return context->expressionTypeForIdentifier(symbol, strlen(symbol)) == Poincare::Context::SymbolAbstractType::Symbol; }, (char *)m_userVariables, Poincare::SymbolAbstract::k_maxNameSize, m_numberOfUserVariables); + m_numberOfUserVariables = varCount < 0 ? Expression::k_maxNumberOfVariables : varCount; + } + + const Expression e = eq->standardForm(context, replaceFunctionsButNotSymbols); // The standard form is memoized so there is no double computation even if replaceFunctionsButNotSymbols is true. + if (e.isUninitialized() || e.type() == ExpressionNode::Type::Undefined || e.recursivelyMatches(Expression::IsMatrix, context, replaceFunctionsButNotSymbols ? ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions : ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition)) { return Error::EquationUndefined; } if (e.type() == ExpressionNode::Type::Unreal) { @@ -156,21 +171,6 @@ EquationStore::Error EquationStore::privateExactSolve(Poincare::Context * contex assert(numberOfVariables >= 0); } - // Step 1. Get user defined variables - // TODO used previously fetched variables? - m_userVariables[0][0] = 0; - m_numberOfUserVariables = 0; - for (int i = 0; i < numberOfDefinedModels(); i++) { - const Expression e = modelForRecord(definedRecordAtIndex(i))->standardForm(context, true); - assert(!e.isUninitialized() && e.type() != ExpressionNode::Type::Undefined && e.type() != ExpressionNode::Type::Unreal); - int varCount = e.getVariables(context, [](const char * symbol, Poincare::Context * context) { return context->expressionTypeForIdentifier(symbol, strlen(symbol)) == Poincare::Context::SymbolAbstractType::Symbol; }, (char *)m_userVariables, Poincare::SymbolAbstract::k_maxNameSize, m_numberOfUserVariables); - if (varCount < 0) { - m_numberOfUserVariables = Expression::k_maxNumberOfVariables; - break; - } - m_numberOfUserVariables = varCount; - } - // Step 2. Linear System? /* Create matrix coefficients and vector constants as: diff --git a/apps/solver/test/equation_store.cpp b/apps/solver/test/equation_store.cpp index 11952f66a..5a5534a6f 100644 --- a/apps/solver/test/equation_store.cpp +++ b/apps/solver/test/equation_store.cpp @@ -170,6 +170,9 @@ QUIZ_CASE(equation_and_symbolic_computation) { assert_solves_to({"c+d=5", "c-d=1"}, {"c=3", "d=2"}); + set("e", "8_g"); + assert_solves_to({"e+1=0"}, {"e=-1"}); + unset("a"); unset("b"); unset("c"); diff --git a/apps/statistics/Makefile b/apps/statistics/Makefile index 108964e88..d29964dfe 100644 --- a/apps/statistics/Makefile +++ b/apps/statistics/Makefile @@ -29,14 +29,7 @@ app_statistics_src = $(addprefix apps/statistics/,\ app_statistics_src += $(app_statistics_test_src) apps_src += $(app_statistics_src) -i18n_files += $(addprefix apps/statistics/,\ - base.de.i18n\ - base.en.i18n\ - base.es.i18n\ - base.fr.i18n\ - base.pt.i18n\ - base.hu.i18n\ -) +i18n_files += $(call i18n_without_universal_for,statistics/base) tests_src += $(addprefix apps/statistics/test/,\ store.cpp\ diff --git a/apps/statistics/base.it.i18n b/apps/statistics/base.it.i18n new file mode 100644 index 000000000..99aacf03d --- /dev/null +++ b/apps/statistics/base.it.i18n @@ -0,0 +1,25 @@ +StatsApp = "Statistica" +StatsAppCapital = "STATISTICA" +HistogramTab = "Istogramma" +BoxTab = "Box Plot" +Values1 = "Valori V1" +Values2 = "Valori V2" +Values3 = "Valori V3" +Sizes1 = "Frequenze N1" +Sizes2 = "Frequenze N2" +Sizes3 = "Frequenze N3" +ImportList = "Importare una lista" +Interval = " Intervallo " +Size = " Frequenza" +Frequency = "Relativa" +HistogramSet = "Regolazione dell'istogramma" +RectangleWidth = "Larghezza dei rettangoli" +BarStart = "Inizio della serie" +FirstQuartile = "Primo quartile" +Median = "Mediana" +ThirdQuartile = "Terzo quartile" +TotalSize = "Dimensione totale" +Range = "Ampiezza" +StandardDeviationSigma = "Deviazione standard σ" +SampleStandardDeviationS = "Dev. std campionaria s" +InterquartileRange = "Scarto interquartile" diff --git a/apps/statistics/base.nl.i18n b/apps/statistics/base.nl.i18n new file mode 100644 index 000000000..3649387c1 --- /dev/null +++ b/apps/statistics/base.nl.i18n @@ -0,0 +1,25 @@ +StatsApp = "Statistiek" +StatsAppCapital = "STATISTIEK" +HistogramTab = "Histogram" +BoxTab = "Box" +Values1 = "Waarden V1" +Values2 = "Waarden V2" +Values3 = "Waarden V3" +Sizes1 = "Frequenties N1" +Sizes2 = "Frequenties N2" +Sizes3 = "Frequenties N3" +ImportList = "Importeren uit een lijst" +Interval = " Interval " +Size = " Frequentie" +Frequency = "Proportie" +HistogramSet = "Histogram instellingen" +RectangleWidth = "Kolombreedte" +BarStart = "X start" +FirstQuartile = "Eerste kwartiel" +Median = "Mediaan" +ThirdQuartile = "Derde kwartiel" +TotalSize = "Totale omvang" +Range = "Bereik" +StandardDeviationSigma = "Standaardafwijking σ" +SampleStandardDeviationS = "Standaardafwijking s" +InterquartileRange = "Interkwartielafstand" diff --git a/apps/statistics/base.pt.i18n b/apps/statistics/base.pt.i18n index 5bb6e6044..3fd12f1e7 100644 --- a/apps/statistics/base.pt.i18n +++ b/apps/statistics/base.pt.i18n @@ -15,11 +15,11 @@ Frequency = "Relativa" HistogramSet = "Configurando o histograma" RectangleWidth = "Largura dos retângulos" BarStart = "Início da série" -FirstQuartile = "Quartil inferior" +FirstQuartile = "Primeiro quartil" Median = "Mediana" -ThirdQuartile = "Quartil superior" -TotalSize = "Número de itens" +ThirdQuartile = "Terceiro quartil" +TotalSize = "Dimensão" Range = "Amplitude" StandardDeviationSigma = "Desvio padrão σ" SampleStandardDeviationS = "Desvio padrão amostral s" -InterquartileRange = "Interquartil" +InterquartileRange = "Amplitude interquartil" diff --git a/apps/statistics/histogram_controller.cpp b/apps/statistics/histogram_controller.cpp index 42d5c52a3..47638f57a 100644 --- a/apps/statistics/histogram_controller.cpp +++ b/apps/statistics/histogram_controller.cpp @@ -50,15 +50,19 @@ bool HistogramController::handleEvent(Ion::Events::Event event) { void HistogramController::viewWillAppear() { MultipleDataViewController::viewWillAppear(); uint32_t storeChecksum = m_store->storeChecksum(); + bool initedRangeParameters = false; if (*m_storeVersion != storeChecksum) { *m_storeVersion = storeChecksum; initBarParameters(); initRangeParameters(); + initedRangeParameters = true; } uint32_t barChecksum = m_store->barChecksum(); if (*m_barVersion != barChecksum) { *m_barVersion = barChecksum; - initRangeParameters(); + if (!initedRangeParameters) { + initRangeParameters(); + } } uint32_t rangeChecksum = m_store->rangeChecksum(); if (*m_rangeVersion != rangeChecksum) { @@ -180,31 +184,33 @@ bool HistogramController::moveSelectionHorizontally(int deltaIndex) { return false; } -void HistogramController::preinitXRangeParameters() { +void HistogramController::preinitXRangeParameters(double * xMin) { /* Compute m_store's min and max values, hold them temporarily in the * CurveViewRange, for later use by initRangeParameters and * initBarParameters. Indeed, initRangeParameters will anyway alter the * CurveViewRange. The CurveViewRange setter methods take care of the case * where minValue >= maxValue. Moreover they compute the xGridUnit, which is * used by initBarParameters. */ - float minValue = FLT_MAX; - float maxValue = -FLT_MAX; + double minValue = DBL_MAX; + double maxValue = -DBL_MAX; for (int i = 0; i < Store::k_numberOfSeries; i ++) { if (!m_store->seriesIsEmpty(i)) { - minValue = std::min(minValue, m_store->minValue(i)); - maxValue = std::max(maxValue, m_store->maxValue(i)); + minValue = std::min(minValue, m_store->minValue(i)); + maxValue = std::max(maxValue, m_store->maxValue(i)); } } + assert(xMin != nullptr); + *xMin = minValue; m_store->setXMin(minValue); m_store->setXMax(maxValue); } void HistogramController::initRangeParameters() { assert(selectedSeriesIndex() >= 0 && m_store->sumOfOccurrences(selectedSeriesIndex()) > 0); - float barWidth = m_store->barWidth(); - preinitXRangeParameters(); - float xMin = m_store->firstDrawnBarAbscissa(); - float xMax = m_store->xMax() + barWidth; + double barWidth = m_store->barWidth(); + double xMin; + preinitXRangeParameters(&xMin); + double xMax = m_store->xMax() + barWidth; /* if a bar is represented by less than one pixel, we cap xMax */ if ((xMax - xMin)/barWidth > k_maxNumberOfBarsPerWindow) { xMax = xMin + k_maxNumberOfBarsPerWindow*barWidth; @@ -244,8 +250,9 @@ void HistogramController::initYRangeParameters(int series) { void HistogramController::initBarParameters() { assert(selectedSeriesIndex() >= 0 && m_store->sumOfOccurrences(selectedSeriesIndex()) > 0); - preinitXRangeParameters(); - m_store->setFirstDrawnBarAbscissa(m_store->xMin()); + double xMin; + preinitXRangeParameters(&xMin); + m_store->setFirstDrawnBarAbscissa(xMin); double barWidth = m_store->xGridUnit(); if (barWidth <= 0.0) { barWidth = 1.0; diff --git a/apps/statistics/histogram_controller.h b/apps/statistics/histogram_controller.h index a64006a3d..813964345 100644 --- a/apps/statistics/histogram_controller.h +++ b/apps/statistics/histogram_controller.h @@ -34,7 +34,7 @@ private: void highlightSelection() override; Responder * tabController() const override; void reloadBannerView() override; - void preinitXRangeParameters(); + void preinitXRangeParameters(double * xMin); void initRangeParameters(); void initYRangeParameters(int series); void initBarParameters(); diff --git a/apps/statistics/histogram_parameter_controller.cpp b/apps/statistics/histogram_parameter_controller.cpp index 6e58a2d8e..c9e3a8b45 100644 --- a/apps/statistics/histogram_parameter_controller.cpp +++ b/apps/statistics/histogram_parameter_controller.cpp @@ -37,66 +37,35 @@ double HistogramParameterController::parameterAtIndex(int index) { return index == 0 ? m_store->barWidth() : m_store->firstDrawnBarAbscissa(); } -bool HistogramParameterController::setParameterAtIndex(int parameterIndex, double f) { - assert(parameterIndex >= 0 && parameterIndex < k_numberOfCells); - if (parameterIndex == 0) { +bool HistogramParameterController::setParameterAtIndex(int parameterIndex, double value) { + assert(parameterIndex == 0 || parameterIndex == 1); + const bool setBarWidth = parameterIndex == 0; + + if (setBarWidth && value <= 0.0) { // The bar width cannot be negative - if (f <= 0.0f) { - Container::activeApp()->displayWarning(I18n::Message::ForbiddenValue); - return false; - } - - // There should be at least one value in the drawn bin - for (int i = 0; i < DoublePairStore::k_numberOfSeries; i++) { - if (m_store->firstDrawnBarAbscissa() <= m_store->maxValue(i)+f) { - break; - } else if (i == DoublePairStore::k_numberOfSeries - 1) { - Container::activeApp()->displayWarning(I18n::Message::ForbiddenValue); - return false; - } - } - - // The number of bars cannot be above the max - assert(DoublePairStore::k_numberOfSeries > 0); - double maxNewNumberOfBars = std::ceil((m_store->maxValue(0) - m_store->minValue(0))/f); - for (int i = 1; i < DoublePairStore::k_numberOfSeries; i++) { - double numberOfBars = std::ceil((m_store->maxValue(i) - m_store->minValue(i))/f); - if (maxNewNumberOfBars < numberOfBars) { - maxNewNumberOfBars = numberOfBars; - } - } - if (maxNewNumberOfBars > Store::k_maxNumberOfBars) { + Container::activeApp()->displayWarning(I18n::Message::ForbiddenValue); + return false; + } + + const double nextFirstDrawnBarAbscissa = setBarWidth ? m_store->firstDrawnBarAbscissa() : value; + const double nextBarWidth = setBarWidth ? value : m_store->barWidth(); + + // The number of bars cannot be above the max + assert(DoublePairStore::k_numberOfSeries > 0); + for (int i = 0; i < DoublePairStore::k_numberOfSeries; i++) { + const double min = setBarWidth ? m_store->minValue(i) : nextFirstDrawnBarAbscissa; + double numberOfBars = std::ceil((m_store->maxValue(i) - min)/nextBarWidth); + if (numberOfBars > Store::k_maxNumberOfBars) { Container::activeApp()->displayWarning(I18n::Message::ForbiddenValue); return false; } + } + if (setBarWidth) { // Set the bar width - m_store->setBarWidth(f); + m_store->setBarWidth(value); } else { - // The number of bars cannot be above the max - assert(DoublePairStore::k_numberOfSeries > 0); - double maxNewNumberOfBars = ceilf((m_store->maxValue(0) - f)/m_store->barWidth()); - for (int i = 1; i < DoublePairStore::k_numberOfSeries; i++) { - double numberOfBars = ceilf((m_store->maxValue(i) - f)/m_store->barWidth()); - if (maxNewNumberOfBars < numberOfBars) { - maxNewNumberOfBars = numberOfBars; - } - } - if (maxNewNumberOfBars > Store::k_maxNumberOfBars) { - Container::activeApp()->displayWarning(I18n::Message::ForbiddenValue); - return false; - } - // There should be at least one value in the drawn bin - for (int i = 0; i < DoublePairStore::k_numberOfSeries; i++) { - if (f <= m_store->maxValue(i)+m_store->barWidth()) { - break; - } else if (i == DoublePairStore::k_numberOfSeries - 1) { - Container::activeApp()->displayWarning(I18n::Message::ForbiddenValue); - return false; - } - } - // Set the first drawn bar abscissa - m_store->setFirstDrawnBarAbscissa(f); + m_store->setFirstDrawnBarAbscissa(value); } return true; } diff --git a/apps/statistics/store.cpp b/apps/statistics/store.cpp index dd9e07b6d..059467a97 100644 --- a/apps/statistics/store.cpp +++ b/apps/statistics/store.cpp @@ -31,9 +31,8 @@ uint32_t Store::barChecksum() const { /* Histogram bars */ void Store::setBarWidth(double barWidth) { - if (barWidth > 0.0) { - m_barWidth = barWidth; - } + assert(barWidth > 0.0); + m_barWidth = barWidth; } double Store::heightOfBarAtIndex(int series, int index) const { @@ -43,8 +42,8 @@ double Store::heightOfBarAtIndex(int series, int index) const { double Store::heightOfBarAtValue(int series, double value) const { double width = barWidth(); int barNumber = std::floor((value - m_firstDrawnBarAbscissa)/width); - double lowerBound = m_firstDrawnBarAbscissa + barNumber*width; - double upperBound = m_firstDrawnBarAbscissa + (barNumber+1)*width; + double lowerBound = m_firstDrawnBarAbscissa + ((double)barNumber)*width; + double upperBound = m_firstDrawnBarAbscissa + ((double)(barNumber+1))*width; return sumOfValuesBetween(series, lowerBound, upperBound); } diff --git a/apps/toolbox.it.i18n b/apps/toolbox.it.i18n new file mode 100644 index 000000000..1a4462380 --- /dev/null +++ b/apps/toolbox.it.i18n @@ -0,0 +1,167 @@ +Unit = "Unità" +UnitTimeMenu = "Tempo" +UnitTimeSecondMenu = "Secondo" +UnitTimeSecond = "Secondo" +UnitTimeSecondMilli = "Millisecondo" +UnitTimeSecondMicro = "Microsecondo" +UnitTimeSecondNano = "Nanosecondo" +UnitTimeMinute = "Minuto" +UnitTimeHour = "Ora" +UnitTimeDay = "Giorno" +UnitTimeWeek = "Settimana" +UnitTimeMonth = "Mese" +UnitTimeYear = "Anno" +UnitDistanceMenu = "Distanza" +UnitDistanceMeterMenu = "Metro" +UnitDistanceMeterKilo = "Chilometro" +UnitDistanceMeter = "Metro" +UnitDistanceMeterMilli = "Millimetro" +UnitDistanceMeterMicro = "Micrometro" +UnitDistanceMeterNano = "Nanometro" +UnitDistanceMeterPico = "Picometro" +UnitDistanceAstronomicalUnit = "Unità astronomica" +UnitDistanceLightYear = "Anno luce" +UnitDistanceParsec = "Parsec" +UnitMassMenu = "Massa" +UnitMassGramKilo = "Kilogrammo" +UnitMassGram = "Grammo" +UnitMassGramMilli = "Milligrammo" +UnitMassGramMicro = "Microgrammo" +UnitMassGramNano = "Nanogrammo" +UnitMassTonne = "Tonnellata" +UnitCurrentMenu = "Intensità di corrente elettrica" +UnitCurrentAmpere = "Ampere" +UnitCurrentAmpereMilli = "Milliampere" +UnitCurrentAmpereMicro = "Microampere" +UnitTemperatureMenu = "Temperatura" +UnitTemperatureKelvin = "Kelvin" +UnitAmountMenu = "Quantità de materia" +UnitAmountMole = "Mole" +UnitAmountMoleMilli = "Millimole" +UnitAmountMoleMicro = "Micromole" +UnitLuminousIntensityMenu = "Intensità luminosa" +UnitLuminousIntensityCandela = "Candela" +UnitFrequencyMenu = "Frequenza" +UnitFrequencyHertzGiga = "Gigahertz" +UnitFrequencyHertzMega = "Megahertz" +UnitFrequencyHertzKilo = "Kilohertz" +UnitFrequencyHertz = "Hertz" +UnitForceMenu = "Forza" +UnitForceNewtonKilo = "Kilonewton" +UnitForceNewton = "Newton" +UnitForceNewtonMilli = "Millinewton" +UnitPressureMenu = "Pressione" +UnitPressurePascal = "Pascal" +UnitPressurePascalHecto = "Hectopascal" +UnitPressureBar = "Bar" +UnitPressureAtm = "Atmosfera" +UnitEnergyMenu = "Energia" +UnitEnergyJouleMenu = "Joule" +UnitEnergyJouleKilo = "Kilojoule" +UnitEnergyJoule = "Joule" +UnitEnergyJouleMilli = "Millijoule" +UnitEnergyEletronVoltMenu = "Electronvolt" +UnitEnergyElectronVoltMega = "Megaelectronvolt" +UnitEnergyElectronVoltKilo = "Kiloelectronvolt" +UnitEnergyElectronVolt = "Electronvolt" +UnitEnergyElectronVoltMilli = "Millielectronvolt" +UnitPowerMenu = "Potenza" +UnitPowerWattGiga = "Gigawatt" +UnitPowerWattMega = "Megawatt" +UnitPowerWattKilo = "Kilowatt" +UnitPowerWatt = "Watt" +UnitPowerWattMilli = "Milliwatt" +UnitPowerWattMicro = "Microwatt" +UnitElectricChargeMenu = "Carica elettrica" +UnitChargeCoulomb = "Coulomb" +UnitPotentialMenu = "Tensione elettrica" +UnitPotentialVoltKilo = "Kilovolt" +UnitPotentialVolt = "Volt" +UnitPotentialVoltMilli = "Millivolt" +UnitPotentialVoltMicro = "Microvolt" +UnitCapacitanceMenu = "Capacità elettrica" +UnitCapacitanceFarad = "Farad" +UnitCapacitanceFaradMilli = "Millifarad" +UnitCapacitanceFaradMicro = "Microfarad" +UnitResistanceMenu = "Resistenza elettrica" +UnitResistanceOhmKilo = "Kiloohm" +UnitResistanceOhm = "Ohm" +UnitConductanceMenu = "Conduttanza elettrica" +UnitConductanceSiemens = "Siemens" +UnitConductanceSiemensMilli = "Millisiemens" +UnitMagneticFieldMenu = "Induzione elettromagnetica" +UnitMagneticFieldTesla = "Tesla" +InductanceMenu = "Induttanza" +UnitInductanceHenry = "Henry" +UnitSurfaceMenu = "Superficie" +UnitSurfaceHectar = "Ettaro" +UnitVolumeMenu = "Volume" +UnitVolumeLiter = "Litro" +UnitVolumeLiterDeci = "Decilitro" +UnitVolumeLiterCenti = "Centilitro" +UnitVolumeLiterMilli = "Millilitro" +Toolbox = "Toolbox" +AbsoluteValue = "Valore assoluto" +NthRoot = "Radice n-esima" +BasedLogarithm = "Logaritmo di base a" +Calculation = "Calcolo" +ComplexNumber = "Numeri complessi" +Combinatorics = "Calcolo combinatorio" +Arithmetic = "Aritmetica" +Matrices = "Matrici" +NewMatrix = "Nuova matrice" +Identity = "Matrice identità di dimensione n" +Lists = "Elenchi" +HyperbolicTrigonometry = "Funzioni iperboliche" +Fluctuation = "Intervallo di previsione" +DerivateNumber = "Derivata" +Integral = "Integrale" +Sum = "Somma" +Product = "Prodotto" +ComplexAbsoluteValue = "Modulo" +Agument = "Argomento" +RealPart = "Parte reale" +ImaginaryPart = "Parte immaginaria" +Conjugate = "Coniugato" +Combination = "Combinazione" +Permutation = "Permutazione" +GreatCommonDivisor = "MCD" +LeastCommonMultiple = "mcm" +Remainder = "Resto divisione p/q" +Quotient = "Quoziente divisione p/q" +Inverse = "Inversa" +Determinant = "Determinante" +Transpose = "Trasposta" +Trace = "Traccia" +Dimension = "Dimensione" +Sort = "Ordine crescente" +InvSort = "Ordine decrescente" +Maximum = "Massimo" +Minimum = "Minimo" +Floor = "Parte intera inferiore" +FracPart = "Parte frazionaria" +Ceiling = "Parte intera superiore" +Rounding = "Arrotonda a n decimali" +HyperbolicCosine = "Coseno iperbolico" +HyperbolicSine = "Seno iperbolico" +HyperbolicTangent = "Tangente iperbolica" +InverseHyperbolicCosine = "Coseno iperbolico inverso" +InverseHyperbolicSine = "Seno iperbolico inverso" +InverseHyperbolicTangent = "Tangente iperbolica inversa" +Prediction95 = "Intervallo di previsione al 95%" +Prediction = "Intervallo di previsione semplice" +Confidence = "Intervallo di confidenza" +RandomAndApproximation = "Aleatorietà e approssimazione" +RandomFloat = "Decimale aleatorio tra [0,1[" +RandomInteger = "Intero aleatorio tra [a,b]" +PrimeFactorDecomposition = "Scomposizione in fattori primi" +NormCDF = "P(X -#include -#include -#include -#include - -class VariableBoxEmptyController : public ViewController { -public: - VariableBoxEmptyController(); - enum class Type { - None = 0, - Expressions = 1, - Functions = 2 - }; - // View Controller - View * view() override; - DisplayParameter displayParameter() override { return DisplayParameter::DoNotShowOwnTitle; } - void viewDidDisappear() override; - - void setType(Type type); -private: - class VariableBoxEmptyView : public View, public Bordered { - public: - static constexpr const KDFont * k_font = KDFont::SmallFont; - VariableBoxEmptyView(); - void setMessages(I18n::Message * message); - void setLayout(Poincare::Layout layout); - void drawRect(KDContext * ctx, KDRect rect) const override; - constexpr static int k_numberOfMessages = 4; - private: - int numberOfSubviews() const override; - View * subviewAtIndex(int index) override; - void layoutSubviews(bool force = false) override; - constexpr static int k_layoutRowIndex = 2; - MessageTextView m_messages[k_numberOfMessages]; - ExpressionView m_layoutExample; - }; - VariableBoxEmptyView m_view; -}; - -#endif diff --git a/apps/variables.it.i18n b/apps/variables.it.i18n new file mode 100644 index 000000000..8871d3e51 --- /dev/null +++ b/apps/variables.it.i18n @@ -0,0 +1,10 @@ +Variables = "Variabili" +Expressions = "Espressioni" +Functions = "Funzioni" +EmptyExpressionBox0 = "Non avete definito nessuna variabile." +EmptyFunctionBox0 = "Non avete definito nessuna funzione." +EmptyExpressionBox1 = "Per definire una variabile, digitare :" +EmptyFunctionBox1 = "Per definire una funzione, digitare :" +EmptyExpressionBox2 = "Il nome della variabile può" +EmptyFunctionBox2 = "Il nome della funzione può" +EnableCharacters = "contenere : A..Z, a..z, 0..9 e _" diff --git a/apps/variables.nl.i18n b/apps/variables.nl.i18n new file mode 100644 index 000000000..06305f239 --- /dev/null +++ b/apps/variables.nl.i18n @@ -0,0 +1,10 @@ +Variables = "Variabelen" +Expressions = "Uitdrukkingen" +Functions = "Functies" +EmptyExpressionBox0 = "Je hebt geen variabelen gedefinieerd." +EmptyFunctionBox0 = "Je hebt geen functies gedefinieerd." +EmptyExpressionBox1 = "Om een variabele to definiëren, typ:" +EmptyFunctionBox1 = "Om een functie to definiëren, typ:" +EmptyExpressionBox2 = "De naam van de variabele kan bevatten:" +EmptyFunctionBox2 = "De naam van de functie kan bevatten:" +EnableCharacters = "A..Z, a..z, 0..9 en _" diff --git a/build/config.mak b/build/config.mak index 134da4f7f..5e12408d4 100644 --- a/build/config.mak +++ b/build/config.mak @@ -4,12 +4,12 @@ PLATFORM ?= device DEBUG ?= 0 HOME_DISPLAY_EXTERNALS ?= 1 -EPSILON_VERSION ?= 13.1.0 +EPSILON_VERSION ?= 14.4.0 OMEGA_VERSION ?= 1.20.0 # USERNAME ?= N/A -EPSILON_APPS ?= calculation rpn graph code statistics probability solver atom sequence regression settings external -EPSILON_I18N ?= en fr es de pt hu -# EPSILON_I18N ?= en fr es de pt hu +EPSILON_APPS ?= calculation graph code statistics probability solver atom sequence regression settings external +#EPSILON_I18N ?= en fr nl pt it de es hu +EPSILON_I18N ?= en fr EPSILON_GETOPT ?= 0 EPSILON_TELEMETRY ?= 0 ESCHER_LOG_EVENTS_BINARY ?= 0 diff --git a/escher/Makefile b/escher/Makefile index 61bced8c3..387fe0627 100644 --- a/escher/Makefile +++ b/escher/Makefile @@ -66,6 +66,7 @@ escher_src += $(addprefix escher/src/,\ message_text_view.cpp \ metric.cpp \ modal_view_controller.cpp \ + modal_view_empty_controller.cpp \ nested_menu_controller.cpp \ palette.cpp \ pointer_text_view.cpp \ diff --git a/escher/include/escher/layout_field.h b/escher/include/escher/layout_field.h index 7476727d2..d8c4694b1 100644 --- a/escher/include/escher/layout_field.h +++ b/escher/include/escher/layout_field.h @@ -87,11 +87,7 @@ private: bool selectionIsEmpty() const; void deleteSelection(); void invalidateInsertionCursor() { m_insertionCursor = Poincare::LayoutCursor(); } - void updateInsertionCursor() { - if (!m_insertionCursor.isDefined()) { - m_insertionCursor = m_cursor; - } - } + void updateInsertionCursor(); private: int numberOfSubviews() const override { return 2; } diff --git a/escher/include/escher/message_table_cell.h b/escher/include/escher/message_table_cell.h index 10a0e795b..c43985d6d 100644 --- a/escher/include/escher/message_table_cell.h +++ b/escher/include/escher/message_table_cell.h @@ -13,8 +13,12 @@ public: void setMessage(I18n::Message message); virtual void setTextColor(KDColor color); void setMessageFont(const KDFont * font); + void setBackgroundColor(KDColor color); +protected: + KDColor backgroundColor() const override { return m_backgroundColor; } private: MessageTextView m_messageTextView; + KDColor m_backgroundColor; }; #endif diff --git a/escher/include/escher/message_tree.h b/escher/include/escher/message_tree.h index f6625cb3a..3983cc3f6 100644 --- a/escher/include/escher/message_tree.h +++ b/escher/include/escher/message_tree.h @@ -10,7 +10,7 @@ public: m_numberOfChildren(numberOfChildren) { }; - virtual const MessageTree * children(int index) const = 0; + virtual const MessageTree * childAtIndex(int index) const = 0; I18n::Message label() const { return m_label; } int numberOfChildren() const { return m_numberOfChildren; } bool isNull() const { return (m_label == (I18n::Message)0); } diff --git a/escher/include/escher/modal_view_empty_controller.h b/escher/include/escher/modal_view_empty_controller.h new file mode 100644 index 000000000..cf175aab4 --- /dev/null +++ b/escher/include/escher/modal_view_empty_controller.h @@ -0,0 +1,37 @@ +#ifndef ESCHER_EMPTY_MODAL_VIEW_EMPTY_CONTROLLER_H +#define ESCHER_EMPTY_MODAL_VIEW_EMPTY_CONTROLLER_H + +#include +#include +#include +#include + +class ModalViewEmptyController : public ViewController { +public: + ModalViewEmptyController() : ViewController(nullptr) {} + void setMessages(I18n::Message * messages); + // View Controller + DisplayParameter displayParameter() override { return DisplayParameter::DoNotShowOwnTitle; } +protected: + class ModalViewEmptyView : public View, public Bordered { + public: + constexpr static const KDFont * k_font = KDFont::SmallFont; + void initMessageViews(); + void setMessages(I18n::Message * message); + void drawRect(KDContext * ctx, KDRect rect) const override; + private: + constexpr static int k_expressionViewRowIndex = 2; + constexpr static KDColor k_backgroundColor = Palette::WallScreen; + int numberOfSubviews() const override; + View * subviewAtIndex(int index) override; + void layoutSubviews(bool force = false) override; + virtual int numberOfMessageTextViews() const = 0; + virtual MessageTextView * messageTextViewAtIndex(int index) = 0; + bool hasExpressionView() const { + return const_cast(this)->expressionView() != nullptr; + } + virtual ExpressionView * expressionView() { return nullptr; } + }; +}; + +#endif diff --git a/escher/include/escher/nested_menu_controller.h b/escher/include/escher/nested_menu_controller.h index 0037d9c90..6711c5eb1 100644 --- a/escher/include/escher/nested_menu_controller.h +++ b/escher/include/escher/nested_menu_controller.h @@ -7,10 +7,11 @@ #include #include -class NestedMenuController : public StackViewController, public ListViewDataSource, public SelectableTableViewDataSource { +class NestedMenuController : public StackViewController, public ListViewDataSource, public SelectableTableViewDataSource, public SelectableTableViewDelegate { public: NestedMenuController(Responder * parentResponder, I18n::Message title = (I18n::Message)0); void setSender(InputEventHandler * sender) { m_sender = sender; } + void setTitle(I18n::Message title); // StackViewController bool handleEvent(Ion::Events::Event event) override; @@ -48,6 +49,7 @@ protected: public: ListController(Responder * parentResponder, SelectableTableView * tableView, I18n::Message title); const char * title() override; + void setTitle(I18n::Message title) { m_title = title; } View * view() override; void didBecomeFirstResponder() override; void setFirstSelectedRow(int firstSelectedRow); diff --git a/escher/include/escher/palette.h b/escher/include/escher/palette.h new file mode 100644 index 000000000..73d447783 --- /dev/null +++ b/escher/include/escher/palette.h @@ -0,0 +1,150 @@ +#ifndef ESCHER_PALETTE_H +#define ESCHER_PALETTE_H + +#include +#include + +class Palette { +public: + constexpr static KDColor PrimaryText = KDColor::RGB24(0x000000); + constexpr static KDColor SecondaryText = KDColor::RGB24(0x6e6e6e); + constexpr static KDColor AccentText = KDColor::RGB24(0x00857f); + constexpr static KDColor ApproximateSignText = KDColor::RGB24(0x595959); + constexpr static KDColor ApproximateExpressionText = KDColor::RGB24(0x595959); + constexpr static KDColor BackgroundHard = KDColor::RGB24(0xffffff); + constexpr static KDColor BackgroundApps = KDColor::RGB24(0xfafafa); + constexpr static KDColor BackgroundAppsSecondary = KDColor::RGB24(0xf0f0f0); + constexpr static KDColor Toolbar = KDColor::RGB24(0xc03535); + constexpr static KDColor ToolbarText = KDColor::RGB24(0xffffff); + constexpr static KDColor ExpressionInputBackground = KDColor::RGB24(0xe0e0e0); + constexpr static KDColor ExpressionInputBorder = KDColor::RGB24(0xd9d9d9); + constexpr static KDColor GridPrimaryLine = KDColor::RGB24(0xd9d9d9); + constexpr static KDColor GridSecondaryLine = KDColor::RGB24(0xf5f5f5); + constexpr static KDColor Battery = KDColor::RGB24(0xffffff); + constexpr static KDColor BatteryInCharge = KDColor::RGB24(0x179e1f); + constexpr static KDColor BatteryLow = KDColor::RGB24(0x992321); + constexpr static KDColor ScrollBarForeground = KDColor::RGB24(0x4a4a4a); + constexpr static KDColor ScrollBarBackground = KDColor::RGB24(0xd9d9d9); + constexpr static KDColor Control = KDColor::RGB24(0x00857f); + constexpr static KDColor ControlEnabled = KDColor::RGB24(0x00b2b0); + constexpr static KDColor ControlDisabled = KDColor::RGB24(0x9e9e9e); + constexpr static KDColor CalculationBackgroundOdd = KDColor::RGB24(0xfafafa); + constexpr static KDColor CalculationBackgroundEven = KDColor::RGB24(0xffffff); + constexpr static KDColor CalculationEmptyBox = KDColor::RGB24(0xc4c4c4); + constexpr static KDColor CalculationEmptyBoxNeeded = KDColor::RGB24(0x00857f); + constexpr static KDColor CalculationTrigoAndComplexForeground = KDColor::RGB24(0xff000c); + constexpr static KDColor CodeBackground = KDColor::RGB24(0xffffff); + constexpr static KDColor CodeBackgroundSelected = KDColor::RGB24(0xe0e0e0); + constexpr static KDColor CodeText = KDColor::RGB24(0x000000); + constexpr static KDColor CodeComment = KDColor::RGB24(0x999988); + constexpr static KDColor CodeNumber = KDColor::RGB24(0x009999); + constexpr static KDColor CodeKeyword = KDColor::RGB24(0xff000c); + constexpr static KDColor CodeOperator = KDColor::RGB24(0xd73a49); + constexpr static KDColor CodeString = KDColor::RGB24(0x032f62); + constexpr static KDColor CodeGutterViewBackground = KDColor::RGB24(0xE4E6E7); + constexpr static KDColor ProbabilityCurve = KDColor::RGB24(0x00857f); + constexpr static KDColor ProbabilityCellBorder = KDColor::RGB24(0xececec); + constexpr static KDColor ProbabilityHistogramBar = KDColor::RGB24(0xd9d9d9); + constexpr static KDColor StatisticsBox = KDColor::RGB24(0x00857f); + constexpr static KDColor StatisticsBoxVerticalLine = KDColor::RGB24(0xd9d9d9); + constexpr static KDColor StatisticsSelected = KDColor::RGB24(0x00857f); + constexpr static KDColor StatisticsNotSelected = KDColor::RGB24(0xf5f5f5); + constexpr static KDColor GraphTangent = KDColor::RGB24(0x595959); + constexpr static KDColor SubMenuBackground = KDColor::RGB24(0xe0e0e0); + constexpr static KDColor SubMenuBorder = KDColor::RGB24(0xfafafa); + constexpr static KDColor SubMenuText = KDColor::RGB24(0x000000); + constexpr static KDColor ToolboxHeaderBackground = KDColor::RGB24(0x4a4a4a); + constexpr static KDColor ToolboxHeaderText = KDColor::RGB24(0xffffff); + constexpr static KDColor ToolboxHeaderBorder = KDColor::RGB24(0x4a4a4a); + constexpr static KDColor ToolboxBackground = KDColor::RGB24(0x000000); + constexpr static KDColor ListCellBackground = KDColor::RGB24(0xffffff); + constexpr static KDColor ListCellBackgroundSelected = KDColor::RGB24(0xe0e0e0); + constexpr static KDColor ListCellBorder = KDColor::RGB24(0xededef); + constexpr static KDColor ButtonBackground = KDColor::RGB24(0xe6e6e6); + constexpr static KDColor ButtonBackgroundSelected = KDColor::RGB24(0xc9c9c9); + constexpr static KDColor ButtonBackgroundSelectedHighContrast = KDColor::RGB24(0x00b2b0); + constexpr static KDColor ButtonBorder = KDColor::RGB24(0xadadad); + constexpr static KDColor ButtonRowBorder = KDColor::RGB24(0xd9d9d9); + constexpr static KDColor ButtonBorderOut = KDColor::RGB24(0xf5f5f5); + constexpr static KDColor ButtonShadow = KDColor::RGB24(0x003833); + constexpr static KDColor ButtonText = KDColor::RGB24(0x000000); + constexpr static KDColor TabBackground = KDColor::RGB24(0x4a4a4a); + constexpr static KDColor TabBackgroundSelected = KDColor::RGB24(0x757575); + constexpr static KDColor TabBackgroundActive = KDColor::RGB24(0xfafafa); + constexpr static KDColor TabBackgroundSelectedAndActive = KDColor::RGB24(0xe3e3e3); + constexpr static KDColor TabText = KDColor::RGB24(0xffffff); + constexpr static KDColor TabTextActive = KDColor::RGB24(0x000000); + constexpr static KDColor SubTabBackground = KDColor::RGB24(0xe0e0e0); + constexpr static KDColor SubTabBackgroundSelected = KDColor::RGB24(0xd1d1d1); + constexpr static KDColor SubTabText = KDColor::RGB24(0x000000); + constexpr static KDColor BannerFirstBackground = KDColor::RGB24(0x4a4a4a); + constexpr static KDColor BannerFirstBorder = KDColor::RGB24(0x4a4a4a); + constexpr static KDColor BannerFirstText = KDColor::RGB24(0xffffff); + constexpr static KDColor BannerFirstVariantBackground = KDColor::RGB24(0x4a4a4a); + constexpr static KDColor BannerFirstVariantBorder = KDColor::RGB24(0xfafafa); + constexpr static KDColor BannerFirstVariantText = KDColor::RGB24(0xffffff); + constexpr static KDColor BannerSecondBackground = KDColor::RGB24(0xe0e0e0); + constexpr static KDColor BannerSecondBorder = KDColor::RGB24(0xfafafa); + constexpr static KDColor BannerSecondText = KDColor::RGB24(0x000000); + constexpr static KDColor HomeBackground = KDColor::RGB24(0xffffff); + constexpr static KDColor HomeCellBackground = KDColor::RGB24(0xffffff); + constexpr static KDColor HomeCellBackgroundActive = KDColor::RGB24(0x4a4a4a); + constexpr static KDColor HomeCellText = KDColor::RGB24(0x000000); + constexpr static KDColor HomeCellTextActive = KDColor::RGB24(0xffffff); + constexpr static KDColor HomeCellTextExternal = KDColor::RGB24(0x008f87); + constexpr static KDColor HomeCellTextExternalActive = KDColor::RGB24(0x6fe6df); + constexpr static KDColor AtomUnknown = KDColor::RGB24(0xeeeeee); + constexpr static KDColor AtomText = KDColor::RGB24(0x000000); + constexpr static KDColor AtomAlkaliMetal = KDColor::RGB24(0xffaa00); + constexpr static KDColor AtomAlkaliEarthMetal = KDColor::RGB24(0xf6f200); + constexpr static KDColor AtomLanthanide = KDColor::RGB24(0xffaa8b); + constexpr static KDColor AtomActinide = KDColor::RGB24(0xdeaacd); + constexpr static KDColor AtomTransitionMetal = KDColor::RGB24(0xde999c); + constexpr static KDColor AtomPostTransitionMetal = KDColor::RGB24(0x9cbaac); + constexpr static KDColor AtomMetalloid = KDColor::RGB24(0x52ce8b); + constexpr static KDColor AtomHalogen = KDColor::RGB24(0x00debd); + constexpr static KDColor AtomReactiveNonmetal = KDColor::RGB24(0x00ee00); + constexpr static KDColor AtomNobleGas = KDColor::RGB24(0x8baaff); + constexpr static KDColor AtomTableLines = KDColor::RGB24(0x323532); + constexpr static KDColor YellowDark = KDColor::RGB24(0xffb734); + constexpr static KDColor YellowLight = KDColor::RGB24(0xffcc7b); + constexpr static KDColor PurpleBright = KDColor::RGB24(0x656975); + constexpr static KDColor PurpleDark = KDColor::RGB24(0x414147); + constexpr static KDColor GreyWhite = KDColor::RGB24(0xf5f5f5); + constexpr static KDColor GreyBright = KDColor::RGB24(0xececec); + constexpr static KDColor GreyMiddle = KDColor::RGB24(0xd9d9d9); + constexpr static KDColor GreyDark = KDColor::RGB24(0xa7a7a7); + constexpr static KDColor GreyVeryDark = KDColor::RGB24(0x8c8c8c); + constexpr static KDColor Select = KDColor::RGB24(0xd4d7e0); + constexpr static KDColor SelectDark = KDColor::RGB24(0xb0b8d8); + constexpr static KDColor WallScreen = KDColor::RGB24(0xf7f9fa); + constexpr static KDColor WallScreenDark = KDColor::RGB24(0xe0e6ed); + constexpr static KDColor SubTab = KDColor::RGB24(0xb8bbc5); + constexpr static KDColor LowBattery = KDColor::RGB24(0xf30211); + constexpr static KDColor Red = KDColor::RGB24(0xff000c); + constexpr static KDColor RedLight = KDColor::RGB24(0xfe6363); + constexpr static KDColor Magenta = KDColor::RGB24(0xff0588); + constexpr static KDColor Turquoise = KDColor::RGB24(0x60c1ec); + constexpr static KDColor Pink = KDColor::RGB24(0xffabb6); + constexpr static KDColor Blue = KDColor::RGB24(0x5075f2); + constexpr static KDColor BlueLight = KDColor::RGB24(0x718fee); + constexpr static KDColor Orange = KDColor::RGB24(0xfe871f); + constexpr static KDColor Green = KDColor::RGB24(0x50c102); + constexpr static KDColor GreenLight = KDColor::RGB24(0x52db8f); + constexpr static KDColor Brown = KDColor::RGB24(0x8d7350); + constexpr static KDColor Purple = KDColor::RGB24(0x6e2d79); + constexpr static KDColor BlueishGrey = KDColor::RGB24(0x919ea4); + constexpr static KDColor Cyan = KDColorBlue; + constexpr static KDColor DataColor[] = {Red, Blue, Green, YellowDark, Magenta, Turquoise, Pink, Orange}; + constexpr static KDColor DataColorLight[] = {RedLight, BlueLight, GreenLight, YellowLight}; + constexpr static KDColor AtomColor[] = { + AtomUnknown, AtomAlkaliMetal, AtomAlkaliEarthMetal, AtomLanthanide, AtomActinide, AtomTransitionMetal, + AtomPostTransitionMetal, AtomMetalloid, AtomHalogen, AtomReactiveNonmetal, AtomNobleGas + }; + + constexpr static size_t numberOfDataColors() { return sizeof(DataColor)/sizeof(KDColor); } + constexpr static size_t numberOfLightDataColors() { return sizeof(DataColorLight)/sizeof(KDColor); } + static KDColor nextDataColor(int * colorIndex); +}; + +#endif diff --git a/escher/include/escher/selectable_table_view_delegate.h b/escher/include/escher/selectable_table_view_delegate.h index c7e20ee7b..b94d6b20f 100644 --- a/escher/include/escher/selectable_table_view_delegate.h +++ b/escher/include/escher/selectable_table_view_delegate.h @@ -13,6 +13,7 @@ public: * the previous selected cell. We might implement different course of action * when the selection change is 'real' or within temporary selection. */ virtual void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false) {} + virtual void tableViewDidChangeSelectionAndDidScroll(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false) {} }; #endif diff --git a/escher/include/escher/stack_view_controller.h b/escher/include/escher/stack_view_controller.h index e3868ddf1..29034a94b 100644 --- a/escher/include/escher/stack_view_controller.h +++ b/escher/include/escher/stack_view_controller.h @@ -16,8 +16,8 @@ public: void push(ViewController * vc, KDColor textColor = Palette::SubMenuText, KDColor backgroundColor = Palette::SubMenuBackground, KDColor separatorColor = Palette::SubMenuBorder); void pop(); - int depth(); - View * view() override; + int depth() const { return m_numberOfChildren; } + View * view() override { return &m_view; } ViewController * topViewController(); const char * title() override; bool handleEvent(Ion::Events::Event event) override; diff --git a/escher/include/escher/table_cell.h b/escher/include/escher/table_cell.h index e46da7bf6..d2d2b086c 100644 --- a/escher/include/escher/table_cell.h +++ b/escher/include/escher/table_cell.h @@ -23,8 +23,9 @@ public: virtual View * subAccessoryView() const; void drawRect(KDContext * ctx, KDRect rect) const override; protected: - virtual KDCoordinate labelMargin() const { return Metric::TableCellHorizontalMargin; } - virtual KDCoordinate accessoryMargin() const { return Metric::TableCellHorizontalMargin; } + virtual KDColor backgroundColor() const { return KDColorWhite; } + virtual KDCoordinate labelMargin() const { return k_horizontalMargin; } + virtual KDCoordinate accessoryMargin() const { return k_horizontalMargin; } int numberOfSubviews() const override; View * subviewAtIndex(int index) override; void layoutSubviews(bool force = false) override; diff --git a/escher/include/escher/table_view.h b/escher/include/escher/table_view.h index b7eb77ae5..106c46261 100644 --- a/escher/include/escher/table_view.h +++ b/escher/include/escher/table_view.h @@ -58,8 +58,6 @@ protected: * coordinates that refer to the data source entire table */ int absoluteColumnNumberFromSubviewIndex(int index) const; int absoluteRowNumberFromSubviewIndex(int index) const; - int numberOfFullyDisplayableRows() const; - int numberOfFullyDisplayableColumns() const; int typeOfSubviewAtIndex(int index) const; /* This method transform a index (of subview for instance) into an index * refering to the set of cells of type "type". */ diff --git a/escher/include/escher/text_area.h b/escher/include/escher/text_area.h index 640511dc1..41c177a41 100644 --- a/escher/include/escher/text_area.h +++ b/escher/include/escher/text_area.h @@ -117,11 +117,12 @@ protected: const char * editedText() const override { return m_text.text(); } size_t editedTextLength() const override { return m_text.textLength(); } const Text * getText() const { return &m_text; } - bool insertTextAtLocation(const char * text, char * location) override; + bool insertTextAtLocation(const char * text, char * location, int textLength = -1) override; void moveCursorGeo(int deltaX, int deltaY); bool removePreviousGlyph() override; bool removeEndOfLine() override; bool removeStartOfLine(); + size_t removeText(const char * start, const char * end); size_t deleteSelection() override; protected: KDRect glyphFrameAtPosition(const char * text, const char * position) const override; diff --git a/escher/include/escher/text_field.h b/escher/include/escher/text_field.h index 4f0a8928a..f7901b7f8 100644 --- a/escher/include/escher/text_field.h +++ b/escher/include/escher/text_field.h @@ -74,7 +74,7 @@ protected: /* If the text to be appended is too long to be added without overflowing the * buffer, nothing is done (not even adding few letters from the text to reach * the maximum buffer capacity) and false is returned. */ - bool insertTextAtLocation(const char * text, char * location) override; + bool insertTextAtLocation(const char * text, char * location, int textLength = -1) override; KDSize minimalSizeForOptimalDisplay() const override; bool removePreviousGlyph() override; bool removeEndOfLine() override; diff --git a/escher/include/escher/text_input.h b/escher/include/escher/text_input.h index 82bb60106..34c75dcd8 100644 --- a/escher/include/escher/text_input.h +++ b/escher/include/escher/text_input.h @@ -49,7 +49,7 @@ protected: // Virtual text get/add/remove virtual const char * text() const = 0; - virtual bool insertTextAtLocation(const char * text, char * location) = 0; + virtual bool insertTextAtLocation(const char * text, char * location, int textLength = -1) = 0; virtual bool removePreviousGlyph() = 0; virtual bool removeEndOfLine() = 0; diff --git a/escher/include/escher/toolbox_message_tree.h b/escher/include/escher/toolbox_message_tree.h index c4a70d77c..835bccde5 100644 --- a/escher/include/escher/toolbox_message_tree.h +++ b/escher/include/escher/toolbox_message_tree.h @@ -24,7 +24,7 @@ public: N, true); } - const MessageTree * children(int index) const override { return &m_children[index]; } + const MessageTree * childAtIndex(int index) const override { return &m_children[index]; } I18n::Message text() const { return m_text; } I18n::Message insertedText() const { return m_insertedText; } bool stripInsertedText() const { return m_stripInsertedText; } diff --git a/escher/src/expression_view.cpp b/escher/src/expression_view.cpp index 35f0f65cf..75dab0d4e 100644 --- a/escher/src/expression_view.cpp +++ b/escher/src/expression_view.cpp @@ -18,13 +18,11 @@ ExpressionView::ExpressionView(float horizontalAlignment, float verticalAlignmen } bool ExpressionView::setLayout(Layout layoutR) { - /* TODO: this would avoid some useless redrawing. However, when we call - * setLayout after raising an Exception that led to erase all - * Poincare::TreePool, accessing m_layout will result in an ACCESS ERROR. - * How do we avoid that? */ - /*if (m_layout.isIdenticalTo(layoutR)) { + if (!m_layout.wasErasedByException() && m_layout.isIdenticalTo(layoutR)) { + /* Check m_layout.wasErasedByException(), otherwise accessing m_layout would + * result in an ACCESS ERROR. */ return false; - }*/ + } m_layout = layoutR; markRectAsDirty(bounds()); return true; diff --git a/escher/src/input_event_handler.cpp b/escher/src/input_event_handler.cpp index ee18a4025..f8da2b112 100644 --- a/escher/src/input_event_handler.cpp +++ b/escher/src/input_event_handler.cpp @@ -5,10 +5,14 @@ #include bool InputEventHandler::handleBoxEvent(Ion::Events::Event event) { + if (m_inputEventHandlerDelegate == nullptr) { + return false; + } NestedMenuController * box = nullptr; - if (m_inputEventHandlerDelegate) { - box = event == Ion::Events::Toolbox ? m_inputEventHandlerDelegate->toolboxForInputEventHandler(this) : box; - box = event == Ion::Events::Var ? m_inputEventHandlerDelegate->variableBoxForInputEventHandler(this) : box; + if (event == Ion::Events::Toolbox) { + box = m_inputEventHandlerDelegate->toolboxForInputEventHandler(this); + } else if (event == Ion::Events::Var) { + box = m_inputEventHandlerDelegate->variableBoxForInputEventHandler(this); } if (box) { box->setSender(this); diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index 808da3cb8..77ee13275 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -42,13 +43,16 @@ bool LayoutField::ContentView::setEditing(bool isEditing) { void LayoutField::ContentView::useInsertionCursor() { if (m_insertionCursor.isDefined()) { + m_cursor.layout().removeGreySquaresFromAllMatrixAncestors(); m_cursor = m_insertionCursor; + m_cursor.layout().addGreySquaresToAllMatrixAncestors(); } } void LayoutField::ContentView::clearLayout() { HorizontalLayout h = HorizontalLayout::Builder(); if (m_expressionView.setLayout(h)) { + resetSelection(); m_cursor.setLayout(h); } } @@ -234,6 +238,17 @@ void LayoutField::ContentView::deleteSelection() { resetSelection(); } +void LayoutField::ContentView::updateInsertionCursor() { + if (!m_insertionCursor.isDefined()) { + Layout l = m_cursor.layout(); + if (l.type() == LayoutNode::Type::EmptyLayout && static_cast(l).color() == EmptyLayoutNode::Color::Grey) { + // Don't set m_insertionCursor pointing to a layout which might disappear + return; + } + m_insertionCursor = m_cursor; + } +} + View * LayoutField::ContentView::subviewAtIndex(int index) { assert(0 <= index && index < numberOfSubviews()); View * m_views[] = {&m_expressionView, &m_cursorView}; diff --git a/escher/src/message_table_cell.cpp b/escher/src/message_table_cell.cpp index 0f368d6f4..648ef9068 100644 --- a/escher/src/message_table_cell.cpp +++ b/escher/src/message_table_cell.cpp @@ -4,7 +4,8 @@ MessageTableCell::MessageTableCell(I18n::Message label, const KDFont * font, Layout layout) : TableCell(layout), - m_messageTextView(font, label, 0, 0.5, Palette::PrimaryText, Palette::ListCellBackground) + m_messageTextView(font, label, 0, 0.5, Palette::PrimaryText, Palette::ListCellBackground), + m_backgroundColor(KDColorWhite) { } @@ -31,3 +32,8 @@ void MessageTableCell::setMessageFont(const KDFont * font) { m_messageTextView.setFont(font); layoutSubviews(); } + +void MessageTableCell::setBackgroundColor(KDColor color) { + m_backgroundColor = color; + m_messageTextView.setBackgroundColor(color); +} diff --git a/escher/src/modal_view_empty_controller.cpp b/escher/src/modal_view_empty_controller.cpp new file mode 100644 index 000000000..23f370a6c --- /dev/null +++ b/escher/src/modal_view_empty_controller.cpp @@ -0,0 +1,81 @@ +#include +#include +#include + +constexpr KDColor ModalViewEmptyController::ModalViewEmptyView::k_backgroundColor; + +// ModalViewEmptyController::ModalViewEmptyView +void ModalViewEmptyController::ModalViewEmptyView::initMessageViews() { + const int numberOfMessageViews = numberOfMessageTextViews(); + for (int i = 0; i < numberOfMessageViews; i++) { + MessageTextView * message = messageTextViewAtIndex(i); + message->setFont(k_font); + message->setBackgroundColor(k_backgroundColor); + float verticalAlignment = 0.5f; + if (i == 0) { + verticalAlignment = 1.0f; + } else if (i == numberOfMessageViews - 1) { + verticalAlignment = 0.0f; + } + message->setAlignment(0.5f, verticalAlignment); + } +} + +void ModalViewEmptyController::ModalViewEmptyView::setMessages(I18n::Message * message) { + const int numberOfMessageViews = numberOfMessageTextViews(); + for (int i = 0; i < numberOfMessageViews; i++) { + messageTextViewAtIndex(i)->setMessage(message[i]); + } +} + +void ModalViewEmptyController::ModalViewEmptyView::drawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(bounds(), k_backgroundColor); + drawBorderOfRect(ctx, bounds(), Palette::GreyBright); +} + +int ModalViewEmptyController::ModalViewEmptyView::numberOfSubviews() const { + return numberOfMessageTextViews() + hasExpressionView(); +} + +View * ModalViewEmptyController::ModalViewEmptyView::subviewAtIndex(int index) { + if (hasExpressionView()) { + if (index == k_expressionViewRowIndex) { + return expressionView(); + } + return messageTextViewAtIndex(index + (index < k_expressionViewRowIndex ? 0 : -1)); + } + return messageTextViewAtIndex(index); +} + +void ModalViewEmptyController::ModalViewEmptyView::layoutSubviews(bool force) { + const int numberOfMessageViews = numberOfMessageTextViews(); + const bool hasExpression = hasExpressionView(); + KDCoordinate width = bounds().width() - 2 * k_separatorThickness; + KDCoordinate height = bounds().height() - 2 * k_separatorThickness; + KDCoordinate textHeight = k_font->glyphSize().height(); + KDCoordinate layoutHeight = hasExpression ? expressionView()->minimalSizeForOptimalDisplay().height() : 0; + KDCoordinate margin = (height - numberOfMessageViews * textHeight - layoutHeight) / 2; + if (hasExpression) { + expressionView()->setFrame(KDRect( + k_separatorThickness, + k_separatorThickness + margin + k_expressionViewRowIndex * textHeight, + width, + layoutHeight), + force); + } + KDCoordinate currentHeight = k_separatorThickness; + for (uint8_t i = 0; i < numberOfMessageViews; i++) { + if (hasExpression && i == k_expressionViewRowIndex) { + currentHeight += layoutHeight; + } + KDCoordinate h = (i == 0 || i == numberOfMessageViews - 1) ? textHeight + margin : textHeight; + messageTextViewAtIndex(i)->setFrame(KDRect(k_separatorThickness, currentHeight, width, h), force); + currentHeight += h; + } +} + +// ModalViewEmptyController + +void ModalViewEmptyController::setMessages(I18n::Message * messages) { + static_cast(view())->setMessages(messages); +} diff --git a/escher/src/nested_menu_controller.cpp b/escher/src/nested_menu_controller.cpp index 9e07091b8..cd966342f 100644 --- a/escher/src/nested_menu_controller.cpp +++ b/escher/src/nested_menu_controller.cpp @@ -90,7 +90,7 @@ void NestedMenuController::ListController::setFirstSelectedRow(int firstSelected NestedMenuController::NestedMenuController(Responder * parentResponder, I18n::Message title) : StackViewController(parentResponder, &m_listController, Palette::ToolboxHeaderText, Palette::ToolboxHeaderBackground, Palette::ToolboxHeaderBorder), - m_selectableTableView(&m_listController, this, this), + m_selectableTableView(&m_listController, this, this, this), m_listController(this, &m_selectableTableView, title), m_sender(nullptr) { @@ -98,6 +98,10 @@ NestedMenuController::NestedMenuController(Responder * parentResponder, I18n::Me m_selectableTableView.setDecoratorType(ScrollView::Decorator::Type::None); } +void NestedMenuController::setTitle(I18n::Message title) { + m_listController.setTitle(title); +} + bool NestedMenuController::handleEvent(Ion::Events::Event event) { return handleEventForRow(event, selectedRow()); } diff --git a/escher/src/palette.cpp b/escher/src/palette.cpp index 1f92dac02..c20e46fc9 100644 --- a/escher/src/palette.cpp +++ b/escher/src/palette.cpp @@ -130,6 +130,8 @@ constexpr KDColor Palette::Green; constexpr KDColor Palette::GreenLight; constexpr KDColor Palette::Brown; constexpr KDColor Palette::Purple; +constexpr KDColor Palette::Cyan; // TODO Palette change +constexpr KDColor Palette::BlueishGrey; // TODO Palette change constexpr KDColor Palette::DataColor[]; constexpr KDColor Palette::DataColorLight[]; diff --git a/escher/src/scroll_view.cpp b/escher/src/scroll_view.cpp index aecbc58c6..9cb783d99 100644 --- a/escher/src/scroll_view.cpp +++ b/escher/src/scroll_view.cpp @@ -53,9 +53,16 @@ void ScrollView::scrollToContentPoint(KDPoint p, bool allowOverscroll) { if (!allowOverscroll && !m_contentView->bounds().contains(p)) { return; } + + KDRect visibleRect = visibleContentRect(); + + if (visibleRect.width() < 0 || visibleRect.height() < 0) { + return; + } + KDCoordinate offsetX = 0; KDCoordinate offsetY = 0; - KDRect visibleRect = visibleContentRect(); + if (visibleRect.left() > p.x()) { offsetX = p.x() - visibleRect.left(); } @@ -74,8 +81,16 @@ void ScrollView::scrollToContentPoint(KDPoint p, bool allowOverscroll) { // Handle cases when the size of the view has decreased. setContentOffset(KDPoint( - std::min(contentOffset().x(), std::max(minimalSizeForOptimalDisplay().width() - bounds().width(), KDCoordinate{0})), - std::min(contentOffset().y(), std::max(minimalSizeForOptimalDisplay().height() - bounds().height(), 0)))); + std::min( + contentOffset().x(), + std::max( + minimalSizeForOptimalDisplay().width() - bounds().width(), + KDCoordinate(0))), + std::min( + contentOffset().y(), + std::max( + minimalSizeForOptimalDisplay().height() - bounds().height(), + KDCoordinate(0))))); } void ScrollView::scrollToContentRect(KDRect rect, bool allowOverscroll) { @@ -83,7 +98,7 @@ void ScrollView::scrollToContentRect(KDRect rect, bool allowOverscroll) { KDPoint br = rect.bottomRight(); KDRect visibleRect = visibleContentRect(); /* We first check that we can display the whole rect. If we can't, we focus - * the croll to the closest part of the rect. */ + * the scroll to the closest part of the rect. */ if (visibleRect.height() < rect.height()) { // The visible rect is too small to display 'rect' if (rect.top() >= visibleRect.top()) { diff --git a/escher/src/scroll_view_indicator.cpp b/escher/src/scroll_view_indicator.cpp index 9f0dc5f10..aa9e78f94 100644 --- a/escher/src/scroll_view_indicator.cpp +++ b/escher/src/scroll_view_indicator.cpp @@ -1,4 +1,5 @@ #include +#include #include extern "C" { #include @@ -8,7 +9,7 @@ extern "C" { ScrollViewIndicator::ScrollViewIndicator() : View(), m_color(Palette::ScrollBarForeground), - m_margin(14) + m_margin(Metric::CommonTopMargin) { } diff --git a/escher/src/selectable_table_view.cpp b/escher/src/selectable_table_view.cpp index b156885fe..27f4bd395 100644 --- a/escher/src/selectable_table_view.cpp +++ b/escher/src/selectable_table_view.cpp @@ -71,6 +71,7 @@ void SelectableTableView::deselectTable(bool withinTemporarySelection) { selectRow(-1); if (m_delegate) { m_delegate->tableViewDidChangeSelection(this, previousSelectedCellX, previousSelectedCellY, withinTemporarySelection); + m_delegate->tableViewDidChangeSelectionAndDidScroll(this, previousSelectedCellX, previousSelectedCellY, withinTemporarySelection); } } @@ -87,21 +88,28 @@ bool SelectableTableView::selectCellAtLocation(int i, int j, bool setFirstRespon selectColumn(i); selectRow(j); + /* The delegate is notified: + * - after changing the selected cell but before scrolling: for instance, + * ExpressionModelListController needs to update its memoized cell before + * being able to scroll; + * - after scrolling: for instance, the calculation history table might + * change its cell content when selected (outup toggling, ellipsis toggling) + * and thus need to access the right used cell - which is defined only + * after scrolling. + */ + if (m_delegate) { m_delegate->tableViewDidChangeSelection(this, previousX, previousY, withinTemporarySelection); } - /* We need to scroll: - * - After notifying the delegate. For instance, - * ExpressionModelListController needs to update its memoized cell - * height values before any scroll. - * - Before setting the first responder. If the first responder is a view, it - * might change during the scroll. */ - if (selectedRow() >= 0) { scrollToCell(selectedColumn(), selectedRow()); } + if (m_delegate) { + m_delegate->tableViewDidChangeSelectionAndDidScroll(this, previousX, previousY, withinTemporarySelection); + } + HighlightCell * cell = selectedCell(); if (cell) { // Update first responder diff --git a/escher/src/stack_view_controller.cpp b/escher/src/stack_view_controller.cpp index 2b88c2288..1b32e0879 100644 --- a/escher/src/stack_view_controller.cpp +++ b/escher/src/stack_view_controller.cpp @@ -129,10 +129,6 @@ void StackViewController::pop() { vc->viewDidDisappear(); } -int StackViewController::depth() { - return m_numberOfChildren; -} - void StackViewController::pushModel(Frame frame) { m_childrenFrame[m_numberOfChildren++] = frame; } @@ -160,10 +156,6 @@ bool StackViewController::handleEvent(Ion::Events::Event event) { return false; } -View * StackViewController::view() { - return &m_view; -} - void StackViewController::initView() { m_childrenFrame[0].viewController()->initView(); } diff --git a/escher/src/table_cell.cpp b/escher/src/table_cell.cpp index f9c60e8e6..ac7ad9f40 100644 --- a/escher/src/table_cell.cpp +++ b/escher/src/table_cell.cpp @@ -98,16 +98,16 @@ void TableCell::layoutSubviews(bool force) { y += labelHeight + k_verticalMargin; } horizontalMargin = k_separatorThickness + k_horizontalMargin; - y = std::max(y, height - k_separatorThickness - withMargin(accessorySize.height(), Metric::TableCellVerticalMargin) - withMargin(subAccessorySize.height(), 0)); + y = std::max(y, height - k_separatorThickness - withMargin(accessorySize.height(), k_verticalMargin) - withMargin(subAccessorySize.height(), 0)); if (subAccessory) { - KDCoordinate subAccessoryHeight = std::min(subAccessorySize.height(), height - y - k_separatorThickness - Metric::TableCellVerticalMargin); + KDCoordinate subAccessoryHeight = std::min(subAccessorySize.height(), height - y - k_separatorThickness - k_verticalMargin); accessory->setFrame(KDRect(horizontalMargin, y, width - 2*horizontalMargin, subAccessoryHeight), force); y += subAccessoryHeight; } horizontalMargin = k_separatorThickness + accessoryMargin(); - y = std::max(y, height - k_separatorThickness - withMargin(accessorySize.height(), Metric::TableCellVerticalMargin)); + y = std::max(y, height - k_separatorThickness - withMargin(accessorySize.height(), k_verticalMargin)); if (accessory) { - KDCoordinate accessoryHeight = std::min(accessorySize.height(), height - y - k_separatorThickness - Metric::TableCellVerticalMargin); + KDCoordinate accessoryHeight = std::min(accessorySize.height(), height - y - k_separatorThickness - k_verticalMargin); accessory->setFrame(KDRect(horizontalMargin, y, width - 2*horizontalMargin, accessoryHeight), force); } } else { @@ -164,7 +164,7 @@ void TableCell::layoutSubviews(bool force) { } void TableCell::drawRect(KDContext * ctx, KDRect rect) const { - KDColor backgroundColor = isHighlighted() ? Palette::ListCellBackgroundSelected : Palette::ListCellBackground; - drawInnerRect(ctx, bounds(), backgroundColor); + KDColor backColor = isHighlighted() ? Palette::ListCellBackgroundSelected : Palette::ListCellBackground; + drawInnerRect(ctx, bounds(), backColor); drawBorderOfRect(ctx, bounds(), Palette::ListCellBorder); } diff --git a/escher/src/table_view.cpp b/escher/src/table_view.cpp index 01ea22a2d..91c587908 100644 --- a/escher/src/table_view.cpp +++ b/escher/src/table_view.cpp @@ -177,55 +177,28 @@ void TableView::ContentView::layoutSubviews(bool force) { } } - -int TableView::ContentView::numberOfFullyDisplayableRows() const { - // The number of displayable rows taking into accounts margins - int rowOffsetWithMargin = m_dataSource->indexFromCumulatedHeight(m_tableView->contentOffset().y() + - m_tableView->topMargin()); - int displayedHeightWithOffsetAndMargin = m_dataSource->indexFromCumulatedHeight(m_tableView->maxContentHeightDisplayableWithoutScrolling() + - m_tableView->contentOffset().y() + m_tableView->topMargin()); - return displayedHeightWithOffsetAndMargin - rowOffsetWithMargin; -} - -int TableView::ContentView::numberOfFullyDisplayableColumns() const { - // The number of displayable rows taking into accounts margins - int columnOffsetWithMargin = m_dataSource->indexFromCumulatedWidth(m_tableView->contentOffset().x() + - m_tableView->leftMargin()); - int displayedWidthWithOffsetAndMargin = m_dataSource->indexFromCumulatedWidth(m_tableView->maxContentWidthDisplayableWithoutScrolling() + - m_tableView->contentOffset().x() + m_tableView->leftMargin()); - return displayedWidthWithOffsetAndMargin - columnOffsetWithMargin; -} - int TableView::ContentView::numberOfDisplayableRows() const { int rowOffset = rowsScrollingOffset(); - int displayedHeightWithOffset = m_dataSource->indexFromCumulatedHeight(m_tableView->bounds().height() + m_tableView->contentOffset().y()); - return std::min( - m_dataSource->numberOfRows(), - displayedHeightWithOffset + 1 - ) - rowOffset; + int displayedHeightWithOffset = m_dataSource->indexFromCumulatedHeight(m_tableView->bounds().height() + (m_tableView->contentOffset().y() - m_tableView->topMargin())); + return std::min(m_dataSource->numberOfRows(), displayedHeightWithOffset + 1) - rowOffset; } int TableView::ContentView::numberOfDisplayableColumns() const { int columnOffset = columnsScrollingOffset(); - int displayedWidthWithOffset = m_dataSource->indexFromCumulatedWidth(m_tableView->bounds().width() + m_tableView->contentOffset().x()); - return std::min( - m_dataSource->numberOfColumns(), - displayedWidthWithOffset + 1 - ) - columnOffset; + int displayedWidthWithOffset = m_dataSource->indexFromCumulatedWidth(m_tableView->bounds().width() + m_tableView->contentOffset().x() - m_tableView->leftMargin()); + return std::min(m_dataSource->numberOfColumns(), displayedWidthWithOffset + 1) - columnOffset; } int TableView::ContentView::rowsScrollingOffset() const { /* Here, we want to translate the offset at which our tableView is displaying * us into an integer offset we can use to ask cells to our data source. */ - KDCoordinate invisibleHeight = m_tableView->contentOffset().y()-m_tableView->topMargin(); - invisibleHeight = invisibleHeight < 0 ? 0 : invisibleHeight; + KDCoordinate invisibleHeight = std::max(m_tableView->contentOffset().y() - m_tableView->topMargin(), 0); return m_dataSource->indexFromCumulatedHeight(invisibleHeight); } int TableView::ContentView::columnsScrollingOffset() const { /* Here, we want to translate the offset at which our tableView is displaying * us into an integer offset we can use to ask cells to our data source. */ - KDCoordinate invisibleWidth = m_tableView->contentOffset().x()-m_tableView->leftMargin(); - invisibleWidth = invisibleWidth < 0 ? 0 : invisibleWidth; + KDCoordinate invisibleWidth = std::max(m_tableView->contentOffset().x() - m_tableView->leftMargin(), 0); return m_dataSource->indexFromCumulatedWidth(invisibleWidth); } diff --git a/escher/src/text_area.cpp b/escher/src/text_area.cpp index 0f94353ce..bb99860a0 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -258,11 +258,16 @@ void TextArea::Text::insertText(const char * s, int textLength, char * location) assert(m_buffer != nullptr); assert(location >= m_buffer && location < m_buffer + m_bufferSize - 1); assert(strlen(m_buffer) + textLength < m_bufferSize); + // assert the text to insert does not overlap the location where to insert + assert(s >= location || s + textLength < location); + /* The text to insert might be located after the insertion location, in which + * case we cannot simply do a memmove, as s will be shifted by the copy. */ + bool noShift = (s + textLength < location) || (s > m_buffer + m_bufferSize); size_t sizeToMove = strlen(location) + 1; assert(location + textLength + sizeToMove <= m_buffer + m_bufferSize); memmove(location + textLength, location, sizeToMove); - memmove(location, s, textLength); + memmove(location, s + (noShift ? 0 : textLength), textLength); } void TextArea::Text::insertSpacesAtLocation(int numberOfSpaces, char * location) { @@ -479,26 +484,19 @@ void TextArea::ContentView::setText(char * textBuffer, size_t textBufferSize) { m_cursorLocation = text(); } -bool TextArea::ContentView::insertTextAtLocation(const char * text, char * location) { - int textSize = strlen(text); - if (m_text.textLength() + textSize >= m_text.bufferSize() || textSize == 0) { +bool TextArea::ContentView::insertTextAtLocation(const char * text, char * location, int textLength) { + int textLen = textLength < 0 ? strlen(text) : textLength; + assert(textLen < 0 || textLen <= strlen(text)); + if (m_text.textLength() + textLen >= m_text.bufferSize() || textLen == 0) { return false; } - bool lineBreak = false; - // Scan for \n and 0 - const char * nullLocation = UTF8Helper::PerformAtCodePoints( - text, '\n', - [](int codePointOffset, void * lineBreak, int context1, int context2) { - *((bool *)lineBreak) = true; - }, - [](int c1, void * c2, int c3, int c4) { }, - &lineBreak, 0); + // Scan for \n + bool lineBreak = UTF8Helper::HasCodePoint(text, '\n', text + textLen); - assert(UTF8Helper::CodePointIs(nullLocation, 0)); - m_text.insertText(text, nullLocation - text, location); + m_text.insertText(text, textLen, location); // Replace System parentheses (used to keep layout tree structure) by normal parentheses - Poincare::SerializationHelper::ReplaceSystemParenthesesByUserParentheses(location, nullLocation - text); + Poincare::SerializationHelper::ReplaceSystemParenthesesByUserParentheses(location, textLen); reloadRectFromPosition(location, lineBreak); return true; } @@ -542,9 +540,13 @@ bool TextArea::ContentView::removeStartOfLine() { return false; } +size_t TextArea::ContentView::removeText(const char * start, const char * end) { + return m_text.removeText(start, end); +} + size_t TextArea::ContentView::deleteSelection() { assert(!selectionIsEmpty()); - size_t removedLength = m_text.removeText(m_selectionStart, m_selectionEnd); + size_t removedLength = removeText(m_selectionStart, m_selectionEnd); /* We cannot call resetSelection() because m_selectionStart and m_selectionEnd * are invalid */ m_selectionStart = nullptr; diff --git a/escher/src/text_field.cpp b/escher/src/text_field.cpp index 7c0b7fdae..36500b007 100644 --- a/escher/src/text_field.cpp +++ b/escher/src/text_field.cpp @@ -119,10 +119,10 @@ void TextField::ContentView::reinitDraftTextBuffer() { setCursorLocation(s_draftTextBuffer); } -bool TextField::ContentView::insertTextAtLocation(const char * text, char * location) { +bool TextField::ContentView::insertTextAtLocation(const char * text, char * location, int textLen) { assert(m_isEditing); - int textLength = strlen(text); + size_t textLength = textLen < 0 ? strlen(text) : (size_t)textLen; if (m_currentDraftTextLength + textLength >= m_draftTextBufferSize || textLength == 0) { return false; } @@ -130,12 +130,12 @@ bool TextField::ContentView::insertTextAtLocation(const char * text, char * loca memmove(location + textLength, location, (s_draftTextBuffer + m_currentDraftTextLength + 1) - location); // Caution! One byte will be overridden by the null-terminating char of strlcpy - char * overridenByteLocation = location + textLength; + size_t copySize = std::min(textLength + 1, static_cast((s_draftTextBuffer + m_draftTextBufferSize) - location)); + char * overridenByteLocation = location + copySize - 1; char overridenByte = *overridenByteLocation; - strlcpy(location, text, (s_draftTextBuffer + m_draftTextBufferSize) - location); - assert(overridenByteLocation < s_draftTextBuffer + m_draftTextBufferSize); + strlcpy(location, text, copySize); *overridenByteLocation = overridenByte; - m_currentDraftTextLength += textLength; + m_currentDraftTextLength += copySize-1; // Do no count the null-termination reloadRectFromPosition(m_horizontalAlignment == 0.0f ? location : s_draftTextBuffer); return true; diff --git a/escher/src/toolbox.cpp b/escher/src/toolbox.cpp index 7bb368be7..7c080263a 100644 --- a/escher/src/toolbox.cpp +++ b/escher/src/toolbox.cpp @@ -25,7 +25,7 @@ int Toolbox::reusableCellCount(int type) { } void Toolbox::willDisplayCellForIndex(HighlightCell * cell, int index) { - ToolboxMessageTree * messageTree = (ToolboxMessageTree *)m_messageTreeModel->children(index); + ToolboxMessageTree * messageTree = (ToolboxMessageTree *)m_messageTreeModel->childAtIndex(index); if (messageTree->numberOfChildren() == 0) { MessageTableCellWithMessage * myCell = (MessageTableCellWithMessage *)cell; myCell->setMessage(messageTree->label()); @@ -39,7 +39,7 @@ void Toolbox::willDisplayCellForIndex(HighlightCell * cell, int index) { } int Toolbox::typeAtLocation(int i, int j) { - MessageTree * messageTree = (MessageTree *)m_messageTreeModel->children(j); + MessageTree * messageTree = (MessageTree *)m_messageTreeModel->childAtIndex(j); if (messageTree->numberOfChildren() == 0) { return LeafCellType; } @@ -48,7 +48,7 @@ int Toolbox::typeAtLocation(int i, int j) { bool Toolbox::selectSubMenu(int selectedRow) { m_selectableTableView.deselectTable(); - m_messageTreeModel = (ToolboxMessageTree *)m_messageTreeModel->children(selectedRow); + m_messageTreeModel = (ToolboxMessageTree *)m_messageTreeModel->childAtIndex(selectedRow); return NestedMenuController::selectSubMenu(selectedRow); } @@ -63,7 +63,7 @@ bool Toolbox::returnToPreviousMenu() { ToolboxMessageTree * parentMessageTree = (ToolboxMessageTree *)rootModel(); Stack::State * previousState = m_stack.stateAtIndex(index++); while (currentDepth-- > 1) { - parentMessageTree = (ToolboxMessageTree *)parentMessageTree->children(previousState->selectedRow()); + parentMessageTree = (ToolboxMessageTree *)parentMessageTree->childAtIndex(previousState->selectedRow()); previousState = m_stack.stateAtIndex(index++); } m_messageTreeModel = parentMessageTree; diff --git a/ion/include/ion/unicode/utf8_helper.h b/ion/include/ion/unicode/utf8_helper.h index 8f5e17c2d..17ff9385f 100644 --- a/ion/include/ion/unicode/utf8_helper.h +++ b/ion/include/ion/unicode/utf8_helper.h @@ -11,10 +11,10 @@ int CountOccurrences(const char * s, CodePoint c); /* Returns the first occurence of a code point in a string, the position of the * null terminating char otherwise. */ -const char * CodePointSearch(const char * s, CodePoint c); +const char * CodePointSearch(const char * s, CodePoint c, const char * stoppingPosition = nullptr); // Returns true if the text had the code point -bool HasCodePoint(const char * s, CodePoint c); +bool HasCodePoint(const char * s, CodePoint c, const char * stoppingPosition = nullptr); /* Returns the first occurence of a code point that is not c in a string, * stopping at the null-terminating char or the start of string. */ @@ -66,8 +66,11 @@ const char * PerformAtCodePoints( const char * initialPosition = nullptr, const char * stoppingPosition = nullptr); +CodePoint PreviousCodePoint(const char * buffer, const char * location); // returns 0 if location == buffer +CodePoint CodePointAtLocation(const char * location); bool PreviousCodePointIs(const char * buffer, const char * location, CodePoint c); bool CodePointIs(const char * location, CodePoint c); +bool CodePointIsEndOfWord(CodePoint c); // Shift the buffer and return the number of bytes removed. int RemovePreviousGlyph(const char * text, char * location, CodePoint * c = nullptr); @@ -81,6 +84,11 @@ size_t GlyphOffsetAtCodePoint(const char * buffer, const char * position); * For instance, strlen("∑") = 3 but StringGlyphLength("∑") = 1 */ size_t StringGlyphLength(const char * s, int maxSize = -1); +// Returns the position of the first previous char ' ', '\n' or text +const char * BeginningOfWord(const char * text, const char * word); +// Returns the position of the first following char ' ', '\n' or 0 +const char * EndOfWord(const char * word); + }; #endif diff --git a/ion/src/shared/storage.cpp b/ion/src/shared/storage.cpp index 95a9fd29d..56fc333ea 100644 --- a/ion/src/shared/storage.cpp +++ b/ion/src/shared/storage.cpp @@ -377,6 +377,9 @@ Storage::Record::Data Storage::valueOfRecord(const Record record) { Storage::Record::ErrorStatus Storage::setValueOfRecord(Record record, Record::Data data) { char * p = pointerOfRecord(record); + /* TODO: if data.buffer == p, assert that size hasn't change and do not do any + * memcopy, but still notify the delegate. Beware of scripts and the accordion + * routine.*/ if (p != nullptr) { record_size_t previousRecordSize = sizeOfRecordStarting(p); const char * fullName = fullNameOfRecordStarting(p); diff --git a/ion/src/shared/unicode/utf8_helper.cpp b/ion/src/shared/unicode/utf8_helper.cpp index 07c5c5838..d15c897c6 100644 --- a/ion/src/shared/unicode/utf8_helper.cpp +++ b/ion/src/shared/unicode/utf8_helper.cpp @@ -33,10 +33,10 @@ int CountOccurrences(const char * s, CodePoint c) { return count; } -const char * CodePointSearch(const char * s, CodePoint c) { +const char * CodePointSearch(const char * s, CodePoint c, const char * stoppingPosition) { if (UTF8Decoder::CharSizeOfCodePoint(c) == 1) { const char * result = s; - while (*result != 0 && *result != c) { + while (*result != 0 && *result != c && (stoppingPosition == nullptr || result != stoppingPosition)) { result++; } return result; @@ -45,7 +45,7 @@ const char * CodePointSearch(const char * s, CodePoint c) { const char * currentPointer = s; CodePoint codePoint = decoder.nextCodePoint(); const char * nextPointer = decoder.stringPosition(); - while (codePoint != UCodePointNull && codePoint != c) { + while (codePoint != UCodePointNull && codePoint != c && (stoppingPosition == nullptr || currentPointer < stoppingPosition)) { currentPointer = nextPointer; codePoint = decoder.nextCodePoint(); nextPointer = decoder.stringPosition(); @@ -53,9 +53,10 @@ const char * CodePointSearch(const char * s, CodePoint c) { return currentPointer; } -bool HasCodePoint(const char * s, CodePoint c) { +bool HasCodePoint(const char * s, CodePoint c, const char * stoppingPosition) { assert(c != 0); - return *CodePointSearch(s, c) != 0; + const char * resultPosition = CodePointSearch(s, c, stoppingPosition); + return *resultPosition != 0 && (stoppingPosition == nullptr || resultPosition < stoppingPosition); } const char * NotCodePointSearch(const char * s, CodePoint c, bool goingLeft, const char * initialPosition) { @@ -257,21 +258,36 @@ const char * PerformAtCodePoints(const char * s, CodePoint c, CodePointAction ac return codePointPointer; } +CodePoint PreviousCodePoint(const char * buffer, const char * location) { + if (location == buffer) { + return UCodePointNull; + } + UTF8Decoder decoder(buffer, location); + return decoder.previousCodePoint(); +} + +CodePoint CodePointAtLocation(const char * location) { + UTF8Decoder decoder(location); + return decoder.nextCodePoint(); +} + bool PreviousCodePointIs(const char * buffer, const char * location, CodePoint c) { assert(location > buffer); if (UTF8Decoder::CharSizeOfCodePoint(c) == 1) { return *(location -1) == c; } - UTF8Decoder decoder(buffer, location); - return decoder.previousCodePoint() == c; + return PreviousCodePoint(buffer, location) == c; } bool CodePointIs(const char * location, CodePoint c) { if (UTF8Decoder::CharSizeOfCodePoint(c) == 1) { return *(location) == c; } - UTF8Decoder decoder(location); - return decoder.nextCodePoint() == c; + return CodePointAtLocation(location) == c; +} + +bool CodePointIsEndOfWord(CodePoint c) { + return c == '\n' || c == ' ' || c == UCodePointNull; } int RemovePreviousGlyph(const char * text, char * location, CodePoint * c) { @@ -363,4 +379,32 @@ size_t StringGlyphLength(const char * s, int maxSize) { return glyphIndex; } +const char * BeginningOfWord(const char * text, const char * word) { + if (text == word) { + return text; + } + UTF8Decoder decoder(text, word); + const char * codePointPointer = decoder.stringPosition(); + CodePoint codePoint = decoder.previousCodePoint(); + while (!CodePointIsEndOfWord(codePoint)) { + codePointPointer = decoder.stringPosition(); + if (codePointPointer == text) { + break; + } + codePoint = decoder.previousCodePoint(); + } + return codePointPointer; +} + +const char * EndOfWord(const char * word) { + UTF8Decoder decoder(word); + CodePoint codePoint = decoder.nextCodePoint(); + const char * result = word; + while (!CodePointIsEndOfWord(codePoint)) { + result = decoder.stringPosition(); + codePoint = decoder.nextCodePoint(); + } + return result; +} + } diff --git a/ion/src/simulator/shared/apple/targets.mak b/ion/src/simulator/shared/apple/targets.mak index 0245e6fb6..027602f2e 100644 --- a/ion/src/simulator/shared/apple/targets.mak +++ b/ion/src/simulator/shared/apple/targets.mak @@ -12,5 +12,8 @@ ifdef IOS_PROVISIONNING_PROFILE $(call rule_label,SIGN) $(Q) codesign --force --entitlements $(BUILD_DIR)/app/entitlements.plist --sign "Apple Distribution: NumWorks" $(BUILD_DIR)/$*.app endif + @ echo "$(shell printf "%-8s" "COPY")$(notdir $*).app into Payload" + $(Q) cd $(dir $@) ; mkdir Payload; cp -r $(notdir $*).app Payload + $(call rule_label,ZIP) - $(Q) cd $(dir $@) ; zip -qr9 $(notdir $@) $*.app + $(Q) cd $(dir $@) ; zip -qr9 $(notdir $@) Payload diff --git a/ion/src/simulator/shared/main_headless.cpp b/ion/src/simulator/shared/main_headless.cpp index 15cf095d6..d4e96e181 100644 --- a/ion/src/simulator/shared/main_headless.cpp +++ b/ion/src/simulator/shared/main_headless.cpp @@ -17,7 +17,7 @@ constexpr int kHeapSize = 131072; #ifdef NDEBUG constexpr int kStackSize = 32768; #else -constexpr int kStackSize = 32768*2; // In DEBUG mode, we increase the stack to be able to pass the tests +constexpr int kStackSize = 32768*10; // In DEBUG mode, we increase the stack to be able to pass the tests #endif char heap[kHeapSize]; diff --git a/ion/test/device/n0110/Makefile b/ion/test/device/n0110/Makefile index 3acf290d9..d379f0fb2 100644 --- a/ion/test/device/n0110/Makefile +++ b/ion/test/device/n0110/Makefile @@ -1,10 +1,8 @@ test_ion_external_flash_read_src += $(addprefix ion/test/$(PLATFORM)/$(MODEL)/, \ - external_flash_helper.cpp \ external_flash_read.cpp \ ) test_ion_external_flash_write_src += $(addprefix ion/test/$(PLATFORM)/$(MODEL)/, \ - external_flash_helper.cpp \ external_flash_write.cpp \ ) diff --git a/ion/test/device/n0110/external_flash_helper.cpp b/ion/test/device/n0110/external_flash_helper.cpp deleted file mode 100644 index 11d776784..000000000 --- a/ion/test/device/n0110/external_flash_helper.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "ion/include/ion/timing.h" -#include -#include - -size_t uint64ToString(uint64_t n, char buffer[]) { - size_t len = 0; - do { - buffer[len++] = (n % 10) + '0'; - } while ((n /= 10) > 0); - int i = 0; - int j = len - 1; - while (i < j) { - char c = buffer[i]; - buffer[i++] = buffer[j]; - buffer[j--] = c; - } - return len; -} - -void printElapsedTime(uint64_t startTime) { - char buffer[7+26+2] = " time: "; - size_t len = uint64ToString((Ion::Timing::millis() - startTime)/1000, buffer+7); - buffer[7+len] = 's'; - buffer[7+len+1] = 0; - quiz_print(buffer); -} diff --git a/ion/test/device/n0110/external_flash_helper.h b/ion/test/device/n0110/external_flash_helper.h index de84d3d0d..11350ce9b 100644 --- a/ion/test/device/n0110/external_flash_helper.h +++ b/ion/test/device/n0110/external_flash_helper.h @@ -17,6 +17,3 @@ inline uint32_t expected_value_at(uint32_t * ptr) { uint16_t * ptr16 = reinterpret_cast(ptr); return (static_cast(expected_value_at(ptr16+1)) << 16) + static_cast(expected_value_at(ptr16)); } - -size_t uint64ToString(uint64_t n, char buffer[]); -void printElapsedTime(uint64_t startTime); diff --git a/ion/test/device/n0110/external_flash_read.cpp b/ion/test/device/n0110/external_flash_read.cpp index 3b234f14c..4a2417c6e 100644 --- a/ion/test/device/n0110/external_flash_read.cpp +++ b/ion/test/device/n0110/external_flash_read.cpp @@ -1,7 +1,6 @@ #include +#include #include -#include -#include #include #include "external_flash_helper.h" @@ -47,56 +46,56 @@ void test(int accessType, int repeat) { } QUIZ_CASE(ion_extflash_read_byte_fwd) { - uint64_t startTime = Ion::Timing::millis(); + uint64_t startTime = quiz_stopwatch_start(); test(0, 1); - printElapsedTime(startTime); + quiz_stopwatch_print_lap(startTime); } QUIZ_CASE(ion_extflash_read_byte_bck) { - uint64_t startTime = Ion::Timing::millis(); + uint64_t startTime = quiz_stopwatch_start(); test(1, 1); - printElapsedTime(startTime); + quiz_stopwatch_print_lap(startTime); } QUIZ_CASE(ion_extflash_read_byte_rand) { - uint64_t startTime = Ion::Timing::millis(); + uint64_t startTime = quiz_stopwatch_start(); test(2, 1); - printElapsedTime(startTime); + quiz_stopwatch_print_lap(startTime); } QUIZ_CASE(ion_extflash_read_half_fwd) { - uint64_t startTime = Ion::Timing::millis(); + uint64_t startTime = quiz_stopwatch_start(); test(0, 1); - printElapsedTime(startTime); + quiz_stopwatch_print_lap(startTime); } QUIZ_CASE(ion_extflash_read_half_bck) { - uint64_t startTime = Ion::Timing::millis(); + uint64_t startTime = quiz_stopwatch_start(); test(1, 1); - printElapsedTime(startTime); + quiz_stopwatch_print_lap(startTime); } QUIZ_CASE(ion_extflash_read_half_rand) { - uint64_t startTime = Ion::Timing::millis(); + uint64_t startTime = quiz_stopwatch_start(); test(2, 1); - printElapsedTime(startTime); + quiz_stopwatch_print_lap(startTime); } QUIZ_CASE(ion_extflash_read_word_fwd) { - uint64_t startTime = Ion::Timing::millis(); + uint64_t startTime = quiz_stopwatch_start(); test(0, 1); - printElapsedTime(startTime); + quiz_stopwatch_print_lap(startTime); } QUIZ_CASE(ion_extflash_read_word_bck) { - uint64_t startTime = Ion::Timing::millis(); + uint64_t startTime = quiz_stopwatch_start(); test(1, 1); - printElapsedTime(startTime); + quiz_stopwatch_print_lap(startTime); } QUIZ_CASE(ion_extflash_read_word_rand) { - uint64_t startTime = Ion::Timing::millis(); + uint64_t startTime = quiz_stopwatch_start(); test(2, 1); - printElapsedTime(startTime); + quiz_stopwatch_print_lap(startTime); Ion::Timing::msleep(3000); } diff --git a/ion/test/device/n0110/external_flash_write.cpp b/ion/test/device/n0110/external_flash_write.cpp index 0585abd96..a9097117a 100644 --- a/ion/test/device/n0110/external_flash_write.cpp +++ b/ion/test/device/n0110/external_flash_write.cpp @@ -1,20 +1,19 @@ #include -#include +#include #include -#include "ion/include/ion/timing.h" #include "external_flash_helper.h" // Choose some not too uniform data to program the external flash memory with. QUIZ_CASE(ion_ext_flash_erase) { - uint64_t startTime = Ion::Timing::millis(); + uint64_t startTime = quiz_stopwatch_start(); Ion::Device::ExternalFlash::MassErase(); - printElapsedTime(startTime); + quiz_stopwatch_print_lap(startTime); } QUIZ_CASE(ion_ext_flash_program) { // Program separately each page of the flash memory - uint64_t startTime = Ion::Timing::millis(); + uint64_t startTime = quiz_stopwatch_start(); for (int page = 0; page < (1<<15); page++) { uint8_t buffer[256]; for (int byte = 0; byte < 256; byte++) { @@ -22,5 +21,5 @@ QUIZ_CASE(ion_ext_flash_program) { } Ion::Device::ExternalFlash::WriteMemory(reinterpret_cast(page * 256), buffer, 256); } - printElapsedTime(startTime); + quiz_stopwatch_print_lap(startTime); } diff --git a/ion/test/utf8_helper.cpp b/ion/test/utf8_helper.cpp index 39be3b366..b96f361d9 100644 --- a/ion/test/utf8_helper.cpp +++ b/ion/test/utf8_helper.cpp @@ -273,3 +273,29 @@ QUIZ_CASE(ion_utf8_helper_string_glyph_length) { uint8_t testString[] = {'a', 'b', 'c', 0b11111111, 0b11111111, 0}; // Malformed utf-8 string assert_string_glyph_length_is((const char *)testString, 3, 3); } + + +void assert_beginning_of_word_is(const char * text, const char * word, const char * beginningOfWord) { + quiz_assert(UTF8Helper::BeginningOfWord(text, word) == beginningOfWord); +} + +QUIZ_CASE(ion_utf8_helper_beginning_of_word) { + const char * test_sentence = "01 34+ \n89"; + assert_beginning_of_word_is(test_sentence, test_sentence, test_sentence); + assert_beginning_of_word_is(test_sentence, test_sentence + 1, test_sentence); + assert_beginning_of_word_is(test_sentence, test_sentence + 2, test_sentence); + assert_beginning_of_word_is(test_sentence, test_sentence + 5, test_sentence + 3); + assert_beginning_of_word_is(test_sentence, test_sentence + 8, test_sentence + 8); +} + +void assert_end_of_word_is(const char * word, const char * endOfWord) { + quiz_assert(UTF8Helper::EndOfWord(word) == endOfWord); +} + +QUIZ_CASE(ion_utf8_helper_end_of_word) { + const char * test_sentence = "01 34+ 789"; + assert_end_of_word_is(test_sentence, test_sentence + 2); + assert_end_of_word_is(test_sentence + 2, test_sentence + 2); + assert_end_of_word_is(test_sentence + 3, test_sentence + 6); + assert_end_of_word_is(test_sentence + 8, test_sentence + 10); +} diff --git a/kandinsky/include/kandinsky/font.h b/kandinsky/include/kandinsky/font.h index ccedc7709..73db61205 100644 --- a/kandinsky/include/kandinsky/font.h +++ b/kandinsky/include/kandinsky/font.h @@ -33,8 +33,8 @@ public: static constexpr const KDFont * LargeFont = &privateLargeFont; static constexpr const KDFont * SmallFont = &privateSmallFont; - KDSize stringSize(const char * text) const { - return stringSizeUntil(text, nullptr); + KDSize stringSize(const char * text, int textLength = -1) const { + return stringSizeUntil(text, textLength < 0 ? nullptr : text + textLength); } KDSize stringSizeUntil(const char * text, const char * limit) const; diff --git a/poincare/Makefile b/poincare/Makefile index 69dfcd8f5..bac81600c 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -184,6 +184,10 @@ tests_src += $(addprefix poincare/test/,\ simplification.cpp\ ) -ifdef POINCARE_TREE_LOG -SFLAGS += -DPOINCARE_TREE_LOG=1 +ifeq ($(DEBUG),1) +POINCARE_TREE_LOG ?= 1 +endif + +ifdef POINCARE_TREE_LOG +SFLAGS += -DPOINCARE_TREE_LOG=$(POINCARE_TREE_LOG) endif diff --git a/poincare/include/poincare/absolute_value.h b/poincare/include/poincare/absolute_value.h index e0a12aa6a..57be4dd32 100644 --- a/poincare/include/poincare/absolute_value.h +++ b/poincare/include/poincare/absolute_value.h @@ -33,7 +33,6 @@ public: Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return ApproximationHelper::Map(this, context, complexFormat, angleUnit, computeOnComplex); } - Expression getUnit() const override; // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/addition.h b/poincare/include/poincare/addition.h index 63971f23f..d49303723 100644 --- a/poincare/include/poincare/addition.h +++ b/poincare/include/poincare/addition.h @@ -35,7 +35,6 @@ public: template static MatrixComplex computeOnComplexAndMatrix(const std::complex c, const MatrixComplex m, Preferences::ComplexFormat complexFormat) { return MatrixComplex::Undefined(); } - Expression getUnit() const override; // Simplification LayoutShape leftLayoutShape() const override { diff --git a/poincare/include/poincare/division.h b/poincare/include/poincare/division.h index 262f4a418..4b924844d 100644 --- a/poincare/include/poincare/division.h +++ b/poincare/include/poincare/division.h @@ -25,7 +25,7 @@ public: // Properties Type type() const override { return Type::Division; } int polynomialDegree(Context * context, const char * symbolName) const override; - Expression getUnit() const override { assert(false); return ExpressionNode::getUnit(); } + Expression removeUnit(Expression * unit) override { assert(false); return ExpressionNode::removeUnit(unit); } // Approximation virtual Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { diff --git a/poincare/include/poincare/empty_expression.h b/poincare/include/poincare/empty_expression.h index 3814f3e91..0ac5a21e7 100644 --- a/poincare/include/poincare/empty_expression.h +++ b/poincare/include/poincare/empty_expression.h @@ -22,7 +22,7 @@ public: // Properties Type type() const override { return Type::EmptyExpression; } int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; - Expression getUnit() const override { assert(false); return ExpressionNode::getUnit(); } + Expression removeUnit(Expression * unit) override { assert(false); return ExpressionNode::removeUnit(unit); } // Simplification LayoutShape leftLayoutShape() const override { diff --git a/poincare/include/poincare/empty_layout.h b/poincare/include/poincare/empty_layout.h index 14cd3bb15..8edb9c538 100644 --- a/poincare/include/poincare/empty_layout.h +++ b/poincare/include/poincare/empty_layout.h @@ -82,6 +82,7 @@ public: node()->setVisible(visible); } + EmptyLayoutNode::Color color() const { return node()->color(); } void setColor(EmptyLayoutNode::Color color) { node()->setColor(color); } diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index c8aa3cc10..6bdb4008e 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -10,8 +10,6 @@ #include #include -#include - namespace Poincare { class Context; @@ -117,6 +115,7 @@ class Expression : public TreeHandle { friend class NAryExpressionNode; friend class StoreNode; friend class SymbolNode; + friend class UnitNode; public: static bool IsExpression() { return true; } @@ -152,7 +151,7 @@ public: bool isDivisionOfIntegers() const; bool hasDefinedComplexApproximation(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; typedef bool (*ExpressionTest)(const Expression e, Context * context); - bool recursivelyMatches(ExpressionTest test, Context * context, bool replaceSymbols = true) const; + bool recursivelyMatches(ExpressionTest test, Context * context, ExpressionNode::SymbolicComputation replaceSymbols = ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition) const; typedef bool (*ExpressionTypeTest)(const Expression e, const void * context); bool hasExpression(ExpressionTypeTest test, const void * context) const; // WARNING: this method must be called on reduced (sorted) expressions @@ -200,7 +199,7 @@ public: Expression replaceSymbolWithExpression(const SymbolAbstract & symbol, const Expression & expression) { return node()->replaceSymbolWithExpression(symbol, expression); } /* Units */ - Expression getUnit() const { return node()->getUnit(); } + Expression removeUnit(Expression * unit) { return node()->removeUnit(unit); } bool hasUnit() const; /* Complex */ @@ -240,11 +239,11 @@ public: * account the complex format required in the expression they return. * (For instance, in Polar mode, they return an expression of the form * r*e^(i*th) reduced and approximated.) */ - static Expression ParseAndSimplify(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation = ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition); + static Expression ParseAndSimplify(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation = ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion unitConversion = ExpressionNode::UnitConversion::Default); Expression simplify(ExpressionNode::ReductionContext reductionContext); - static void ParseAndSimplifyAndApproximate(const char * text, Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation = ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition); - void simplifyAndApproximate(Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation = ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition); + static void ParseAndSimplifyAndApproximate(const char * text, Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation = ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion unitConversion = ExpressionNode::UnitConversion::Default); + void simplifyAndApproximate(Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation = ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion unitConversion = ExpressionNode::UnitConversion::Default); Expression reduce(ExpressionNode::ReductionContext context); Expression mapOnMatrixFirstChild(ExpressionNode::ReductionContext reductionContext); diff --git a/poincare/include/poincare/expression_node.h b/poincare/include/poincare/expression_node.h index 21b19004d..2d3b33a33 100644 --- a/poincare/include/poincare/expression_node.h +++ b/poincare/include/poincare/expression_node.h @@ -128,8 +128,13 @@ public: ReplaceAllSymbolsWithDefinitionsOrUndefined = 0, ReplaceAllDefinedSymbolsWithDefinition = 1, ReplaceDefinedFunctionsWithDefinitions = 2, - ReplaceAllSymbolsWithUndefinedAndDoNotReplaceUnits = 3, // Used in UnitConvert::shallowReduce - ReplaceAllSymbolsWithUndefinedAndReplaceUnits = 4 // Used in UnitConvert::shallowReduce + ReplaceAllSymbolsWithUndefined = 3, // Used in UnitConvert::shallowReduce + DoNotReplaceAnySymbol = 4 + }; + enum class UnitConversion { + None = 0, + Default, + InternationalSystem }; enum class Sign { Negative = -1, @@ -139,24 +144,27 @@ public: class ReductionContext { public: - ReductionContext(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ReductionTarget target, SymbolicComputation symbolicComputation = SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition) : + ReductionContext(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ReductionTarget target, SymbolicComputation symbolicComputation = SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, UnitConversion unitConversion = UnitConversion::Default) : m_context(context), m_complexFormat(complexFormat), m_angleUnit(angleUnit), m_target(target), - m_symbolicComputation(symbolicComputation) + m_symbolicComputation(symbolicComputation), + m_unitConversion(unitConversion) {} Context * context() { return m_context; } Preferences::ComplexFormat complexFormat() const { return m_complexFormat; } Preferences::AngleUnit angleUnit() const { return m_angleUnit; } ReductionTarget target() const { return m_target; } SymbolicComputation symbolicComputation() const { return m_symbolicComputation; } + UnitConversion unitConversion() const { return m_unitConversion; } private: Context * m_context; Preferences::ComplexFormat m_complexFormat; Preferences::AngleUnit m_angleUnit; ReductionTarget m_target; SymbolicComputation m_symbolicComputation; + UnitConversion m_unitConversion; }; virtual Sign sign(Context * context) const { return Sign::Unknown; } @@ -178,7 +186,7 @@ public: virtual float characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const; bool isOfType(Type * types, int length) const; - virtual Expression getUnit() const; // Only reduced nodes should answer + virtual Expression removeUnit(Expression * unit); // Only reduced nodes should answer /* Simplification */ /* SimplificationOrder returns: diff --git a/poincare/include/poincare/float.h b/poincare/include/poincare/float.h index 2a1b032bb..40f73ffed 100644 --- a/poincare/include/poincare/float.h +++ b/poincare/include/poincare/float.h @@ -50,7 +50,7 @@ public: Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } private: // Simplification - LayoutShape leftLayoutShape() const override { assert(false); return LayoutShape::Decimal; } + LayoutShape leftLayoutShape() const override { return LayoutShape::Decimal; } template Evaluation templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { return Complex::Builder((U)m_value); diff --git a/poincare/include/poincare/function.h b/poincare/include/poincare/function.h index bb2d2f92c..cc64a531b 100644 --- a/poincare/include/poincare/function.h +++ b/poincare/include/poincare/function.h @@ -28,9 +28,6 @@ public: int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[], ExpressionNode::SymbolicComputation symbolicComputation) const override; int getVariables(Context * context, isVariableTest isVariable, char * variables, int maxSizeVariable, int nextVariableIndex) const override; float characteristicXRange(Context * context, Preferences::AngleUnit angleUnit) const override; - /* getUnit() is ExpressionNode::getUnit -> - * as the function is reduced, it would have been replaced if it had a - * definition. It thus has no definition, so no unit. */ private: char m_name[0]; // MUST be the last member variable diff --git a/poincare/include/poincare/integer.h b/poincare/include/poincare/integer.h index 984041d9c..1d27e50c5 100644 --- a/poincare/include/poincare/integer.h +++ b/poincare/include/poincare/integer.h @@ -126,13 +126,17 @@ public: static int NumberOfBase10DigitsWithoutSign(const Integer & i); bool isOne() const { return (numberOfDigits() == 1 && digit(0) == 1 && !m_negative); }; bool isTwo() const { return (numberOfDigits() == 1 && digit(0) == 2 && !m_negative); }; + bool isThree() const { return (numberOfDigits() == 1 && digit(0) == 3 && !m_negative); }; bool isTen() const { return (numberOfDigits() == 1 && digit(0) == 10 && !m_negative); }; bool isMinusOne() const { return (numberOfDigits() == 1 && digit(0) == 1 && m_negative); }; + bool isMinusTwo() const { return (numberOfDigits() == 1 && digit(0) == 2 && m_negative); }; bool isZero() const { return (numberOfDigits() == 0); }; bool isEven() const { return ((digit(0) & 1) == 0); } - constexpr static int k_maxExtractableInteger = 0x7FFFFFFF; - int extractedInt() const { assert(numberOfDigits() == 0 || (numberOfDigits() <= 1 && digit(0) <= k_maxExtractableInteger)); return numberOfDigits() == 0 ? 0 : (m_negative ? -digit(0) : digit(0)); } + bool isExtractable() const { + return numberOfDigits() == 0 || (numberOfDigits() <= 1 && digit(0) <= k_maxExtractableInteger); + } + int extractedInt() const { assert(isExtractable()); return numberOfDigits() == 0 ? 0 : (m_negative ? -digit(0) : digit(0)); } // Comparison static int NaturalOrder(const Integer & i, const Integer & j); @@ -158,6 +162,7 @@ public: constexpr static int k_maxNumberOfDigits = 32; private: constexpr static int k_maxNumberOfDigitsBase10 = 308; // (2^32)^k_maxNumberOfDigits ~ 1E308 + constexpr static int k_maxExtractableInteger = 0x7FFFFFFF; // Constructors Integer(native_uint_t * digits, uint16_t numberOfDigits, bool negative); diff --git a/poincare/include/poincare/multiplication.h b/poincare/include/poincare/multiplication.h index 256c1d479..4b1c41a74 100644 --- a/poincare/include/poincare/multiplication.h +++ b/poincare/include/poincare/multiplication.h @@ -25,7 +25,7 @@ public: int polynomialDegree(Context * context, const char * symbolName) const override; int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[], ExpressionNode::SymbolicComputation symbolicComputation) const override; bool childAtIndexNeedsUserParentheses(const Expression & child, int childIndex) const override; - Expression getUnit() const override; + Expression removeUnit(Expression * unit) override; // Approximation template static Complex compute(const std::complex c, const std::complex d, Preferences::ComplexFormat complexFormat) { return Complex::Builder(c*d); } @@ -63,10 +63,9 @@ private: }; class Multiplication : public NAryExpression { - friend class AdditionNode; friend class Addition; friend class Power; - friend class UnitConvert; + friend class MultiplicationNode; public: Multiplication(const MultiplicationNode * n) : NAryExpression(n) {} static Multiplication Builder(const Tuple & children = {}) { return TreeHandle::NAryBuilder(convert(children)); } @@ -78,7 +77,7 @@ public: // Properties int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[], ExpressionNode::SymbolicComputation symbolicComputation) const; - Expression getUnit() const; + // Approximation template static void computeOnArrays(T * m, T * n, T * result, int mNumberOfColumns, int mNumberOfRows, int nNumberOfColumns); // Simplification @@ -90,9 +89,11 @@ public: NAryExpression::sortChildrenInPlace(order, context, false, canBeInterrupted); } private: + // Unit + Expression removeUnit(Expression * unit); + // Simplification Expression privateShallowReduce(ExpressionNode::ReductionContext reductionContext, bool expand, bool canBeInterrupted); - void mergeMultiplicationChildrenInPlace(); void factorizeBase(int i, int j, ExpressionNode::ReductionContext reductionContext); void mergeInChildByFactorizingBase(int i, Expression e, ExpressionNode::ReductionContext reductionContext); void factorizeExponent(int i, int j, ExpressionNode::ReductionContext reductionContext); @@ -106,7 +107,7 @@ private: static bool TermHasNumeralExponent(const Expression & e); static const Expression CreateExponent(Expression e); static inline const Expression Base(const Expression e); - void splitIntoNormalForm(Expression & numerator, Expression & denominator, Expression & units, ExpressionNode::ReductionContext reductionContext) const; + void splitIntoNormalForm(Expression & numerator, Expression & denominator, ExpressionNode::ReductionContext reductionContext) const; }; } diff --git a/poincare/include/poincare/n_ary_expression.h b/poincare/include/poincare/n_ary_expression.h index 6db14d2ed..97455f3af 100644 --- a/poincare/include/poincare/n_ary_expression.h +++ b/poincare/include/poincare/n_ary_expression.h @@ -52,6 +52,7 @@ public: Expression squashUnaryHierarchyInPlace() { return node()->squashUnaryHierarchyInPlace(); } + void mergeSameTypeChildrenInPlace(); /* allChildrenAreReal returns: * - 1 if all children are real * - 0 if all non real children are ComplexCartesian diff --git a/poincare/include/poincare/nth_root.h b/poincare/include/poincare/nth_root.h index d0d1d8e87..b1280494a 100644 --- a/poincare/include/poincare/nth_root.h +++ b/poincare/include/poincare/nth_root.h @@ -20,7 +20,7 @@ public: #endif private: - Expression getUnit() const override { assert(false); return ExpressionNode::getUnit(); } + Expression removeUnit(Expression * unit) override { assert(false); return ExpressionNode::removeUnit(unit); } // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/parenthesis.h b/poincare/include/poincare/parenthesis.h index 067898502..66fe39ca2 100644 --- a/poincare/include/poincare/parenthesis.h +++ b/poincare/include/poincare/parenthesis.h @@ -21,7 +21,7 @@ public: // Properties Type type() const override { return Type::Parenthesis; } int polynomialDegree(Context * context, const char * symbolName) const override; - Expression getUnit() const override { assert(false); return ExpressionNode::getUnit(); } + Expression removeUnit(Expression * unit) override { assert(false); return ExpressionNode::removeUnit(unit); } // Layout Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/power.h b/poincare/include/poincare/power.h index ad269d15d..815dc065c 100644 --- a/poincare/include/poincare/power.h +++ b/poincare/include/poincare/power.h @@ -29,7 +29,7 @@ public: Sign sign(Context * context) const override; Expression setSign(Sign s, ReductionContext reductionContext) override; bool childAtIndexNeedsUserParentheses(const Expression & child, int childIndex) const override; - Expression getUnit() const override; + Expression removeUnit(Expression * unit) override; int polynomialDegree(Context * context, const char * symbolName) const override; int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[], ExpressionNode::SymbolicComputation symbolicComputation) const override; @@ -79,12 +79,14 @@ public: int getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[]) const; Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); - Expression getUnit() const; private: constexpr static int k_maxExactPowerMatrix = 100; constexpr static int k_maxNumberOfTermsInExpandedMultinome = 25; + // Unit + Expression removeUnit(Expression * unit); + // Simplification Expression denominator(ExpressionNode::ReductionContext reductionContext) const; diff --git a/poincare/include/poincare/rational.h b/poincare/include/poincare/rational.h index 4e16c5cf0..c1af28415 100644 --- a/poincare/include/poincare/rational.h +++ b/poincare/include/poincare/rational.h @@ -45,7 +45,10 @@ public: // Basic test bool isZero() const { return unsignedNumerator().isZero(); } bool isOne() const { return signedNumerator().isOne() && isInteger(); } + bool isTwo() const { return signedNumerator().isTwo() && isInteger(); } + bool isThree() const { return signedNumerator().isThree() && isInteger(); } bool isMinusOne() const { return signedNumerator().isMinusOne() && isInteger(); } + bool isMinusTwo() const { return signedNumerator().isMinusTwo() && isInteger(); } bool isHalf() const { return signedNumerator().isOne() && denominator().isTwo(); } bool isMinusHalf() const { return signedNumerator().isMinusOne() && denominator().isTwo(); } bool isTen() const { return signedNumerator().isTen() && isInteger(); } @@ -89,7 +92,10 @@ public: bool isNegative() const { return node()->isNegative(); } bool isZero() const { return node()->isZero(); } bool isOne() const { return node()->isOne(); } + bool isTwo() const { return node()->isTwo(); } + bool isThree() const { return node()->isThree(); } bool isMinusOne() const { return node()->isMinusOne(); } + bool isMinusTwo() const { return node()->isMinusTwo(); } bool isHalf() const { return node()->isHalf(); } bool isMinusHalf() const { return node()->isMinusHalf(); } bool isTen() const { return node()->isTen(); } diff --git a/poincare/include/poincare/subtraction.h b/poincare/include/poincare/subtraction.h index f8703648e..92e213ba9 100644 --- a/poincare/include/poincare/subtraction.h +++ b/poincare/include/poincare/subtraction.h @@ -24,7 +24,7 @@ public: Type type() const override { return Type::Subtraction; } int polynomialDegree(Context * context, const char * symbolName) const override; bool childAtIndexNeedsUserParentheses(const Expression & child, int childIndex) const override; - Expression getUnit() const override { assert(false); return ExpressionNode::getUnit(); } + Expression removeUnit(Expression * unit) override { assert(false); return ExpressionNode::removeUnit(unit); } // Approximation template static Complex compute(const std::complex c, const std::complex d, Preferences::ComplexFormat complexFormat) { return Complex::Builder(c - d); } diff --git a/poincare/include/poincare/tree_handle.h b/poincare/include/poincare/tree_handle.h index b171871b6..30aea87fd 100644 --- a/poincare/include/poincare/tree_handle.h +++ b/poincare/include/poincare/tree_handle.h @@ -62,6 +62,9 @@ public: uint16_t identifier() const { return m_identifier; } TreeNode * node() const; + bool wasErasedByException() const { + return hasNode(m_identifier) && node() == nullptr; + } int nodeRetainCount() const { return node()->retainCount(); } size_t size() const; void * addressInPool() const { return reinterpret_cast(node()); } diff --git a/poincare/include/poincare/tree_pool.h b/poincare/include/poincare/tree_pool.h index cf5108d73..cb3094018 100644 --- a/poincare/include/poincare/tree_pool.h +++ b/poincare/include/poincare/tree_pool.h @@ -7,7 +7,6 @@ #include #include #if POINCARE_TREE_LOG -#include #include #endif diff --git a/poincare/include/poincare/trigonometry_cheat_table.h b/poincare/include/poincare/trigonometry_cheat_table.h index 8f77ef8f1..5b5cae99c 100644 --- a/poincare/include/poincare/trigonometry_cheat_table.h +++ b/poincare/include/poincare/trigonometry_cheat_table.h @@ -51,8 +51,8 @@ private: }; // END OF PAIR CLASS - constexpr Row(Pair angleInRadians, Pair angleInGradians, Pair angleInDegrees, Pair sine, Pair cosine, Pair tangent) : - m_pairs{angleInRadians, angleInGradians, angleInDegrees, sine, cosine, tangent} {} + constexpr Row(Pair angleInDegrees, Pair angleInRadians, Pair angleInGradians, Pair sine, Pair cosine, Pair tangent) : + m_pairs{angleInDegrees, angleInRadians, angleInGradians, sine, cosine, tangent} {} float floatForType(Type t) const { assert(((int) t) >= 0 && ((int) t) < k_numberOfPairs); return m_pairs[(int)t].value(); diff --git a/poincare/include/poincare/unit.h b/poincare/include/poincare/unit.h index 8565a77fc..f32dbee35 100644 --- a/poincare/include/poincare/unit.h +++ b/poincare/include/poincare/unit.h @@ -11,17 +11,27 @@ public: /* The units having the same physical dimension are grouped together. * Each such group has a standard representative with a standard prefix. * - * A standard unit is a derived unit, when defined from base units - * or otherwise a base unit (if no definition is provided). - * * Each representative has * - a root symbol * - a definition * - a list of allowed output prefixes * Given a Dimension, a representative in that Dimension and a Prefix * allowed for that representative, one may get a symbol and an Expression. + * + * FIXME ? + * The UnitNode class holds as members pointers to a Dimension, a + * Representative, a Prefix. Those nested classes may not be forward + * declared and must be defined in UnitNode and then aliased in Unit so as + * to be used outside. That technical limitation could have been avoided if + * UnitNode were itself a nested class of Unit, say Unit::Node. More + * generally, turning all the Poincare::...Node classes into nested + * Poincare::...::Node classes might be a more clever usage of namespaces + * and scopes. */ + // There are 7 base units from which all other units are derived. + static constexpr size_t NumberOfBaseUnits = 7; + class Prefix { public: constexpr Prefix(const char * symbol, int8_t exponent) : @@ -46,19 +56,19 @@ public: Yes }; template - constexpr Representative(const char * rootSymbol, const char * definition, const Prefixable prefixable, const Prefix (&outputPrefixes)[N]) : + constexpr Representative(const char * rootSymbol, const char * definition, const Prefixable prefixable, const Prefix * const (&outputPrefixes)[N]) : m_rootSymbol(rootSymbol), m_definition(definition), m_prefixable(prefixable), m_outputPrefixes(outputPrefixes), - m_outputPrefixesUpperBound(outputPrefixes + N) + m_outputPrefixesLength(N) { } const char * rootSymbol() const { return m_rootSymbol; } const char * definition() const { return m_definition; } bool isPrefixable() const { return m_prefixable == Prefixable::Yes; } - const Prefix * outputPrefixes() const { return m_outputPrefixes; } - const Prefix * outputPrefixesUpperBound() const { return m_outputPrefixesUpperBound; } + const Prefix * const * outputPrefixes() const { return m_outputPrefixes; } + size_t outputPrefixesLength() const { return m_outputPrefixesLength; } bool canParse(const char * symbol, size_t length, const Prefix * * prefix) const; int serialize(char * buffer, int bufferSize, const Prefix * prefix) const; @@ -67,25 +77,52 @@ public: const char * m_rootSymbol; const char * m_definition; const Prefixable m_prefixable; - const Prefix * m_outputPrefixes; - const Prefix * m_outputPrefixesUpperBound; + const Prefix * const * m_outputPrefixes; + const size_t m_outputPrefixesLength; }; class Dimension { public: + template + struct Vector { + struct Metrics { + size_t supportSize; + T norm; + }; + Metrics metrics() const; + static Vector FromBaseUnits(const Expression baseUnits); + const T coefficientAtIndex(size_t i) const { + assert(i < NumberOfBaseUnits); + return *(reinterpret_cast(this) + i); + } + void setCoefficientAtIndex(size_t i, T c) { + assert(i < NumberOfBaseUnits); + *(reinterpret_cast(this) + i) = c; + } + T time; + T distance; + T mass; + T current; + T temperature; + T amountOfSubstance; + T luminuousIntensity; + }; template - constexpr Dimension(const Representative (&representatives)[N], const Prefix * stdRepresentativePrefix) : + constexpr Dimension(Vector vector, const Representative (&representatives)[N], const Prefix * stdRepresentativePrefix) : + m_vector(vector), m_representatives(representatives), m_representativesUpperBound(representatives + N), m_stdRepresentativePrefix(stdRepresentativePrefix) { } + const Vector * vector() const { return &m_vector; } const Representative * stdRepresentative() const { return m_representatives; } const Representative * representativesUpperBound() const { return m_representativesUpperBound; } const Prefix * stdRepresentativePrefix() const { return m_stdRepresentativePrefix; } bool canParse(const char * symbol, size_t length, const Representative * * representative, const Prefix * * prefix) const; private: + Vector m_vector; const Representative * m_representatives; const Representative * m_representativesUpperBound; const Prefix * m_stdRepresentativePrefix; @@ -114,7 +151,7 @@ public: // Expression Properties Type type() const override { return Type::Unit; } Sign sign(Context * context) const override; - Expression getUnit() const override; + Expression removeUnit(Expression * unit) override; /* Layout */ Layout createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; @@ -147,10 +184,18 @@ private: }; class Unit final : public Expression { + friend class UnitNode; public: typedef UnitNode::Prefix Prefix; typedef UnitNode::Representative Representative; typedef UnitNode::Dimension Dimension; + /* TODO: Prefix, Representative and Dimension defined below must be defined + * only once and all units must be constructed from their pointers. This way + * we can easily check if two Unit objects are equal by comparing pointers. + * This saves us from overloading the == operator on Prefix, Representative + * and Dimension and saves time at execution. We should assert at compilation + * that only one occurence of each is built by maybe privatizing constructors + * on these classes? */ static constexpr const Prefix PicoPrefix = Prefix("p", -12), NanoPrefix = Prefix("n", -9), @@ -165,59 +210,59 @@ public: MegaPrefix = Prefix("M", 6), GigaPrefix = Prefix("G", 9), TeraPrefix = Prefix("T", 12); - static constexpr const Prefix - NoPrefix[] = { - EmptyPrefix - }, - NegativeLongScalePrefixes[] = { - PicoPrefix, - NanoPrefix, - MicroPrefix, - MilliPrefix, - EmptyPrefix, - }, - PositiveLongScalePrefixes[] = { - EmptyPrefix, - KiloPrefix, - MegaPrefix, - GigaPrefix, - TeraPrefix, - }, - LongScalePrefixes[] = { - PicoPrefix, - NanoPrefix, - MicroPrefix, - MilliPrefix, - EmptyPrefix, - KiloPrefix, - MegaPrefix, - GigaPrefix, - TeraPrefix, - }, - NegativePrefixes[] = { - PicoPrefix, - NanoPrefix, - MicroPrefix, - MilliPrefix, - CentiPrefix, - DeciPrefix, - EmptyPrefix, - }, - AllPrefixes[] = { - PicoPrefix, - NanoPrefix, - MicroPrefix, - MilliPrefix, - CentiPrefix, - DeciPrefix, - EmptyPrefix, - DecaPrefix, - HectoPrefix, - KiloPrefix, - MegaPrefix, - GigaPrefix, - TeraPrefix, + static constexpr const Prefix * NoPrefix[] = { + &EmptyPrefix }; + static constexpr const Prefix * NegativeLongScalePrefixes[] = { + &PicoPrefix, + &NanoPrefix, + &MicroPrefix, + &MilliPrefix, + &EmptyPrefix, + }; + static constexpr const Prefix * PositiveLongScalePrefixes[] = { + &EmptyPrefix, + &KiloPrefix, + &MegaPrefix, + &GigaPrefix, + &TeraPrefix, + }; + static constexpr const Prefix * LongScalePrefixes[] = { + &PicoPrefix, + &NanoPrefix, + &MicroPrefix, + &MilliPrefix, + &EmptyPrefix, + &KiloPrefix, + &MegaPrefix, + &GigaPrefix, + &TeraPrefix, + }; + static constexpr const Prefix * NegativePrefixes[] = { + &PicoPrefix, + &NanoPrefix, + &MicroPrefix, + &MilliPrefix, + &CentiPrefix, + &DeciPrefix, + &EmptyPrefix, + }; + static constexpr const Prefix * AllPrefixes[] = { + &PicoPrefix, + &NanoPrefix, + &MicroPrefix, + &MilliPrefix, + &CentiPrefix, + &DeciPrefix, + &EmptyPrefix, + &DecaPrefix, + &HectoPrefix, + &KiloPrefix, + &MegaPrefix, + &GigaPrefix, + &TeraPrefix, + }; + static constexpr size_t NumberOfBaseUnits = UnitNode::NumberOfBaseUnits; static constexpr const Representative TimeRepresentatives[] = { Representative("s", nullptr, @@ -406,130 +451,380 @@ public: Representative::Prefixable::Yes, NegativePrefixes), }; + // TODO: find a better way to define these pointers + static_assert(sizeof(TimeRepresentatives)/sizeof(Representative) == 7, "The Unit::SecondRepresentative, Unit::HourRepresentative and so on might require to be fixed if the TimeRepresentatives table was changed."); + static const Representative constexpr * SecondRepresentative = &TimeRepresentatives[0]; + static const Representative constexpr * MinuteRepresentative = &TimeRepresentatives[1]; + static const Representative constexpr * HourRepresentative = &TimeRepresentatives[2]; + static const Representative constexpr * DayRepresentative = &TimeRepresentatives[3]; + static const Representative constexpr * MonthRepresentative = &TimeRepresentatives[5]; + static const Representative constexpr * YearRepresentative = &TimeRepresentatives[6]; + static const Representative constexpr * MeterRepresentative = &DistanceRepresentatives[0]; + static const Representative constexpr * KilogramRepresentative = &MassRepresentatives[0]; + static const Representative constexpr * LiterRepresentative = &VolumeRepresentatives[0]; + static const Representative constexpr * WattRepresentative = &PowerRepresentatives[0]; + static_assert(sizeof(EnergyRepresentatives)/sizeof(Representative) == 2, "The Unit::ElectronVoltRepresentative might require to be fixed if the EnergyRepresentatives table was changed."); + static const Representative constexpr * ElectronVoltRepresentative = &EnergyRepresentatives[1]; static constexpr const Dimension DimensionTable[] = { /* The current table is sorted from most to least simple units. * The order determines the behavior of simplification. */ Dimension( + Dimension::Vector { + .time = 1, + .distance = 0, + .mass = 0, + .current = 0, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, TimeRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time = 0, + .distance = 1, + .mass = 0, + .current = 0, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, DistanceRepresentatives, &EmptyPrefix ), Dimension( - SolideAngleRepresentatives, - &EmptyPrefix - ), - Dimension( + Dimension::Vector { + .time = 0, + .distance = 0, + .mass = 1, + .current = 0, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, MassRepresentatives, &KiloPrefix ), Dimension( + Dimension::Vector { + .time = 0, + .distance = 0, + .mass = 0, + .current = 1, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, CurrentRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time = 0, + .distance = 0, + .mass = 0, + .current = 0, + .temperature = 1, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, TemperatureRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time = 0, + .distance = 0, + .mass = 0, + .current = 0, + .temperature = 0, + .amountOfSubstance = 1, + .luminuousIntensity = 0, + }, AmountOfSubstanceRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time = 0, + .distance = 0, + .mass = 0, + .current = 0, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 1, + }, LuminousIntensityRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time =-1, + .distance = 0, + .mass = 0, + .current = 0, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, FrequencyRepresentatives, &EmptyPrefix ), Dimension( - LuminousFluxRepresentatives, - &EmptyPrefix - ), - Dimension( - IlluminanceRepresentatives, - &EmptyPrefix - ), - Dimension( + Dimension::Vector { + .time =-2, + .distance = 1, + .mass = 1, + .current = 0, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, ForceRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time =-2, + .distance =-1, + .mass = 1, + .current = 0, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, PressureRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time =-2, + .distance = 2, + .mass = 1, + .current = 0, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, EnergyRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time =-3, + .distance = 2, + .mass = 1, + .current = 0, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, PowerRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time = 1, + .distance = 0, + .mass = 0, + .current = 1, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, ElectricChargeRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time =-3, + .distance = 2, + .mass = 1, + .current =-1, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, ElectricPotentialRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time = 4, + .distance =-2, + .mass =-1, + .current = 2, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, ElectricCapacitanceRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time =-3, + .distance = 2, + .mass = 1, + .current =-2, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, ElectricResistanceRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time = 3, + .distance =-2, + .mass =-1, + .current = 2, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, ElectricConductanceRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time =-2, + .distance = 2, + .mass = 1, + .current =-1, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, MagneticFluxRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time =-2, + .distance = 0, + .mass = 1, + .current =-1, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, MagneticFieldRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time =-2, + .distance = 2, + .mass = 1, + .current =-2, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, InductanceRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time =-1, + .distance = 0, + .mass = 0, + .current = 0, + .temperature = 0, + .amountOfSubstance = 1, + .luminuousIntensity = 0, + }, CatalyticActivityRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time = 0, + .distance = 2, + .mass = 0, + .current = 0, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, SurfaceRepresentatives, &EmptyPrefix ), Dimension( + Dimension::Vector { + .time = 0, + .distance = 3, + .mass = 0, + .current = 0, + .temperature = 0, + .amountOfSubstance = 0, + .luminuousIntensity = 0, + }, VolumeRepresentatives, &EmptyPrefix ), }; + // TODO: find a better way to find defines these pointers + static_assert(sizeof(DimensionTable)/sizeof(Dimension) == 23, "The Unit::TimeDimension, Unit::DistanceDimension and so on might require to be fixed if the Dimension table was changed."); + static const Dimension constexpr * TimeDimension = &DimensionTable[0] ; + static const Dimension constexpr * DistanceDimension = &DimensionTable[1]; + static const Dimension constexpr * MassDimension = &DimensionTable[2]; + static const Dimension constexpr * EnergyDimension = &DimensionTable[10]; + static const Dimension constexpr * PowerDimension = &DimensionTable[11]; + static const Dimension constexpr * VolumeDimension = &DimensionTable[sizeof(DimensionTable)/sizeof(Dimension)-1]; + static constexpr const Unit::Dimension * DimensionTableUpperBound = DimensionTable + sizeof(DimensionTable)/sizeof(Dimension); static bool CanParse(const char * symbol, size_t length, const Dimension * * dimension, const Representative * * representative, const Prefix * * prefix); - const Dimension * dimension() const { return static_cast(node())->dimension(); } - Unit(const UnitNode * node) : Expression(node) {} static Unit Builder(const Dimension * dimension, const Representative * representative, const Prefix * prefix); - Expression getUnit() const { return clone(); } + static Unit Kilometer() { return Builder(DistanceDimension, MeterRepresentative, &KiloPrefix); } + static Unit Second() { return Builder(TimeDimension, SecondRepresentative, &EmptyPrefix); } + static Unit Minute() { return Builder(TimeDimension, MinuteRepresentative, &EmptyPrefix); } + static Unit Hour() { return Builder(TimeDimension, HourRepresentative, &EmptyPrefix); } + static Unit Day() { return Builder(TimeDimension, DayRepresentative, &EmptyPrefix); } + static Unit Month() { return Builder(TimeDimension, MonthRepresentative, &EmptyPrefix); } + static Unit Year() { return Builder(TimeDimension, YearRepresentative, &EmptyPrefix); } + static Unit Liter() { return Builder(VolumeDimension, LiterRepresentative, &EmptyPrefix); } + static Unit ElectronVolt() { return Builder(EnergyDimension, ElectronVoltRepresentative, &EmptyPrefix); } + static Unit Watt() { return Builder(PowerDimension, WattRepresentative, &EmptyPrefix); } + static Expression BuildTimeSplit(double seconds, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit); + + static bool IsIS(Expression & e); + static bool IsISSpeed(Expression & e); + static bool IsISVolume(Expression & e); + static bool IsISEnergy(Expression & e); + static bool IsISTime(Expression & e); + bool isMeter() const; + bool isSecond() const; + bool isKilogram() const; // Simplification Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); - void chooseBestMultipleForValue(double & value, const int exponent, ExpressionNode::ReductionContext reductionContext); + static void ChooseBestRepresentativeAndPrefixForValue(Expression * units, double * value, ExpressionNode::ReductionContext reductionContext) { return ChooseBestMultipleForValue(units, value, true, reductionContext); } + static void ChooseBestPrefixForValue(Expression * units, double * value, ExpressionNode::ReductionContext reductionContext) { return ChooseBestMultipleForValue(units, value, false, reductionContext); } + + // This could be computed from the time representatives but we save time by using constexpr double + static constexpr double SecondsPerMinute = 60.0; +private: + static constexpr double MinutesPerHour = 60.0; + static constexpr double HoursPerDay = 24.0; + static constexpr double DaysPerYear = 365.25; + static constexpr double MonthPerYear = 12.0; + static constexpr double DaysPerMonth = DaysPerYear/MonthPerYear; + UnitNode * node() const { return static_cast(Expression::node()); } + bool isIS() const; + static void ChooseBestMultipleForValue(Expression * units, double * value, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext); + void chooseBestMultipleForValue(double * value, const int exponent, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext); + Expression removeUnit(Expression * unit); }; } diff --git a/poincare/include/poincare/unit_convert.h b/poincare/include/poincare/unit_convert.h index 70943a9f5..73379fb1c 100644 --- a/poincare/include/poincare/unit_convert.h +++ b/poincare/include/poincare/unit_convert.h @@ -13,16 +13,17 @@ public: size_t size() const override { return sizeof(UnitConvertNode); } #if POINCARE_TREE_LOG void logNodeName(std::ostream & stream) const override { - stream << "UnivtConvert"; + stream << "UnitConvert"; } #endif // ExpressionNode Type type() const override { return Type::UnitConvert; } private: - Expression getUnit() const override { assert(false); return ExpressionNode::getUnit(); } + Expression removeUnit(Expression * unit) override; // Simplification - Expression shallowReduce(ReductionContext reductionContext) override; + void deepReduceChildren(ExpressionNode::ReductionContext reductionContext) override; + Expression shallowBeautify(ReductionContext reductionContext) override; // Evalutation Evaluation approximate(SinglePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } Evaluation approximate(DoublePrecision p, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } @@ -36,8 +37,8 @@ public: static UnitConvert Builder(Expression value, Expression unit) { return TreeHandle::FixedArityBuilder({value, unit}); } // Expression - Expression shallowReduce(ExpressionNode::ReductionContext reductionContext); - + void deepReduceChildren(ExpressionNode::ReductionContext reductionContext); + Expression shallowBeautify(ExpressionNode::ReductionContext reductionContext); private: UnitConvertNode * node() const { return static_cast(Expression::node()); } }; diff --git a/poincare/src/absolute_value.cpp b/poincare/src/absolute_value.cpp index bf4aee0d1..6945e1fc5 100644 --- a/poincare/src/absolute_value.cpp +++ b/poincare/src/absolute_value.cpp @@ -19,10 +19,6 @@ Expression AbsoluteValueNode::setSign(Sign s, ReductionContext reductionContext) return AbsoluteValue(this); } -Expression AbsoluteValueNode::getUnit() const { - return childAtIndex(0)->getUnit(); -} - Layout AbsoluteValueNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { return AbsoluteValueLayout::Builder(childAtIndex(0)->createLayout(floatDisplayMode, numberOfSignificantDigits)); } diff --git a/poincare/src/addition.cpp b/poincare/src/addition.cpp index aa120632a..141cf5a5f 100644 --- a/poincare/src/addition.cpp +++ b/poincare/src/addition.cpp @@ -29,11 +29,6 @@ int AdditionNode::getPolynomialCoefficients(Context * context, const char * symb return Addition(this).getPolynomialCoefficients(context, symbolName, coefficients, symbolicComputation); } -Expression AdditionNode::getUnit() const { - // The expression is reduced, so we can just ask the unit of the first child - return childAtIndex(0)->getUnit(); -} - // Layout Layout AdditionNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { @@ -148,15 +143,11 @@ Expression Addition::shallowReduce(ExpressionNode::ReductionContext reductionCon } /* Step 1: Addition is associative, so let's start by merging children which - * are additions. */ - int i = 0; - while (i < numberOfChildren()) { - if (childAtIndex(i).type() == ExpressionNode::Type::Addition) { - mergeChildrenAtIndexInPlace(childAtIndex(i), i); - continue; - } - i++; - } + * are additions. + * TODO If the parent Expression is an Addition, one should perhaps + * return now and let the parent do the reduction. + */ + mergeSameTypeChildrenInPlace(); const int childrenCount = numberOfChildren(); assert(childrenCount > 1); @@ -164,12 +155,26 @@ Expression Addition::shallowReduce(ExpressionNode::ReductionContext reductionCon /* Step 2: Handle the units. All children should have the same unit, otherwise * the result is not homogeneous. */ { - Expression unit = childAtIndex(0).getUnit(); + Expression unit; + childAtIndex(0).removeUnit(&unit); + const bool hasUnit = !unit.isUninitialized(); for (int i = 1; i < childrenCount; i++) { - if (!unit.isIdenticalTo(childAtIndex(i).getUnit())) { + Expression otherUnit; + childAtIndex(i).removeUnit(&otherUnit); + if (hasUnit == otherUnit.isUninitialized() || + (hasUnit && !unit.isIdenticalTo(otherUnit))) + { return replaceWithUndefinedInPlace(); } } + if (hasUnit) { + Expression addition = shallowReduce(reductionContext); + Multiplication result = Multiplication::Builder(unit); + result.mergeSameTypeChildrenInPlace(); + addition.replaceWithInPlace(result); + result.addChildAtIndexInPlace(addition, 0, 1); + return std::move(result); + } } // Step 3: Sort the children @@ -231,7 +236,7 @@ Expression Addition::shallowReduce(ExpressionNode::ReductionContext reductionCon /* Step 5: Factorize like terms. Thanks to the simplification order, those are * next to each other at this point. */ - i = 0; + int i = 0; while (i < numberOfChildren()-1) { Expression e1 = childAtIndex(i); Expression e2 = childAtIndex(i+1); @@ -350,7 +355,7 @@ bool Addition::TermsHaveIdenticalNonNumeralFactors(const Expression & e1, const int numberOfNonNumeralFactors = numberOfNonNumeralFactorsInE1; if (numberOfNonNumeralFactors == 1) { Expression nonNumeralFactor = FirstNonNumeralFactor(e1); - if (nonNumeralFactor.recursivelyMatches(Expression::IsRandom, context, true)) { + if (nonNumeralFactor.recursivelyMatches(Expression::IsRandom, context)) { return false; } return FirstNonNumeralFactor(e1).isIdenticalTo(FirstNonNumeralFactor(e2)); @@ -375,7 +380,7 @@ Expression Addition::factorizeOnCommonDenominator(ExpressionNode::ReductionConte Expression childI = childAtIndex(i); Expression currentDenominator = childI.denominator(reductionContext); if (!currentDenominator.isUninitialized()) { - if (currentDenominator.recursivelyMatches(Expression::IsRandom, reductionContext.context(), true)) { + if (currentDenominator.recursivelyMatches(Expression::IsRandom, reductionContext.context())) { // Remove "random" factors removeChildInPlace(childI, childI.numberOfChildren()); a.addChildAtIndexInPlace(childI, a.numberOfChildren(), a.numberOfChildren()); diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index f274ab3c6..41b9b04bd 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -87,14 +87,29 @@ bool Expression::isRationalOne() const { return type() == ExpressionNode::Type::Rational && convert().isOne(); } -bool Expression::recursivelyMatches(ExpressionTest test, Context * context, bool replaceSymbols) const { +bool Expression::recursivelyMatches(ExpressionTest test, Context * context, ExpressionNode::SymbolicComputation replaceSymbols) const { if (test(*this, context)) { return true; } + + // Handle symbols and functions ExpressionNode::Type t = type(); - if (replaceSymbols && (t == ExpressionNode::Type::Symbol || t == ExpressionNode::Type::Function)) { - return SymbolAbstract::matches(convert(), test, context); + if (t == ExpressionNode::Type::Symbol || t == ExpressionNode::Type::Function) { + assert(replaceSymbols == ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition + || replaceSymbols == ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions + || replaceSymbols == ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol); // We need only those cases for now + + if (replaceSymbols == ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol + || (replaceSymbols == ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions + && t == ExpressionNode::Type::Symbol)) + { + return false; + } + assert(replaceSymbols == ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition + || t == ExpressionNode::Type::Function); + return SymbolAbstract::matches(convert(), test, context); } + const int childrenCount = this->numberOfChildren(); for (int i = 0; i < childrenCount; i++) { if (childAtIndex(i).recursivelyMatches(test, context, replaceSymbols)) { @@ -126,7 +141,7 @@ bool Expression::deepIsMatrix(Context * context) const { } // Scalar expressions ExpressionNode::Type types1[] = {ExpressionNode::Type::BinomialCoefficient, ExpressionNode::Type::Derivative, ExpressionNode::Type::Determinant, ExpressionNode::Type::DivisionQuotient, ExpressionNode::Type::DivisionRemainder, ExpressionNode::Type::Factor, ExpressionNode::Type::GreatCommonDivisor, ExpressionNode::Type::Integral, ExpressionNode::Type::LeastCommonMultiple, ExpressionNode::Type::MatrixTrace, ExpressionNode::Type::NthRoot, ExpressionNode::Type::PermuteCoefficient, ExpressionNode::Type::Randint, ExpressionNode::Type::Round, ExpressionNode::Type::SignFunction, ExpressionNode::Type::SquareRoot}; - if (isOfType(types1, 16)) { + if (isOfType(types1, sizeof(types1)/sizeof(ExpressionNode::Type))) { return false; } // The children were sorted so any expression which is a matrix (deeply) would be at the end @@ -137,7 +152,7 @@ bool Expression::deepIsMatrix(Context * context) const { } // Logarithm, Power, Product, Sum are matrices only if their first child is a matrix ExpressionNode::Type types2[] = {ExpressionNode::Type::Logarithm, ExpressionNode::Type::Power, ExpressionNode::Type::Product, ExpressionNode::Type::Sum}; - if (isOfType(types2, 4)) { + if (isOfType(types2, sizeof(types2)/sizeof(ExpressionNode::Type))) { assert(numberOfChildren() > 0); return childAtIndex(0).deepIsMatrix(context); } @@ -197,7 +212,7 @@ bool containsVariables(const Expression e, char * variables, int maxVariableSize } bool Expression::getLinearCoefficients(char * variables, int maxVariableSize, Expression coefficients[], Expression constant[], Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation) const { - assert(!recursivelyMatches(IsMatrix, context, true)); + assert(!recursivelyMatches(IsMatrix, context, symbolicComputation)); // variables is in fact of type char[k_maxNumberOfVariables][maxVariableSize] int index = 0; while (variables[index*maxVariableSize] != 0) { @@ -223,7 +238,7 @@ bool Expression::getLinearCoefficients(char * variables, int maxVariableSize, Ex /* degree is supposed to be 0 or 1. Otherwise, it means that equation * is 'undefined' due to the reduction of 0*inf for example. * (ie, x*y*inf = 0) */ - assert(!recursivelyMatches([](const Expression e, Context * context) { return e.isUndefined(); }, context, true)); + assert(!recursivelyMatches([](const Expression e, Context * context) { return e.isUndefined(); }, context)); return false; } /* The equation is can be written: a_1*x+a_0 with a_1 and a_0 x-independent. @@ -350,7 +365,9 @@ Expression Expression::defaultHandleUnitsInChildren() { // Generically, an Expression does not accept any Unit in its children. const int childrenCount = numberOfChildren(); for (int i = 0; i < childrenCount; i++) { - if (childAtIndex(i).hasUnit()) { + Expression unit; + childAtIndex(i).removeUnit(&unit); + if (!unit.isUninitialized()) { return replaceWithUndefinedInPlace(); } } @@ -471,7 +488,7 @@ int Expression::getPolynomialReducedCoefficients(const char * symbolName, Expres /* Units */ bool Expression::hasUnit() const { - return recursivelyMatches([](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Unit; }, nullptr, false); + return recursivelyMatches([](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Unit; }, nullptr, ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol); } /* Complex */ @@ -492,7 +509,7 @@ Preferences::ComplexFormat Expression::UpdatedComplexFormatWithTextInput(Prefere } Preferences::ComplexFormat Expression::UpdatedComplexFormatWithExpressionInput(Preferences::ComplexFormat complexFormat, const Expression & exp, Context * context) { - if (complexFormat == Preferences::ComplexFormat::Real && exp.recursivelyMatches([](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Constant && static_cast(e).isIComplex(); }, context, true)) { + if (complexFormat == Preferences::ComplexFormat::Real && exp.recursivelyMatches([](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Constant && static_cast(e).isIComplex(); }, context)) { return Preferences::ComplexFormat::Cartesian; } return complexFormat; @@ -569,12 +586,12 @@ int Expression::serialize(char * buffer, int bufferSize, Preferences::PrintFloat /* Simplification */ -Expression Expression::ParseAndSimplify(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation) { +Expression Expression::ParseAndSimplify(const char * text, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation, ExpressionNode::UnitConversion unitConversion) { Expression exp = Parse(text, context, false); if (exp.isUninitialized()) { return Undefined::Builder(); } - exp = exp.simplify(ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::User, symbolicComputation)); + exp = exp.simplify(ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::User, symbolicComputation, unitConversion)); /* simplify might have been interrupted, in which case the resulting * expression is uninitialized, so we need to check that. */ if (exp.isUninitialized()) { @@ -583,7 +600,7 @@ Expression Expression::ParseAndSimplify(const char * text, Context * context, Pr return exp; } -void Expression::ParseAndSimplifyAndApproximate(const char * text, Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation) { +void Expression::ParseAndSimplifyAndApproximate(const char * text, Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation, ExpressionNode::UnitConversion unitConversion) { assert(simplifiedExpression); Expression exp = Parse(text, context, false); if (exp.isUninitialized()) { @@ -591,7 +608,7 @@ void Expression::ParseAndSimplifyAndApproximate(const char * text, Expression * *approximateExpression = Undefined::Builder(); return; } - exp.simplifyAndApproximate(simplifiedExpression, approximateExpression, context, complexFormat, angleUnit, symbolicComputation); + exp.simplifyAndApproximate(simplifiedExpression, approximateExpression, context, complexFormat, angleUnit, symbolicComputation, unitConversion); /* simplify might have been interrupted, in which case the resulting * expression is uninitialized, so we need to check that. */ if (simplifiedExpression->isUninitialized()) { @@ -604,7 +621,7 @@ void Expression::ParseAndSimplifyAndApproximate(const char * text, Expression * Expression Expression::simplify(ExpressionNode::ReductionContext reductionContext) { sSimplificationHasBeenInterrupted = false; - Expression e = deepReduce(reductionContext); + Expression e = reduce(reductionContext); if (!sSimplificationHasBeenInterrupted) { e = e.deepBeautify(reductionContext); } @@ -673,17 +690,16 @@ void Expression::beautifyAndApproximateScalar(Expression * simplifiedExpression, } } -void Expression::simplifyAndApproximate(Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation) { +void Expression::simplifyAndApproximate(Expression * simplifiedExpression, Expression * approximateExpression, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation, ExpressionNode::UnitConversion unitConversion) { assert(simplifiedExpression); sSimplificationHasBeenInterrupted = false; // Step 1: we reduce the expression - ExpressionNode::ReductionContext userReductionContext = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::User, symbolicComputation); - const bool isUnitConvert = type() == ExpressionNode::Type::UnitConvert; - Expression e = clone().deepReduce(userReductionContext); + ExpressionNode::ReductionContext userReductionContext = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::User, symbolicComputation, unitConversion); + Expression e = clone().reduce(userReductionContext); if (sSimplificationHasBeenInterrupted) { sSimplificationHasBeenInterrupted = false; - ExpressionNode::ReductionContext systemReductionContext = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::SystemForApproximation, symbolicComputation); - e = deepReduce(systemReductionContext); + ExpressionNode::ReductionContext systemReductionContext = ExpressionNode::ReductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::SystemForApproximation, symbolicComputation, unitConversion); + e = reduce(systemReductionContext); } *simplifiedExpression = Expression(); if (sSimplificationHasBeenInterrupted) { @@ -712,13 +728,6 @@ void Expression::simplifyAndApproximate(Expression * simplifiedExpression, Expre if (approximateExpression) { static_cast(approximateExpression)->setDimensions(m.numberOfRows(), m.numberOfColumns()); } - } else if (isUnitConvert) { - /* Case 2: the initial expression is a unit convert, so we already beautified the result. */ - *simplifiedExpression = e; - if (approximateExpression) { - *approximateExpression = e; - } - return; } else { /* Case 3: the reduced expression is scalar or too complex to respect the * complex format. */ @@ -825,12 +834,6 @@ Expression Expression::deepBeautify(ExpressionNode::ReductionContext reductionCo for (int i = 0; i < nbChildren; i++) { Expression child = e.childAtIndex(i); child = child.deepBeautify(reductionContext); - /* Unit::shallowBeautify replaces units in inhomogeneous Expression by - * Undefined. Undefined children must be bubbled up to the root. - */ - if (child.type() == ExpressionNode::Type::Undefined) { - return e.replaceWithUndefinedInPlace(); - } // We add missing Parentheses after beautifying the parent and child if (e.node()->childAtIndexNeedsUserParentheses(child, i)) { e.replaceChildAtIndexInPlace(i, Parenthesis::Builder(child)); diff --git a/poincare/src/expression_node.cpp b/poincare/src/expression_node.cpp index 94115d40e..fe117da7e 100644 --- a/poincare/src/expression_node.cpp +++ b/poincare/src/expression_node.cpp @@ -136,8 +136,8 @@ bool ExpressionNode::isOfType(Type * types, int length) const { return false; } -Expression ExpressionNode::getUnit() const { - return Undefined::Builder(); +Expression ExpressionNode::removeUnit(Expression * unit) { + return Expression(this); } void ExpressionNode::setChildrenInPlace(Expression other) { diff --git a/poincare/src/function.cpp b/poincare/src/function.cpp index 9ed5e7fae..ae5764af2 100644 --- a/poincare/src/function.cpp +++ b/poincare/src/function.cpp @@ -115,12 +115,14 @@ Expression Function::replaceSymbolWithExpression(const SymbolAbstract & symbol, } Expression Function::shallowReduce(ExpressionNode::ReductionContext reductionContext) { - if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefinedAndDoNotReplaceUnits - || reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefinedAndReplaceUnits + if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined || childAtIndex(0).isUndefined()) { return replaceWithUndefinedInPlace(); } + if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol) { + return *this; + } Expression result = SymbolAbstract::Expand(*this, reductionContext.context(), true, reductionContext.symbolicComputation()); if (result.isUninitialized()) { if (reductionContext.symbolicComputation() != ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined) { diff --git a/poincare/src/integer.cpp b/poincare/src/integer.cpp index b1c197398..6cbe5b91c 100644 --- a/poincare/src/integer.cpp +++ b/poincare/src/integer.cpp @@ -18,9 +18,6 @@ extern "C" { #include #include } -#if POINCARE_INTEGER_LOG -#include -#endif #include namespace Poincare { diff --git a/poincare/src/layout_node.cpp b/poincare/src/layout_node.cpp index 7c33a2f3d..6058df3f2 100644 --- a/poincare/src/layout_node.cpp +++ b/poincare/src/layout_node.cpp @@ -169,7 +169,6 @@ bool LayoutNode::protectedIsIdenticalTo(Layout l) { return false; } for (int i = 0; i < numberOfChildren(); i++) { - Layout child = childAtIndex(i); if (!childAtIndex(i)->isIdenticalTo(l.childAtIndex(i))) { return false; } diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index 2056768ee..45eae68e9 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -65,8 +65,8 @@ bool MultiplicationNode::childAtIndexNeedsUserParentheses(const Expression & chi return child.isOfType(types, 2); } -Expression MultiplicationNode::getUnit() const { - return Multiplication(this).getUnit(); +Expression MultiplicationNode::removeUnit(Expression * unit) { + return Multiplication(this).removeUnit(unit); } template @@ -275,24 +275,30 @@ int Multiplication::getPolynomialCoefficients(Context * context, const char * sy return deg; } -Expression Multiplication::getUnit() const { - const int childrenCount = numberOfChildren(); - if (childrenCount == 1) { - return childAtIndex(0).getUnit(); - } - Multiplication result = Multiplication::Builder(); +Expression Multiplication::removeUnit(Expression * unit) { + Multiplication unitMult = Multiplication::Builder(); int resultChildrenCount = 0; - for (int i = 0; i < childrenCount; i++) { - Expression currentUnit = childAtIndex(i).getUnit(); - if (!currentUnit.isUndefined()) { - result.addChildAtIndexInPlace(currentUnit, resultChildrenCount, resultChildrenCount); + for (int i = 0; i < numberOfChildren(); i++) { + Expression currentUnit; + childAtIndex(i).removeUnit(¤tUnit); + if (!currentUnit.isUninitialized()) { + unitMult.addChildAtIndexInPlace(currentUnit, resultChildrenCount, resultChildrenCount); resultChildrenCount++; + assert(childAtIndex(i).isRationalOne()); + removeChildAtIndexInPlace(i--); } } if (resultChildrenCount == 0) { - return Undefined::Builder(); + *unit = Expression(); + } else { + *unit = unitMult.squashUnaryHierarchyInPlace(); } - return std::move(result); + if (numberOfChildren() == 0) { + Expression one = Rational::Builder(1); + replaceWithInPlace(one); + return one; + } + return squashUnaryHierarchyInPlace(); } template @@ -322,82 +328,32 @@ Expression Multiplication::shallowReduce(ExpressionNode::ReductionContext reduct return privateShallowReduce(reductionContext, true, true); } -static void ExponentsCopy(Integer (&dst)[numberOfFondamentalUnits], const Integer (&src)[numberOfFondamentalUnits]) { - for (int i = 0; i < numberOfFondamentalUnits; i++) { - dst[i] = src[i]; - } -} - -static void ExponentsMetrics(const Integer (&exponents)[numberOfFondamentalUnits], size_t & supportSize, Integer & norm) { - assert(supportSize == 0 && norm.isZero()); - for (int i = 0; i < numberOfFondamentalUnits; i++) { - Integer unsignedExponent = exponents[i]; - unsignedExponent.setNegative(false); - if (!unsignedExponent.isZero()) { - supportSize++; - norm = Integer::Addition(norm, unsignedExponent); - } - } -} - -static void ExponentsOfBaseUnits(const Expression units, Integer (&exponents)[numberOfFondamentalUnits]) { - // Make sure the provided Expression is a Multiplication - Expression u = units; - if (u.type() == ExpressionNode::Type::Unit || u.type() == ExpressionNode::Type::Power) { - u = Multiplication::Builder(u.clone()); - } - const int numberOfChildren = u.numberOfChildren(); - for (int i = 0; i < numberOfChildren; i++) { - Expression factor = u.childAtIndex(i); - - // Get the unit's exponent - Integer exponent(1); - if (factor.type() == ExpressionNode::Type::Power) { - Expression exp = factor.childAtIndex(1); - assert(exp.type() == ExpressionNode::Type::Rational && static_cast(exp).isInteger()); - exponent = static_cast(exp).signedIntegerNumerator(); - factor = factor.childAtIndex(0); - } - - // The leading factors may not be of Unit type - if (factor.type() != ExpressionNode::Type::Unit) { - continue; - } - - // Fill the exponents array with the unit's exponent - const int indexInTable = static_cast(factor).dimension() - Unit::DimensionTable; - assert(0 <= indexInTable && indexInTable < numberOfFondamentalUnits); - exponents[indexInTable] = exponent; - } -} - static bool CanSimplifyUnitProduct( - const Integer (&unitsExponents)[numberOfFondamentalUnits], const Integer (&entryUnitExponents)[numberOfFondamentalUnits], const Integer entryUnitNorm, const Expression entryUnit, - Integer (*operationOnExponents)(const Integer & unitsExponent, const Integer & entryUnitExponent), - Expression & bestUnit, Integer & bestUnitNorm, Integer (&bestRemainderExponents)[numberOfFondamentalUnits], size_t & bestRemainderSupportSize, Integer & bestRemainderNorm) { + const Unit::Dimension::Vector &unitsExponents, Unit::Dimension::Vector::Metrics &unitsMetrics, + const Unit::Dimension::Vector *entryUnitExponents, int8_t entryUnitNorm, int8_t entryUnitExponent, + int8_t & bestUnitExponent, Unit::Dimension::Vector &bestRemainderExponents, Unit::Dimension::Vector::Metrics & bestRemainderMetrics) { /* This function tries to simplify a Unit product (given as the * 'unitsExponents' Integer array), by applying a given operation. If the * result of the operation is simpler, 'bestUnit' and * 'bestRemainder' are updated accordingly. */ - Integer simplifiedExponents[numberOfFondamentalUnits]; - for(int i = 0; i < numberOfFondamentalUnits; i++){ - simplifiedExponents[i] = Integer(0); + Unit::Dimension::Vector simplifiedExponents; + Integer (*operationOnExponents)(const Integer &, const Integer &) = entryUnitExponent == -1 ? Integer::Addition : Integer::Subtraction; + for (size_t i = 0; i < Unit::NumberOfBaseUnits; i++) { + simplifiedExponents.setCoefficientAtIndex(i, operationOnExponents(unitsExponents.coefficientAtIndex(i), entryUnitExponents->coefficientAtIndex(i))); } - for (int i = 0; i < numberOfFondamentalUnits; i++) { - simplifiedExponents[i] = operationOnExponents(unitsExponents[i], entryUnitExponents[i]); - } - size_t simplifiedSupportSize = 0; - Integer simplifiedNorm(0); - ExponentsMetrics(simplifiedExponents, simplifiedSupportSize, simplifiedNorm); - bool isSimpler = simplifiedSupportSize < bestRemainderSupportSize || - (simplifiedSupportSize == bestRemainderSupportSize && - Integer::Addition(simplifiedNorm, entryUnitNorm).isLowerThan(Integer::Addition(bestRemainderNorm, bestUnitNorm))); + Unit::Dimension::Vector::Metrics simplifiedMetrics = simplifiedExponents.metrics(); + Unit::Dimension::Vector::Metrics candidateMetrics = { + .supportSize = 1 + simplifiedMetrics.supportSize, + .norm = Integer::Addition(entryUnitNorm, simplifiedMetrics.norm) + }; + bool isSimpler = candidateMetrics.supportSize < unitsMetrics.supportSize || + (candidateMetrics.supportSize == unitsMetrics.supportSize && + candidateMetrics.norm.isLowerThan(unitsMetrics.norm)); if (isSimpler) { - bestUnit = entryUnit; - bestUnitNorm = entryUnitNorm; - ExponentsCopy(bestRemainderExponents, simplifiedExponents); - bestRemainderSupportSize = simplifiedSupportSize; - bestRemainderNorm = simplifiedNorm; + bestUnitExponent = entryUnitExponent; + bestRemainderExponents = simplifiedExponents; + bestRemainderMetrics = simplifiedMetrics; + unitsMetrics = candidateMetrics; } return isSimpler; } @@ -406,8 +362,8 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu /* Beautifying a Multiplication consists in several possible operations: * - Add Opposite ((-3)*x -> -(3*x), useful when printing fractions) * - Recognize derived units in the product of units - * - Creating a Division if there's either a term with a power of -1 (a.b^(-1) - * shall become a/b) or a non-integer rational term (3/2*a -> (3*a)/2). */ + * - Creating a Division if relevant + */ // Step 1: Turn -n*A into -(n*A) Expression noNegativeNumeral = makePositiveAnyNegativeNumeralFactor(reductionContext); @@ -419,159 +375,130 @@ Expression Multiplication::shallowBeautify(ExpressionNode::ReductionContext redu return std::move(o); } - Expression numer, denom, units; - splitIntoNormalForm(numer, denom, units, reductionContext); - - /* Step 2: Recognize derived units - * The reason why 'units' is handled before 'numer' and 'denom' is that this - * step is likely to alter the latter Expressions. - */ - if (!units.isUninitialized()) { - /* In the following: - * - Look up in the table of derived units, the one which itself or its inverse simplifies 'units' the most. - * - If an entry is found, simplify 'units' and add the corresponding unit or its inverse in 'unitsAccu'. - * - Repeat those steps until no more simplification is possible. - */ - Multiplication unitsAccu = Multiplication::Builder(); - Integer unitsExponents[numberOfFondamentalUnits]; - for(int i = 0; i < numberOfFondamentalUnits; i++){ - unitsExponents[i] = Integer(0); - } - ExponentsOfBaseUnits(units, unitsExponents); - size_t unitsSupportSize = 0; - Integer unitsNorm(0); - ExponentsMetrics(unitsExponents, unitsSupportSize, unitsNorm); - Integer bestRemainderExponents[numberOfFondamentalUnits]; - for(int i = 0; i < numberOfFondamentalUnits; i++){ - bestRemainderExponents[i] = Integer(0); - } - while (unitsSupportSize > 1) { - Expression bestUnit; - Integer bestUnitNorm(0); - size_t bestRemainderSupportSize = unitsSupportSize - 1; - Integer bestRemainderNorm = unitsNorm; - for (const Unit::Dimension * dim = Unit::DimensionTable + 8; dim < Unit::DimensionTableUpperBound; dim++) { - Unit entryUnit = Unit::Builder(dim, dim->stdRepresentative(), dim->stdRepresentativePrefix()); - Integer entryUnitExponents[numberOfFondamentalUnits]; - for(int i = 0; i < numberOfFondamentalUnits; i++){ - entryUnitExponents[i] = Integer(0); - } - Integer entryUnitNorm(0); - size_t entryUnitSupportSize = 0; - ExponentsOfBaseUnits(entryUnit.clone().shallowReduce(reductionContext), entryUnitExponents); - ExponentsMetrics(entryUnitExponents, entryUnitSupportSize, entryUnitNorm); - CanSimplifyUnitProduct( - unitsExponents, entryUnitExponents, entryUnitNorm, entryUnit, - Integer::Subtraction, - bestUnit, bestUnitNorm, bestRemainderExponents, bestRemainderSupportSize, bestRemainderNorm - ) - || - CanSimplifyUnitProduct( - unitsExponents, entryUnitExponents, entryUnitNorm, Power::Builder(entryUnit, Rational::Builder(-1)), - Integer::Addition, - bestUnit, bestUnitNorm, bestRemainderExponents, bestRemainderSupportSize, bestRemainderNorm - ); - } - if (bestUnit.isUninitialized()) { - break; - } - const int position = unitsAccu.numberOfChildren(); - unitsAccu.addChildAtIndexInPlace(bestUnit, position, position); - ExponentsCopy(unitsExponents, bestRemainderExponents); - unitsSupportSize = bestRemainderSupportSize; - unitsNorm = bestRemainderNorm; - } - if (unitsAccu.numberOfChildren() > 0) { - units = Division::Builder(units, unitsAccu.clone()).deepReduce(reductionContext); - units = Multiplication::Builder(unitsAccu, units).shallowReduce(reductionContext); - } - } - - // Step 3: Create a Division if relevant Expression result; - if (!numer.isUninitialized()) { - result = numer; - } - if (!denom.isUninitialized()) { - result = Division::Builder(result.isUninitialized() ? Rational::Builder(1) : result, denom); - } + Expression self = *this; - // Step 4: Turn into 'Float x units' and choose a unit multiple adequate for - // the numerical value - if (!units.isUninitialized()) { - /* An exhaustive exploration of all possible multiples would have + // Step 2: Handle the units + if (hasUnit()) { + Expression units; + self = deepReduce(reductionContext); // removeUnit has to be called on reduced expression + self = removeUnit(&units); + + if (units.isUninitialized()) { + // TODO: handle error "Invalid unit" + result = Undefined::Builder(); + goto replace_by_result; + } + + ExpressionNode::UnitConversion unitConversionMode = reductionContext.unitConversion(); + if (unitConversionMode == ExpressionNode::UnitConversion::Default) { + /* Step 2a: Recognize derived units + * - Look up in the table of derived units, the one which itself or its inverse simplifies 'units' the most. + * - If an entry is found, simplify 'units' and add the corresponding unit or its inverse in 'unitsAccu'. + * - Repeat those steps until no more simplification is possible. + */ + Multiplication unitsAccu = Multiplication::Builder(); + Unit::Dimension::Vector unitsExponents = Unit::Dimension::Vector::FromBaseUnits(units); + Unit::Dimension::Vector::Metrics unitsMetrics = unitsExponents.metrics(); + Unit::Dimension::Vector bestRemainderExponents; + Unit::Dimension::Vector::Metrics bestRemainderMetrics; + while (unitsMetrics.supportSize > 1) { + const Unit::Dimension * bestDim = nullptr; + int8_t bestUnitExponent = 0; + for (const Unit::Dimension * dim = Unit::DimensionTable + Unit::NumberOfBaseUnits; dim < Unit::DimensionTableUpperBound; dim++) { + const Unit::Dimension::Vector * entryUnitExponents = dim->vector(); + int8_t entryUnitNorm = entryUnitExponents->metrics().norm; + if (CanSimplifyUnitProduct( + unitsExponents, unitsMetrics, + entryUnitExponents, entryUnitNorm, 1, + bestUnitExponent, bestRemainderExponents, bestRemainderMetrics + ) + || + CanSimplifyUnitProduct( + unitsExponents, unitsMetrics, + entryUnitExponents, entryUnitNorm, -1, + bestUnitExponent, bestRemainderExponents, bestRemainderMetrics + )) + { + bestDim = dim; + } + } + if (bestDim == nullptr) { + break; + } + Expression derivedUnit = Unit::Builder(bestDim, bestDim->stdRepresentative(), bestDim->stdRepresentativePrefix()); + assert(bestUnitExponent == 1 || bestUnitExponent == -1); + if (bestUnitExponent == -1) { + derivedUnit = Power::Builder(derivedUnit, Rational::Builder(-1)); + } + const int position = unitsAccu.numberOfChildren(); + unitsAccu.addChildAtIndexInPlace(derivedUnit, position, position); + unitsExponents = bestRemainderExponents; + unitsMetrics = bestRemainderMetrics; + } + if (unitsAccu.numberOfChildren() > 0) { + units = Division::Builder(units, unitsAccu.clone()).deepReduce(reductionContext); + Expression newUnits; + units = units.removeUnit(&newUnits); + Multiplication m = Multiplication::Builder(units); + self.replaceWithInPlace(m); + m.addChildAtIndexInPlace(self, 0, 1); + self = m; + if (newUnits.isUninitialized()) { + units = unitsAccu; + } else { + units = Multiplication::Builder(unitsAccu, newUnits); + static_cast(units).mergeSameTypeChildrenInPlace(); + } + } + } + + /* Step 2b: Turn into 'Float x units'. + * Choose a unit multiple adequate for the numerical value. + * An exhaustive exploration of all possible multiples would have * exponential complexity with respect to the number of factors. Instead, * we focus on one single factor. The first Unit factor is certainly the * most relevant. */ - if (units.type() == ExpressionNode::Type::Multiplication) { - /* First, as step 2 might have introduced non Unit factors in 'units', - * 'units' must be split again. - */ - Expression unitsNumer, unitsDenom, unitsUnits; - static_cast(units).splitIntoNormalForm(unitsNumer, unitsDenom, unitsUnits, reductionContext); - if (!unitsNumer.isUninitialized()) { - result = result.isUninitialized() ? unitsNumer : Multiplication::Builder(result, unitsNumer); - } - if (!unitsDenom.isUninitialized()) { - result = Division::Builder(result.isUninitialized() ? Rational::Builder(1) : result, unitsDenom); - } - units = unitsUnits; - } - double value = 1.0; - if (!result.isUninitialized()) { - value = result.approximateToScalar(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); - } + double value = self.approximateToScalar(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); if (std::isnan(value)) { // If the value is undefined, return "undef" without any unit result = Undefined::Builder(); } else { - Expression resultWithoutUnit; - if (std::isinf(value)) { - resultWithoutUnit = Infinity::Builder(value < 0.0); - } else { - // Find the right unit prefix when the value ≠ 0 - if (value != 0.0 && value != 1.0) { - // Identify the first Unit factor and its exponent - Expression firstFactor = units; - int exponent = 1; - if (firstFactor.type() == ExpressionNode::Type::Multiplication) { - firstFactor = firstFactor.childAtIndex(0); - } - if (firstFactor.type() == ExpressionNode::Type::Power) { - Expression exp = firstFactor.childAtIndex(1); - firstFactor = firstFactor.childAtIndex(0); - assert(exp.type() == ExpressionNode::Type::Rational && static_cast(exp).isInteger()); - Integer expInt = static_cast(exp).signedIntegerNumerator(); - if (expInt.isLowerThan(Integer(Integer::k_maxExtractableInteger))) { - exponent = expInt.extractedInt(); - } else { - // The exponent is too large to be extracted, so do not try to use it. - exponent = 0; - } - } - assert(firstFactor.type() == ExpressionNode::Type::Unit); - // Choose its multiple and update value accordingly - if (exponent != 0) { - static_cast(firstFactor).chooseBestMultipleForValue(value, exponent, reductionContext); - } - } - resultWithoutUnit = Float::Builder(value); + if (unitConversionMode == ExpressionNode::UnitConversion::Default) { + // Find the right unit prefix + Unit::ChooseBestRepresentativeAndPrefixForValue(&units, &value, reductionContext); } // Build final Expression - result = Multiplication::Builder(resultWithoutUnit, units); - static_cast(result).mergeMultiplicationChildrenInPlace(); + result = Multiplication::Builder(Number::FloatNumber(value), units); + static_cast(result).mergeSameTypeChildrenInPlace(); + } + } else { + // Step 3: Create a Division if relevant + Expression numer, denom; + splitIntoNormalForm(numer, denom, reductionContext); + if (!numer.isUninitialized()) { + result = numer; + } + if (!denom.isUninitialized()) { + result = Division::Builder(numer.isUninitialized() ? Rational::Builder(1) : numer, denom); } } - replaceWithInPlace(result); +replace_by_result: + self.replaceWithInPlace(result); return result; } Expression Multiplication::denominator(ExpressionNode::ReductionContext reductionContext) const { - Expression numer, denom, units; - splitIntoNormalForm(numer, denom, units, reductionContext); + /* TODO ? + * Turn the denominator const method into an extractDenominator method + * (non const) in the same same way as extractUnits. + * Then remove splitIntoNormalForm. + */ + Expression numer, denom; + splitIntoNormalForm(numer, denom, reductionContext); return denom; } @@ -584,8 +511,11 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext } /* Step 1: MultiplicationNode is associative, so let's start by merging children - * which also are multiplications themselves. */ - mergeMultiplicationChildrenInPlace(); + * which also are multiplications themselves. + * TODO If the parent Expression is a Multiplication, one should perhaps + * return now and let the parent do the reduction. + */ + mergeSameTypeChildrenInPlace(); Context * context = reductionContext.context(); @@ -688,12 +618,9 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext while (i < numberOfChildren()-1) { Expression oi = childAtIndex(i); Expression oi1 = childAtIndex(i+1); - if (oi.recursivelyMatches(Expression::IsRandom, context, true)) { + if (oi.recursivelyMatches(Expression::IsRandom, context)) { // Do not factorize random or randint - i++; - continue; - } - if (TermsHaveIdenticalBase(oi, oi1)) { + } else if (TermsHaveIdenticalBase(oi, oi1)) { bool shouldFactorizeBase = true; if (TermHasNumeralBase(oi)) { /* Combining powers of a given rational isn't straightforward. Indeed, @@ -746,8 +673,7 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext * i*i -> -1 * 2^(1/2)*2^(1/2) -> 2 * sin(x)*cos(x) -> 1*tan(x) - * Last, we remove the only rational child if it is one and not the only - * child. */ + */ i = 1; while (i < numberOfChildren()) { Expression o = childAtIndex(i); @@ -757,8 +683,8 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext } if (o.isNumber()) { if (childAtIndex(0).isNumber()) { - Number o0 = childAtIndex(0).convert(); - Number m = Number::Multiplication(o0, static_cast(o)); + Expression o0 = childAtIndex(0); + Number m = Number::Multiplication(static_cast(o0), static_cast(o)); if ((IsInfinity(m, context) || m.isUndefined()) && !IsInfinity(o0, context) && !o0.isUndefined() && !IsInfinity(o, context) && !o.isUndefined()) @@ -786,15 +712,17 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext * If the first child is 1, we remove it if there are other children. */ { const Expression c = childAtIndex(0); - if (c.type() == ExpressionNode::Type::Rational && static_cast(c).isZero()) { - // Check that other children don't match inf or unit - bool infiniteOrUnitFactor = recursivelyMatches([](const Expression e, Context * context) { return Expression::IsInfinity(e,context) || e.type() == ExpressionNode::Type::Unit; }, context); - if (!infiniteOrUnitFactor) { + if (hasUnit()) { + // Do not expand Multiplication in presence of units + shouldExpand = false; + } else if (c.type() != ExpressionNode::Type::Rational) { + } else if (static_cast(c).isZero()) { + // Check that other children don't match inf or matrix + if (!recursivelyMatches([](const Expression e, Context * context) { return IsInfinity(e, context) || IsMatrix(e, context); }, context)) { replaceWithInPlace(c); return c; } - } - if (c.type() == ExpressionNode::Type::Rational && static_cast(c).isOne() && numberOfChildren() > 1) { + } else if (static_cast(c).isOne() && numberOfChildren() > 1) { removeChildAtIndexInPlace(0); } } @@ -807,7 +735,7 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext * reduce expressions such as (x+y)^(-1)*(x+y)(a+b). * If there is a random somewhere, do not expand. */ Expression p = parent(); - bool hasRandom = recursivelyMatches(Expression::IsRandom, context, true); + bool hasRandom = recursivelyMatches(Expression::IsRandom, context); if (shouldExpand && (p.isUninitialized() || p.type() != ExpressionNode::Type::Multiplication) && !hasRandom) @@ -841,17 +769,18 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext assert(childAtIndex(i).type() == ExpressionNode::Type::ComplexCartesian); // First, we merge all ComplexCartesian children into one ComplexCartesian child = childAtIndex(i).convert(); - removeChildAtIndexInPlace(i); - i--; - while (i >= 0) { + while (true) { + removeChildAtIndexInPlace(i); + i--; + if (i < 0) { + break; + } Expression e = childAtIndex(i); if (e.type() != ExpressionNode::Type::ComplexCartesian) { // the Multiplication is sorted so ComplexCartesian nodes are the last ones break; } child = child.multiply(static_cast(e), reductionContext); - removeChildAtIndexInPlace(i); - i--; } // The real children are both factors of the real and the imaginary multiplication Multiplication real = *this; @@ -870,19 +799,6 @@ Expression Multiplication::privateShallowReduce(ExpressionNode::ReductionContext return result; } -void Multiplication::mergeMultiplicationChildrenInPlace() { - // Multiplication is associative: a*(b*c)->a*b*c - int i = 0; - while (i < numberOfChildren()) { - Expression c = childAtIndex(i); - if (c.type() == ExpressionNode::Type::Multiplication) { - mergeChildrenAtIndexInPlace(c, i); // TODO: ensure that matrix children are not swapped to implement MATRIX_EXACT_REDUCING - continue; - } - i++; - } -} - void Multiplication::factorizeBase(int i, int j, ExpressionNode::ReductionContext reductionContext) { /* This function factorizes two children which have a common base. For example * if this is Multiplication::Builder(pi^2, pi^3), then pi^2 and pi^3 could be merged @@ -911,7 +827,7 @@ void Multiplication::mergeInChildByFactorizingBase(int i, Expression e, Expressi * ie: 12^(1/2) -> 2*3^(1/2). In that case, we need to merge the multiplication * node with this. */ if (p.type() == ExpressionNode::Type::Multiplication) { - mergeMultiplicationChildrenInPlace(); + mergeChildrenAtIndexInPlace(p, i); } } @@ -932,7 +848,7 @@ void Multiplication::factorizeExponent(int i, int j, ExpressionNode::ReductionCo * ie: 12^(1/2) -> 2*3^(1/2). In that case, we need to merge the multiplication * node with this. */ if (p.type() == ExpressionNode::Type::Multiplication) { - mergeMultiplicationChildrenInPlace(); + mergeChildrenAtIndexInPlace(p, i); } } @@ -1092,20 +1008,17 @@ bool Multiplication::TermHasNumeralExponent(const Expression & e) { return false; } -void Multiplication::splitIntoNormalForm(Expression & numerator, Expression & denominator, Expression & units, ExpressionNode::ReductionContext reductionContext) const { +void Multiplication::splitIntoNormalForm(Expression & numerator, Expression & denominator, ExpressionNode::ReductionContext reductionContext) const { Multiplication mNumerator = Multiplication::Builder(); Multiplication mDenominator = Multiplication::Builder(); - Multiplication mUnits = Multiplication::Builder(); int numberOfFactorsInNumerator = 0; int numberOfFactorsInDenominator = 0; - int numberOfFactorsInUnits = 0; const int numberOfFactors = numberOfChildren(); for (int i = 0; i < numberOfFactors; i++) { Expression factor = childAtIndex(i).clone(); ExpressionNode::Type factorType = factor.type(); Expression factorsNumerator; Expression factorsDenominator; - Expression factorsUnit; if (factorType == ExpressionNode::Type::Rational) { Rational r = static_cast(factor); if (r.isRationalOne()) { @@ -1124,29 +1037,10 @@ void Multiplication::splitIntoNormalForm(Expression & numerator, Expression & de } else if (factorType == ExpressionNode::Type::Power) { Expression fd = factor.denominator(reductionContext); if (fd.isUninitialized()) { - if (factor.childAtIndex(0).type() == ExpressionNode::Type::Unit) { - /* A Unit should only have integer exponents, otherwise - * simplification returns Undefined. That will be handled later in - * Unit::shallowBeautify: since an Expression is beautified from - * children to parent, the children of the current Multiplication are - * not beautified yet and the exponent of a Unit is not guaranted at - * this point to be an integer. Until then, any Unit with non-integer - * exponent, is flushed into the numerator instead of units. - */ - Expression exponent = factor.childAtIndex(1); - if (exponent.type() == ExpressionNode::Type::Rational && static_cast(exponent).isInteger()) { - factorsUnit = factor; - } else { - factorsNumerator = factor; - } - } else { - factorsNumerator = factor; - } + factorsNumerator = factor; } else { factorsDenominator = fd; } - } else if (factorType == ExpressionNode::Type::Unit) { - factorsUnit = factor; } else { factorsNumerator = factor; } @@ -1158,10 +1052,6 @@ void Multiplication::splitIntoNormalForm(Expression & numerator, Expression & de mDenominator.addChildAtIndexInPlace(factorsDenominator, numberOfFactorsInDenominator, numberOfFactorsInDenominator); numberOfFactorsInDenominator++; } - if (!factorsUnit.isUninitialized()) { - mUnits.addChildAtIndexInPlace(factorsUnit, numberOfFactorsInUnits, numberOfFactorsInUnits); - numberOfFactorsInUnits++; - } } if (numberOfFactorsInNumerator) { numerator = mNumerator.squashUnaryHierarchyInPlace(); @@ -1169,9 +1059,6 @@ void Multiplication::splitIntoNormalForm(Expression & numerator, Expression & de if (numberOfFactorsInDenominator) { denominator = mDenominator.squashUnaryHierarchyInPlace(); } - if (numberOfFactorsInUnits) { - units = mUnits.squashUnaryHierarchyInPlace(); - } } const Expression Multiplication::Base(const Expression e) { diff --git a/poincare/src/n_ary_expression.cpp b/poincare/src/n_ary_expression.cpp index 917877808..314f4b8cb 100644 --- a/poincare/src/n_ary_expression.cpp +++ b/poincare/src/n_ary_expression.cpp @@ -97,6 +97,21 @@ int NAryExpressionNode::simplificationOrderGreaterType(const ExpressionNode * e, return 0; } +void NAryExpression::mergeSameTypeChildrenInPlace() { + // Multiplication is associative: a*(b*c)->a*b*c + // The same goes for Addition + ExpressionNode::Type parentType = type(); + int i = 0; + while (i < numberOfChildren()) { + Expression c = childAtIndex(i); + if (c.type() != parentType) { + i++; + } else { + mergeChildrenAtIndexInPlace(c, i); + } + } +} + int NAryExpression::allChildrenAreReal(Context * context) const { int i = 0; int result = 1; diff --git a/poincare/src/parsing/parser.cpp b/poincare/src/parsing/parser.cpp index 2b8873bd1..f38c57e3f 100644 --- a/poincare/src/parsing/parser.cpp +++ b/poincare/src/parsing/parser.cpp @@ -276,32 +276,29 @@ void Parser::parseRightwardsArrow(Expression & leftHandSide, Token::Type stoppin return; } // At this point, m_currentToken is Token::RightwardsArrow. - bool parseId = m_nextToken.is(Token::Identifier) && !IsReservedName(m_nextToken.text(), m_nextToken.length()); - if (parseId) { - popToken(); - // Try parsing a store - Expression rightHandSide; - parseCustomIdentifier(rightHandSide, m_currentToken.text(), m_currentToken.length(), true); - if (m_status != Status::Progress) { - return; - } - if (!m_nextToken.is(Token::EndOfStream) - || !(rightHandSide.type() == ExpressionNode::Type::Symbol - || (rightHandSide.type() == ExpressionNode::Type::Function - && rightHandSide.childAtIndex(0).type() == ExpressionNode::Type::Symbol))) - { - m_status = Status::Error; // Store expects a single symbol or function. - return; - } + const char * tokenName = m_nextToken.text(); + size_t tokenNameLength = m_nextToken.length(); + /* Right part of the RightwardsArrow are either a Symbol, a Function or units. + * Even undefined function "plouf(x)" should be interpreted as function and + * not as a multiplication. */ + m_symbolPlusParenthesesAreFunctions = true; + Expression rightHandSide = parseUntil(stoppingType); + m_symbolPlusParenthesesAreFunctions = false; + if (m_status != Status::Progress) { + return; + } + + // Pattern : "-> a" or "-> f(x)" Try parsing a store + if (m_nextToken.is(Token::EndOfStream) && + (rightHandSide.type() == ExpressionNode::Type::Symbol + || (rightHandSide.type() == ExpressionNode::Type::Function + && rightHandSide.childAtIndex(0).type() == ExpressionNode::Type::Symbol)) && + !IsReservedName(tokenName, tokenNameLength)) { leftHandSide = Store::Builder(leftHandSide, static_cast(rightHandSide)); return; } // Try parsing a unit convert - Expression rightHandSide = parseUntil(stoppingType); - if (m_status != Status::Progress) { - return; - } - if (!m_nextToken.is(Token::EndOfStream) || rightHandSide.isUninitialized() || rightHandSide.type() == ExpressionNode::Type::Store || rightHandSide.type() == ExpressionNode::Type::UnitConvert) { + if (!m_nextToken.is(Token::EndOfStream) || rightHandSide.isUninitialized() || rightHandSide.type() == ExpressionNode::Type::Store || rightHandSide.type() == ExpressionNode::Type::UnitConvert || rightHandSide.type() == ExpressionNode::Type::Equal) { m_status = Status::Error; // UnitConvert expect a unit on the right. return; } @@ -451,19 +448,19 @@ void Parser::parseSpecialIdentifier(Expression & leftHandSide) { } } -void Parser::parseCustomIdentifier(Expression & leftHandSide, const char * name, size_t length, bool symbolPlusParenthesesAreFunctions) { +void Parser::parseCustomIdentifier(Expression & leftHandSide, const char * name, size_t length) { if (length >= SymbolAbstract::k_maxNameSize) { m_status = Status::Error; // Identifier name too long. return; } bool poppedParenthesisIsSystem = false; - /* If symbolPlusParenthesesAreFunctions is false, check the context: if the + /* If m_symbolPlusParenthesesAreFunctions is false, check the context: if the * identifier does not already exist as a function, interpret it as a symbol, * even if there are parentheses afterwards. */ Context::SymbolAbstractType idType = Context::SymbolAbstractType::None; - if (m_context != nullptr && !symbolPlusParenthesesAreFunctions) { + if (m_context != nullptr && !m_symbolPlusParenthesesAreFunctions) { idType = m_context->expressionTypeForIdentifier(name, length); if (idType != Context::SymbolAbstractType::Function) { leftHandSide = Symbol::Builder(name, length); @@ -511,7 +508,7 @@ void Parser::parseIdentifier(Expression & leftHandSide, Token::Type stoppingType } else if (IsSpecialIdentifierName(m_currentToken.text(), m_currentToken.length())) { parseSpecialIdentifier(leftHandSide); } else { - parseCustomIdentifier(leftHandSide, m_currentToken.text(), m_currentToken.length(), false); + parseCustomIdentifier(leftHandSide, m_currentToken.text(), m_currentToken.length()); } isThereImplicitMultiplication(); } diff --git a/poincare/src/parsing/parser.h b/poincare/src/parsing/parser.h index 08e04a211..37ff4f523 100644 --- a/poincare/src/parsing/parser.h +++ b/poincare/src/parsing/parser.h @@ -26,7 +26,8 @@ public: m_tokenizer(text), m_currentToken(Token(Token::Undefined)), m_nextToken(m_tokenizer.popToken()), - m_pendingImplicitMultiplication(false) {} + m_pendingImplicitMultiplication(false), + m_symbolPlusParenthesesAreFunctions(false) {} Expression parse(); Status getStatus() const { return m_status; } @@ -75,7 +76,7 @@ private: void parseReservedFunction(Expression & leftHandSide, const Expression::FunctionHelper * const * functionHelper); void parseSpecialIdentifier(Expression & leftHandSide); void parseSequence(Expression & leftHandSide, const char name, Token::Type leftDelimiter1, Token::Type rightDelimiter1, Token::Type leftDelimiter2, Token::Type rightDelimiter2); - void parseCustomIdentifier(Expression & leftHandSide, const char * name, size_t length, bool symbolPlusParenthesesAreFunctions); + void parseCustomIdentifier(Expression & leftHandSide, const char * name, size_t length); void defaultParseLeftParenthesis(bool isSystemParenthesis, Expression & leftHandSide, Token::Type stoppingType); // Data members @@ -88,6 +89,7 @@ private: Token m_currentToken; Token m_nextToken; bool m_pendingImplicitMultiplication; + bool m_symbolPlusParenthesesAreFunctions; // The array of reserved functions' helpers static constexpr const Expression::FunctionHelper * s_reservedFunctions[] = { diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index da0579f72..d5913e835 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -73,7 +73,7 @@ int PowerNode::polynomialDegree(Context * context, const char * symbolName) cons return -1; } Integer numeratorInt = r->signedNumerator(); - if (Integer::NaturalOrder(numeratorInt, Integer(Integer::k_maxExtractableInteger)) > 0) { + if (!numeratorInt.isExtractable()) { return -1; } op0Deg *= numeratorInt.extractedInt(); @@ -82,8 +82,8 @@ int PowerNode::polynomialDegree(Context * context, const char * symbolName) cons return -1; } -Expression PowerNode::getUnit() const { - return Power(this).getUnit(); +Expression PowerNode::removeUnit(Expression * unit) { + return Power(this).removeUnit(unit); } int PowerNode::getPolynomialCoefficients(Context * context, const char * symbolName, Expression coefficients[], ExpressionNode::SymbolicComputation symbolicComputation) const { @@ -360,7 +360,7 @@ int Power::getPolynomialCoefficients(Context * context, const char * symbolName, return -1; } Integer num = r.unsignedIntegerNumerator(); - if (Integer::NaturalOrder(num, Integer(Integer::k_maxExtractableInteger)) > 0) { + if (!num.isExtractable()) { return -1; } int n = num.extractedInt(); @@ -375,6 +375,22 @@ int Power::getPolynomialCoefficients(Context * context, const char * symbolName, return -1; } +Expression Power::removeUnit(Expression * unit) { + Expression childUnit; + Expression child = childAtIndex(0).node()->removeUnit(&childUnit); + if (!childUnit.isUninitialized()) { + // Reduced power containing unit are of form "unit^i" with i integer + assert(child.isRationalOne()); + assert(childUnit.type() == ExpressionNode::Type::Unit); + Power p = *this; + replaceWithInPlace(child); + p.replaceChildAtIndexInPlace(0, childUnit); + *unit = p; + return child; + } + return *this; +} + Expression Power::shallowReduce(ExpressionNode::ReductionContext reductionContext) { { @@ -387,9 +403,21 @@ Expression Power::shallowReduce(ExpressionNode::ReductionContext reductionContex Expression base = childAtIndex(0); Expression index = childAtIndex(1); - // Step 1: There should be no unit in the index! - if (index.hasUnit()) { - return replaceWithUndefinedInPlace(); + // Step 1: Handle the units + { + Expression indexUnit; + index.removeUnit(&indexUnit); + if (!indexUnit.isUninitialized()) { + // There must be no unit in the exponent + return replaceWithUndefinedInPlace(); + } + assert(index == childAtIndex(1)); + if (base.hasUnit()) { + if (index.type() != ExpressionNode::Type::Rational || !static_cast(index).isInteger()) { + // The exponent must be an Integer + return replaceWithUndefinedInPlace(); + } + } } // Step 2: Handle matrices @@ -402,45 +430,50 @@ Expression Power::shallowReduce(ExpressionNode::ReductionContext reductionContex if (indexType != ExpressionNode::Type::Rational || !static_cast(index).isInteger()) { return replaceWithUndefinedInPlace(); } - if (baseType != ExpressionNode::Type::Matrix) { - return *this; + if (baseType == ExpressionNode::Type::Matrix) { + Matrix matrixBase = static_cast(base); + if (matrixBase.numberOfRows() != matrixBase.numberOfColumns()) { + return replaceWithUndefinedInPlace(); + } + Integer exponent = static_cast(index).signedIntegerNumerator(); + if (exponent.isNegative()) { + index.setSign(ExpressionNode::Sign::Positive, reductionContext); + Expression reducedPositiveExponentMatrix = shallowReduce(reductionContext); + if (reducedPositiveExponentMatrix.type() == ExpressionNode::Type::Power) { + /* The shallowReduce did not work, stop here so we do not get in an + * infinite loop. */ + static_cast(reducedPositiveExponentMatrix).childAtIndex(1).setSign(ExpressionNode::Sign::Negative, reductionContext); + return reducedPositiveExponentMatrix; + } + Expression dummyExpression = Undefined::Builder(); + MatrixInverse inv = MatrixInverse::Builder(dummyExpression); + reducedPositiveExponentMatrix.replaceWithInPlace(inv); + inv.replaceChildInPlace(dummyExpression, reducedPositiveExponentMatrix); + return inv.shallowReduce(reductionContext); + } + if (Integer::NaturalOrder(exponent, Integer(k_maxExactPowerMatrix)) > 0) { + return *this; + } + int exp = exponent.extractedInt(); // Ok, because 0 < exponent < k_maxExactPowerMatrix + if (exp == 0) { + Matrix id = Matrix::CreateIdentity(matrixBase.numberOfRows()); + replaceWithInPlace(id); + return std::move(id); + } + if (exp == 1) { + replaceWithInPlace(matrixBase); + return std::move(matrixBase); + } + Expression result = matrixBase.clone(); + // TODO: implement a quick exponentiation + for (int k = 1; k < exp; k++) { + result = Multiplication::Builder(result, matrixBase.clone()); + result = result.shallowReduce(reductionContext); + } + assert(!result.isUninitialized()); + replaceWithInPlace(result); + return result; } - Matrix matrixBase = static_cast(base); - if (matrixBase.numberOfRows() != matrixBase.numberOfColumns()) { - return replaceWithUndefinedInPlace(); - } - Integer exponent = static_cast(index).signedIntegerNumerator(); - if (exponent.isNegative()) { - index.setSign(ExpressionNode::Sign::Positive, reductionContext); - Expression reducedPositiveExponentMatrix = shallowReduce(reductionContext); - Expression dummyExpression = Undefined::Builder(); - MatrixInverse inv = MatrixInverse::Builder(dummyExpression); - reducedPositiveExponentMatrix.replaceWithInPlace(inv); - inv.replaceChildInPlace(dummyExpression, reducedPositiveExponentMatrix); - return inv.shallowReduce(reductionContext); - } - if (Integer::NaturalOrder(exponent, Integer(k_maxExactPowerMatrix)) > 0) { - return *this; - } - int exp = exponent.extractedInt(); // Ok, because 0 < exponent < k_maxExactPowerMatrix - if (exp == 0) { - Matrix id = Matrix::CreateIdentity(matrixBase.numberOfRows()); - replaceWithInPlace(id); - return std::move(id); - } - if (exp == 1) { - replaceWithInPlace(matrixBase); - return std::move(matrixBase); - } - Expression result = matrixBase.clone(); - // TODO: implement a quick exponentiation - for (int k = 1; k < exp; k++) { - result = Multiplication::Builder(result, matrixBase.clone()); - result = result.shallowReduce(reductionContext); - } - assert(!result.isUninitialized()); - replaceWithInPlace(result); - return result; } Expression power = *this; @@ -982,17 +1015,6 @@ Expression Power::shallowBeautify(ExpressionNode::ReductionContext reductionCont return *this; } -Expression Power::getUnit() const { - if (childAtIndex(0).type() == ExpressionNode::Type::Unit) { - return clone(); - } - Expression baseUnit = childAtIndex(0).getUnit(); - if (baseUnit.isUndefined()) { - return baseUnit; - } - return Power::Builder(baseUnit, childAtIndex(1).clone()); -} - // Private // Simplification diff --git a/poincare/src/symbol.cpp b/poincare/src/symbol.cpp index 7f58b44b9..b1f1f1687 100644 --- a/poincare/src/symbol.cpp +++ b/poincare/src/symbol.cpp @@ -151,12 +151,12 @@ bool Symbol::isRegressionSymbol(const char * c, Poincare::Context * context) { } Expression Symbol::shallowReduce(ExpressionNode::ReductionContext reductionContext) { - if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions) { + if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions + || reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol) + { return *this; } - if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefinedAndDoNotReplaceUnits - || reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefinedAndReplaceUnits) - { + if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined) { return replaceWithUndefinedInPlace(); } { diff --git a/poincare/src/symbol_abstract.cpp b/poincare/src/symbol_abstract.cpp index a7af63006..61c679767 100644 --- a/poincare/src/symbol_abstract.cpp +++ b/poincare/src/symbol_abstract.cpp @@ -62,17 +62,14 @@ size_t SymbolAbstract::TruncateExtension(char * dst, const char * src, size_t le bool SymbolAbstract::matches(const SymbolAbstract & symbol, ExpressionTest test, Context * context) { Expression e = SymbolAbstract::Expand(symbol, context, false); - return !e.isUninitialized() && e.recursivelyMatches(test, context, false); + return !e.isUninitialized() && e.recursivelyMatches(test, context, ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol); } Expression SymbolAbstract::Expand(const SymbolAbstract & symbol, Context * context, bool clone, ExpressionNode::SymbolicComputation symbolicComputation) { - if (symbolicComputation == ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefinedAndDoNotReplaceUnits - || symbolicComputation == ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefinedAndReplaceUnits) - { - return Undefined::Builder(); - } bool shouldNotReplaceSymbols = symbolicComputation == ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions; - if (symbol.type() == ExpressionNode::Type::Symbol && shouldNotReplaceSymbols) { + if (symbolicComputation == ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol + || (symbol.type() == ExpressionNode::Type::Symbol && shouldNotReplaceSymbols)) + { return clone ? symbol.clone() : *const_cast(&symbol); } Expression e = context->expressionForSymbolAbstract(symbol, clone); diff --git a/poincare/src/tree_handle.cpp b/poincare/src/tree_handle.cpp index 97e7fbfc1..477565d25 100644 --- a/poincare/src/tree_handle.cpp +++ b/poincare/src/tree_handle.cpp @@ -2,9 +2,6 @@ #include #include #include -#if POINCARE_TREE_LOG -#include -#endif namespace Poincare { diff --git a/poincare/src/tree_node.cpp b/poincare/src/tree_node.cpp index 29af7657c..5076cc277 100644 --- a/poincare/src/tree_node.cpp +++ b/poincare/src/tree_node.cpp @@ -1,7 +1,6 @@ #include #include #include -#include namespace Poincare { @@ -32,7 +31,7 @@ void TreeNode::rename(uint16_t identifier, bool unregisterPreviousIdentifier) { TreeNode * TreeNode::parent() const { assert(m_parentIdentifier != m_identifier); - return TreeHandle::hasNode(m_parentIdentifier) ? TreePool::sharedPool()->node(m_parentIdentifier) : nullptr; + return TreeHandle::hasNode(m_parentIdentifier) ? TreePool::sharedPool()->node(m_parentIdentifier) : nullptr; } TreeNode * TreeNode::root() { diff --git a/poincare/src/unit.cpp b/poincare/src/unit.cpp index 074f59893..d879b3da4 100644 --- a/poincare/src/unit.cpp +++ b/poincare/src/unit.cpp @@ -1,7 +1,9 @@ #include +#include #include #include #include +#include #include #include #include @@ -28,8 +30,9 @@ bool UnitNode::Representative::canParse(const char * symbol, size_t length, *prefix = &Unit::EmptyPrefix; return length == 0; } - const Prefix * pre = Unit::AllPrefixes; - while (pre < Unit::AllPrefixes + sizeof(Unit::AllPrefixes)/sizeof(Unit::Prefix)) { + size_t numberOfPrefixes = sizeof(Unit::AllPrefixes)/sizeof(Unit::Prefix *); + for (size_t i = 0; i < numberOfPrefixes; i++) { + const Prefix * pre = Unit::AllPrefixes[i]; const char * prefixSymbol = pre->symbol(); if (strncmp(symbol, prefixSymbol, length) == 0 && prefixSymbol[length] == 0) @@ -44,37 +47,104 @@ bool UnitNode::Representative::canParse(const char * symbol, size_t length, int UnitNode::Representative::serialize(char * buffer, int bufferSize, const Prefix * prefix) const { int length = 0; - if (isPrefixable()) { - length += prefix->serialize(buffer, bufferSize); - assert(length < bufferSize); - buffer += length; - bufferSize -= length; - } + length += prefix->serialize(buffer, bufferSize); + assert(length == 0 || isPrefixable()); + assert(length < bufferSize); + buffer += length; + bufferSize -= length; assert(bufferSize >= 0); length += std::min(strlcpy(buffer, m_rootSymbol, bufferSize), bufferSize - 1); return length; } const UnitNode::Prefix * UnitNode::Representative::bestPrefixForValue(double & value, const int exponent) const { - const Prefix * bestPre = &Unit::EmptyPrefix; - if (isPrefixable()) { - unsigned int diff = -1; - /* Find the 'Prefix' with the most adequate 'exponent' for the order of - * magnitude of 'value'. - */ - const int orderOfMagnitude = IEEE754::exponentBase10(std::fabs(value)); - for (const Prefix * pre = m_outputPrefixes; pre < m_outputPrefixesUpperBound; pre++) { - unsigned int newDiff = absInt(orderOfMagnitude - pre->exponent() * exponent); - if (newDiff < diff) { - diff = newDiff; - bestPre = pre; - } - } - value *= std::pow(10.0, -bestPre->exponent() * exponent); + if (!isPrefixable()) { + return &Unit::EmptyPrefix; } + const Prefix * bestPre = nullptr; + unsigned int diff = -1; + /* Find the 'Prefix' with the most adequate 'exponent' for the order of + * magnitude of 'value'. + */ + const int orderOfMagnitude = IEEE754::exponentBase10(std::fabs(value)); + for (size_t i = 0; i < m_outputPrefixesLength; i++) { + const Prefix * pre = m_outputPrefixes[i]; + unsigned int newDiff = absInt(orderOfMagnitude - pre->exponent() * exponent); + if (newDiff < diff) { + diff = newDiff; + bestPre = pre; + } + } + value *= std::pow(10.0, -bestPre->exponent() * exponent); return bestPre; } +template<> +Unit::Dimension::Vector::Metrics UnitNode::Dimension::Vector::metrics() const { + size_t supportSize = 0; + Integer norm(0); + for (const Integer * i = reinterpret_cast(this); i < reinterpret_cast(this) + NumberOfBaseUnits; i++) { + Integer coefficient = *i; + if (coefficient.isZero()) { + continue; + } + supportSize++; + coefficient.setNegative(false); + norm = Integer::Addition(norm, coefficient); + } + return {.supportSize = supportSize, .norm = norm}; +} + +template<> +Unit::Dimension::Vector::Metrics UnitNode::Dimension::Vector::metrics() const { + size_t supportSize = 0; + int8_t norm = 0; + for (const int8_t * i = reinterpret_cast(this); i < reinterpret_cast(this) + NumberOfBaseUnits; i++) { + int8_t coefficient = *i; + if (coefficient == 0) { + continue; + } + supportSize++; + norm += coefficient > 0 ? coefficient : -coefficient; + } + return {.supportSize = supportSize, .norm = norm}; +} + +template<> +Unit::Dimension::Vector UnitNode::Dimension::Vector::FromBaseUnits(const Expression baseUnits) { + Vector vector; + int numberOfFactors; + int factorIndex = 0; + Expression factor; + if (baseUnits.type() == ExpressionNode::Type::Multiplication) { + numberOfFactors = baseUnits.numberOfChildren(); + factor = baseUnits.childAtIndex(0); + } else { + numberOfFactors = 1; + factor = baseUnits; + } + do { + // Get the unit's exponent + Integer exponent(1); + if (factor.type() == ExpressionNode::Type::Power) { + Expression exp = factor.childAtIndex(1); + assert(exp.type() == ExpressionNode::Type::Rational && static_cast(exp).isInteger()); + exponent = static_cast(exp).signedIntegerNumerator(); + factor = factor.childAtIndex(0); + } + // Fill the vector with the unit's exponent + assert(factor.type() == ExpressionNode::Type::Unit); + const ptrdiff_t indexInTable = static_cast(factor.node())->dimension() - Unit::DimensionTable; + assert(0 <= indexInTable && indexInTable < NumberOfBaseUnits); + vector.setCoefficientAtIndex(indexInTable, exponent); + if (++factorIndex >= numberOfFactors) { + break; + } + factor = baseUnits.childAtIndex(factorIndex); + } while (true); + return vector; +} + bool UnitNode::Dimension::canParse(const char * symbol, size_t length, const Representative * * representative, const Prefix * * prefix) const { @@ -99,8 +169,8 @@ ExpressionNode::Sign UnitNode::sign(Context * context) const { return Sign::Positive; } -Expression UnitNode::getUnit() const { - return Unit(this).getUnit(); +Expression UnitNode::removeUnit(Expression * unit) { + return Unit(this).removeUnit(unit); } int UnitNode::simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted, bool ignoreParentheses) const { @@ -109,15 +179,20 @@ int UnitNode::simplificationOrderSameType(const ExpressionNode * e, bool ascendi } assert(type() == e->type()); const UnitNode * eNode = static_cast(e); + // This works because dimensions are ordered in a table const ptrdiff_t dimdiff = eNode->dimension() - m_dimension; if (dimdiff != 0) { return dimdiff; } + // This works because reprensentatives are ordered in a table const ptrdiff_t repdiff = eNode->representative() - m_representative; if (repdiff != 0) { - return repdiff; + /* We order representatives in the reverse order as how they're stored in + * the representatives table. This enables to sort addition of time as: + * year + month + days + hours + minutes + seconds. */ + return -repdiff; } - const ptrdiff_t prediff = eNode->prefix() - m_prefix; + const ptrdiff_t prediff = eNode->prefix()->exponent() - m_prefix->exponent(); return prediff; } @@ -167,13 +242,12 @@ constexpr const Unit::Prefix Unit::MegaPrefix, Unit::GigaPrefix, Unit::TeraPrefix; -constexpr const Unit::Prefix - Unit::NoPrefix[], - Unit::NegativeLongScalePrefixes[], - Unit::PositiveLongScalePrefixes[], - Unit::LongScalePrefixes[], - Unit::NegativePrefixes[], - Unit::AllPrefixes[]; +constexpr const Unit::Prefix * const Unit::NoPrefix[]; +constexpr const Unit::Prefix * const Unit::NegativeLongScalePrefixes[]; +constexpr const Unit::Prefix * const Unit::PositiveLongScalePrefixes[]; +constexpr const Unit::Prefix * const Unit::LongScalePrefixes[]; +constexpr const Unit::Prefix * const Unit::NegativePrefixes[]; +constexpr const Unit::Prefix * const Unit::AllPrefixes[]; constexpr const Unit::Representative Unit::TimeRepresentatives[], Unit::DistanceRepresentatives[], @@ -201,7 +275,12 @@ constexpr const Unit::Representative Unit::CatalyticActivityRepresentatives[], Unit::SurfaceRepresentatives[], Unit::VolumeRepresentatives[]; +const Unit::Representative constexpr * Unit::SecondRepresentative; +const Unit::Representative constexpr * Unit::HourRepresentative; +const Unit::Representative constexpr * Unit::MeterRepresentative; constexpr const Unit::Dimension Unit::DimensionTable[]; +const Unit::Dimension constexpr * Unit::TimeDimension; +const Unit::Dimension constexpr * Unit::DistanceDimension; constexpr const Unit::Dimension * Unit::DimensionTableUpperBound; bool Unit::CanParse(const char * symbol, size_t length, @@ -224,10 +303,10 @@ Unit Unit::Builder(const Dimension * dimension, const Representative * represent } Expression Unit::shallowReduce(ExpressionNode::ReductionContext reductionContext) { - if (reductionContext.symbolicComputation() == ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefinedAndDoNotReplaceUnits) { + if (reductionContext.unitConversion() == ExpressionNode::UnitConversion::None) { return *this; } - UnitNode * unitNode = static_cast(node()); + UnitNode * unitNode = node(); const Dimension * dim = unitNode->dimension(); const Representative * rep = unitNode->representative(); const Prefix * pre = unitNode->prefix(); @@ -252,58 +331,62 @@ Expression Unit::shallowReduce(ExpressionNode::ReductionContext reductionContext } Expression Unit::shallowBeautify(ExpressionNode::ReductionContext reductionContext) { - Expression ancestor = parent(); // Force Float(1) in front of an orphan Unit - if (ancestor.isUninitialized()) { + if (parent().isUninitialized() || parent().type() == ExpressionNode::Type::Opposite) { Multiplication m = Multiplication::Builder(Float::Builder(1.0)); replaceWithInPlace(m); m.addChildAtIndexInPlace(*this, 1, 1); return std::move(m); } - // Check that the exponent, if any, of a Unit is an integer - if (!ancestor.isUninitialized() && ancestor.type() == ExpressionNode::Type::Power) { - Expression exponent = ancestor.childAtIndex(1); - if (!(exponent.type() == ExpressionNode::Type::Rational && static_cast(exponent).isInteger())) { - goto UnitCheckUnsuccessful; - } - ancestor = ancestor.parent(); - } - /* Check homogeneity: at this point, ancestor must be - * - either uninitialized - * - or a Multiplication whose parent is uninitialized. - */ - if (!ancestor.isUninitialized() && ancestor.type() == ExpressionNode::Type::Multiplication) { - ancestor = ancestor.parent(); - } - if (!ancestor.isUninitialized() && ancestor.type() == ExpressionNode::Type::Opposite) { - ancestor = ancestor.parent(); - } - if (ancestor.isUninitialized()) { - return *this; - } - UnitCheckUnsuccessful: - /* If the latter checks are not successfully passed, then the function - * returns replaceWithUndefinedInPlace. - * TODO Something else should be returned in order to report a more - * specific error. For instance: inhomogeneous expression. - */ - return replaceWithUndefinedInPlace(); + return *this; } -void Unit::chooseBestMultipleForValue(double & value, const int exponent, ExpressionNode::ReductionContext reductionContext) { - assert(value != 0 && !std::isnan(value) && !std::isinf(value) && exponent != 0); - UnitNode * unitNode = static_cast(node()); +void Unit::ChooseBestMultipleForValue(Expression * units, double * value, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext) { + // Identify the first Unit factor and its exponent + Expression firstFactor = *units; + int exponent = 1; + if (firstFactor.type() == ExpressionNode::Type::Multiplication) { + firstFactor = firstFactor.childAtIndex(0); + } + if (firstFactor.type() == ExpressionNode::Type::Power) { + Expression exp = firstFactor.childAtIndex(1); + firstFactor = firstFactor.childAtIndex(0); + assert(exp.type() == ExpressionNode::Type::Rational && static_cast(exp).isInteger()); + Integer expInt = static_cast(exp).signedIntegerNumerator(); + if (expInt.isExtractable()) { + exponent = expInt.extractedInt(); + } else { + // The exponent is too large to be extracted, so do not try to use it. + exponent = 0; + } + } + assert(firstFactor.type() == ExpressionNode::Type::Unit); + // Choose its multiple and update value accordingly + if (exponent != 0) { + static_cast(firstFactor).chooseBestMultipleForValue(value, exponent, tuneRepresentative, reductionContext); + } +} + +void Unit::chooseBestMultipleForValue(double * value, const int exponent, bool tuneRepresentative, ExpressionNode::ReductionContext reductionContext) { + assert(!std::isnan(*value) && exponent != 0); + if (*value == 0 || *value == 1.0 || std::isinf(*value)) { + return; + } + UnitNode * unitNode = node(); const Dimension * dim = unitNode->dimension(); - /* Find in the Dimension 'dim' which unit (Representative and Prefix) make - * the value closer to 1. + /* Find in the Dimension 'dim' which unit (Prefix and optionally + * Representative) make the value closer to 1. */ const Representative * bestRep = unitNode->representative(); const Prefix * bestPre = unitNode->prefix(); - double bestVal = value; + double bestVal = *value; - for (const Representative * rep = dim->stdRepresentative(); rep < dim->representativesUpperBound(); rep++) { + // Test all representatives if tuneRepresentative is on. Otherwise, force current representative + const Representative * startRep = tuneRepresentative ? dim->stdRepresentative() : bestRep; + const Representative * endRep = tuneRepresentative ? dim->representativesUpperBound() : bestRep + 1; + for (const Representative * rep = startRep; rep < endRep; rep++) { // evaluate quotient - double val = value * std::pow(Division::Builder(clone(), Unit::Builder(dim, rep, &EmptyPrefix)).deepReduce(reductionContext).approximateToScalar(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()), exponent); + double val = *value * std::pow(Division::Builder(clone(), Unit::Builder(dim, rep, &EmptyPrefix)).deepReduce(reductionContext).approximateToScalar(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()), exponent); // Get the best prefix and update val accordingly const Prefix * pre = rep->bestPrefixForValue(val, exponent); if (std::fabs(std::log10(std::fabs(bestVal))) - std::fabs(std::log10(std::fabs(val))) > Epsilon()) { @@ -315,7 +398,129 @@ void Unit::chooseBestMultipleForValue(double & value, const int exponent, Expres } unitNode->setRepresentative(bestRep); unitNode->setPrefix(bestPre); - value = bestVal; + *value = bestVal; +} + +Expression Unit::removeUnit(Expression * unit) { + *unit = *this; + Expression one = Rational::Builder(1); + replaceWithInPlace(one); + return one; +} + +bool Unit::isSecond() const { + // TODO: comparing pointers suffices because all time dimension are built from the same pointers. This should be asserted some way at compile-time? + return node()->dimension() == TimeDimension && node()->representative() == SecondRepresentative && node()->prefix() == &EmptyPrefix; +} + +bool Unit::isMeter() const { + // See comment on isSecond + return node()->dimension() == DistanceDimension && node()->representative() == MeterRepresentative && node()->prefix() == &EmptyPrefix; +} + + +bool Unit::isKilogram() const { + // See comment on isSecond + return node()->dimension() == MassDimension && node()->representative() == KilogramRepresentative && node()->prefix() == &KiloPrefix; +} + +bool Unit::isIS() const { + UnitNode * unitNode = node(); + const Dimension * dim = unitNode->dimension(); + const Representative * rep = unitNode->representative(); + return rep == dim->stdRepresentative() && + rep->definition() == nullptr && + unitNode->prefix() == dim->stdRepresentativePrefix(); +} + +bool Unit::IsIS(Expression & e) { + if (e.type() == ExpressionNode::Type::Multiplication) { + for (int i = 0; i < e.numberOfChildren(); i++) { + Expression child = e.childAtIndex(i); + assert(child.type() == ExpressionNode::Type::Power || child.type() == ExpressionNode::Type::Unit); + if (!IsIS(child)) { + return false; + } + } + return true; + } + if (e.type() == ExpressionNode::Type::Power) { + assert(e.childAtIndex(1).type() == ExpressionNode::Type::Rational && e.childAtIndex(1).convert().isInteger()); + Expression child = e.childAtIndex(0); + assert(child.type() == ExpressionNode::Type::Unit); + return IsIS(child); + } + assert(e.type() == ExpressionNode::Type::Unit); + return static_cast(e).isIS(); +} + +bool Unit::IsISSpeed(Expression & e) { + // Form m*s^-1 + return e.type() == ExpressionNode::Type::Multiplication && e.numberOfChildren() == 2 && + e.childAtIndex(0).type() == ExpressionNode::Type::Unit && e.childAtIndex(0).convert().isMeter() && + e.childAtIndex(1).type() == ExpressionNode::Type::Power && + e.childAtIndex(1).childAtIndex(1).type() == ExpressionNode::Type::Rational && e.childAtIndex(1).childAtIndex(1).convert().isMinusOne() && + e.childAtIndex(1).childAtIndex(0).type() == ExpressionNode::Type::Unit && e.childAtIndex(1).childAtIndex(0).convert().isSecond(); +} + +bool Unit::IsISVolume(Expression & e) { + // Form m^3 + return e.type() == ExpressionNode::Type::Power && + e.childAtIndex(0).type() == ExpressionNode::Type::Unit && e.childAtIndex(0).convert().isMeter() && + e.childAtIndex(1).type() == ExpressionNode::Type::Rational && e.childAtIndex(1).convert().isThree(); +} + +bool Unit::IsISEnergy(Expression & e) { + // Form _kg*_m^2*_s^-2 + return e.type() == ExpressionNode::Type::Multiplication && e.numberOfChildren() == 3 && + e.childAtIndex(0).type() == ExpressionNode::Type::Unit && e.childAtIndex(0).convert().isKilogram() && + e.childAtIndex(1).type() == ExpressionNode::Type::Power && + e.childAtIndex(1).childAtIndex(0).type() == ExpressionNode::Type::Unit && e.childAtIndex(1).childAtIndex(0).convert().isMeter() && + e.childAtIndex(1).childAtIndex(1).type() == ExpressionNode::Type::Rational && e.childAtIndex(1).childAtIndex(1).convert().isTwo() && + e.childAtIndex(2).type() == ExpressionNode::Type::Power && + e.childAtIndex(2).childAtIndex(0).type() == ExpressionNode::Type::Unit && e.childAtIndex(2).childAtIndex(0).convert().isSecond() && + e.childAtIndex(2).childAtIndex(1).type() == ExpressionNode::Type::Rational && e.childAtIndex(2).childAtIndex(1).convert().isMinusTwo(); +} + +bool Unit::IsISTime(Expression & e) { + return e.type() == ExpressionNode::Type::Unit && static_cast(e).isSecond(); +} + +Expression Unit::BuildTimeSplit(double seconds, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { + assert(!std::isnan(seconds)); + if (std::isinf(seconds) || std::fabs(seconds) < Expression::Epsilon()) { + return Multiplication::Builder(Number::FloatNumber(seconds), Unit::Second()); + } + /* Round the number of seconds to 13 significant digits + * (= k_numberOfStoredSignificantDigits - 1). + * Indeed, the user input has been converted to the most adequate unit + * which might have led to approximating the value to 14 significants + * digits. The number of seconds was then computed from this approximation. + * We thus round it to avoid displaying small numbers of seconds that are + * artifacts of the previous approximations. */ + double err = std::pow(10.0, Poincare::PrintFloat::k_numberOfStoredSignificantDigits - 1 - std::ceil(log10(std::fabs(seconds)))); + double remain = std::round(seconds*err)/err; + + constexpr static int numberOfTimeUnits = 6; + constexpr static double timeFactors[numberOfTimeUnits] = {MonthPerYear*DaysPerMonth*HoursPerDay*MinutesPerHour*SecondsPerMinute, DaysPerMonth*HoursPerDay*MinutesPerHour*SecondsPerMinute, HoursPerDay*MinutesPerHour*SecondsPerMinute, MinutesPerHour*SecondsPerMinute, SecondsPerMinute, 1.0 }; + Unit units[numberOfTimeUnits] = {Unit::Year(), Unit::Month(), Unit::Day(), Unit::Hour(), Unit::Minute(), Unit::Second() }; + double valuesPerUnit[numberOfTimeUnits]; + Addition a = Addition::Builder(); + for (size_t i = 0; i < numberOfTimeUnits; i++) { + valuesPerUnit[i] = remain/timeFactors[i]; + // Keep only the floor of the values except for the last unit (seconds) + if (i < numberOfTimeUnits - 1) { + valuesPerUnit[i] = valuesPerUnit[i] >= 0.0 ? std::floor(valuesPerUnit[i]) : std::ceil(valuesPerUnit[i]); + } + remain -= valuesPerUnit[i]*timeFactors[i]; + if (std::fabs(valuesPerUnit[i]) > Expression::Epsilon()) { + Multiplication m = Multiplication::Builder(Float::Builder(valuesPerUnit[i]), units[i]); + a.addChildAtIndexInPlace(m, a.numberOfChildren(), a.numberOfChildren()); + } + } + ExpressionNode::ReductionContext reductionContext(context, complexFormat, angleUnit, ExpressionNode::ReductionTarget::User, ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition, ExpressionNode::UnitConversion::None); + // Beautify the addition into an subtraction if necessary + return a.squashUnaryHierarchyInPlace().shallowBeautify(reductionContext); } template Evaluation UnitNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; diff --git a/poincare/src/unit_convert.cpp b/poincare/src/unit_convert.cpp index 4dfc699ba..9807c48be 100644 --- a/poincare/src/unit_convert.cpp +++ b/poincare/src/unit_convert.cpp @@ -13,8 +13,19 @@ namespace Poincare { -Expression UnitConvertNode::shallowReduce(ReductionContext reductionContext) { - return UnitConvert(this).shallowReduce(reductionContext); +Expression UnitConvertNode::removeUnit(Expression * unit) { + /* Warning: removeUnit of a UnitConvert doesn't make much sense but we + * implement a 'dummy' version since UnitConvert still exists among the + * reduced expression. */ + childAtIndex(1)->removeUnit(unit); + return UnitConvert(this).replaceWithUndefinedInPlace(); +} +Expression UnitConvertNode::shallowBeautify(ReductionContext reductionContext) { + return UnitConvert(this).shallowBeautify(reductionContext); +} + +void UnitConvertNode::deepReduceChildren(ExpressionNode::ReductionContext reductionContext) { + UnitConvert(this).deepReduceChildren(reductionContext); } template @@ -25,55 +36,63 @@ Evaluation UnitConvertNode::templatedApproximate(Context * context, Preferenc return Complex::Undefined(); } -Expression UnitConvert::shallowReduce(ExpressionNode::ReductionContext reductionContext) { - { - Expression e = Expression::defaultShallowReduce(); - if (e.isUndefined()) { - return e; - } - } +void UnitConvert::deepReduceChildren(ExpressionNode::ReductionContext reductionContext) { + childAtIndex(0).deepReduce(reductionContext); + ExpressionNode::ReductionContext reductionContextKeepUnitAsIs = ExpressionNode::ReductionContext( + reductionContext.context(), + reductionContext.complexFormat(), + reductionContext.angleUnit(), + reductionContext.target(), + ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined, + ExpressionNode::UnitConversion::None); + // Don't transform the targetted unit + childAtIndex(1).deepReduce(reductionContextKeepUnitAsIs); +} - // Find the unit +Expression UnitConvert::shallowBeautify(ExpressionNode::ReductionContext reductionContext) { + // Discard cases like 4 -> _m/_km { ExpressionNode::ReductionContext reductionContextWithUnits = ExpressionNode::ReductionContext( reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), reductionContext.target(), - ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefinedAndReplaceUnits); - Expression unit = childAtIndex(1).clone().reduce(reductionContextWithUnits).getUnit(); - if (unit.isUndefined()) { + ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined); + Expression unit; + childAtIndex(1).clone().deepReduce(reductionContextWithUnits).removeUnit(&unit); + if (unit.isUninitialized()) { // There is no unit on the right return replaceWithUndefinedInPlace(); } } - + // Find the unit ExpressionNode::ReductionContext reductionContextWithoutUnits = ExpressionNode::ReductionContext( reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), reductionContext.target(), - ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefinedAndDoNotReplaceUnits); - Expression finalUnit = childAtIndex(1).reduce(reductionContextWithoutUnits).getUnit(); + ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined, + ExpressionNode::UnitConversion::None); + Expression unit; + childAtIndex(1).removeUnit(&unit); + if (unit.isUninitialized()) { + // There is no unit on the right + return replaceWithUndefinedInPlace(); + } // Divide the left member by the new unit - Expression division = Division::Builder(childAtIndex(0), finalUnit.clone()); - division = division.reduce(reductionContext); - if (division.hasUnit()) { + Expression division = Division::Builder(childAtIndex(0), unit.clone()); + division = division.deepReduce(reductionContext); + Expression divisionUnit; + division = division.removeUnit(&divisionUnit); + if (!divisionUnit.isUninitialized()) { // The left and right members are not homogeneous return replaceWithUndefinedInPlace(); } - double floatValue = division.approximateToScalar(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); - if (std::isinf(floatValue)) { - return Infinity::Builder(false); //FIXME sign? - } - if (std::isnan(floatValue)) { - return Undefined::Builder(); - } - division = Multiplication::Builder(Float::Builder(floatValue), finalUnit); - static_cast(division).mergeMultiplicationChildrenInPlace(); - replaceWithInPlace(division); - return division; + Expression result = Multiplication::Builder(division, unit); + replaceWithInPlace(result); + result.shallowReduce(reductionContextWithoutUnits); + return result.shallowBeautify(reductionContextWithoutUnits); } template Evaluation UnitConvertNode::templatedApproximate(Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; diff --git a/poincare/test/approximation.cpp b/poincare/test/approximation.cpp index ee3d66795..b48a45200 100644 --- a/poincare/test/approximation.cpp +++ b/poincare/test/approximation.cpp @@ -229,6 +229,9 @@ QUIZ_CASE(poincare_approximation_function) { assert_expression_approximates_to("abs(-1)", "1"); assert_expression_approximates_to("abs(-1)", "1"); + assert_expression_approximates_to("abs(-2.3ᴇ-39)", "2.3ᴇ-39", Degree, Cartesian, 5); + assert_expression_approximates_to("abs(-2.3ᴇ-39)", "2.3ᴇ-39"); + assert_expression_approximates_to("abs(3+2𝐢)", "3.605551"); assert_expression_approximates_to("abs(3+2𝐢)", "3.605551275464"); @@ -916,7 +919,11 @@ QUIZ_CASE(poincare_approximation_complex_format) { // Overflow assert_expression_approximates_to("-2ᴇ20+2ᴇ20×𝐢", "-2ᴇ20+2ᴇ20×𝐢", Radian, Cartesian); - assert_expression_approximates_to("-2ᴇ20+2ᴇ20×𝐢", "2.828427ᴇ20×ℯ^\u00122.356194×𝐢\u0013", Radian, Polar); + /* TODO: this test fails on the device because libm hypotf (which is called + * eventually by std::abs) is not accurate enough. We might change the + * embedded libm? */ + //assert_expression_approximates_to("-2ᴇ20+2ᴇ20×𝐢", "2.828427ᴇ20×ℯ^\u00122.356194×𝐢\u0013", Radian, Polar); + assert_expression_approximates_to("-2ᴇ10+2ᴇ10×𝐢", "2.828427ᴇ10×ℯ^\u00122.356194×𝐢\u0013", Radian, Polar); assert_expression_approximates_to("1ᴇ155-1ᴇ155×𝐢", "1ᴇ155-1ᴇ155×𝐢", Radian, Cartesian); assert_expression_approximates_to("1ᴇ155-1ᴇ155×𝐢", "1.41421356237ᴇ155×ℯ^\u0012-0.785398163397×𝐢\u0013", Radian, Polar,12); assert_expression_approximates_to("-2ᴇ100+2ᴇ100×𝐢", Undefined::Name()); diff --git a/poincare/test/context.cpp b/poincare/test/context.cpp index 9137d02df..b8b8d64dd 100644 --- a/poincare/test/context.cpp +++ b/poincare/test/context.cpp @@ -205,7 +205,7 @@ QUIZ_CASE(poincare_context_user_variable_properties) { quiz_assert(Symbol::Builder('a').recursivelyMatches(Expression::IsMatrix, &context)); assert_expression_approximates_to("1.2→b", "1.2"); - quiz_assert(Symbol::Builder('b').recursivelyMatches(Expression::IsApproximate, &context, true)); + quiz_assert(Symbol::Builder('b').recursivelyMatches(Expression::IsApproximate, &context)); /* [[x]]→f(x) expression contains a matrix, so its simplification is going * to be interrupted. We thus rather approximate it instead of simplifying it. @@ -214,7 +214,7 @@ QUIZ_CASE(poincare_context_user_variable_properties) { assert_expression_approximates_to("[[x]]→f(x)", "[[undef]]"); quiz_assert(Function::Builder("f", 1, Symbol::Builder('x')).recursivelyMatches(Poincare::Expression::IsMatrix, &context)); assert_expression_approximates_to("0.2*x→g(x)", "undef"); - quiz_assert(Function::Builder("g", 1, Rational::Builder(2)).recursivelyMatches(Expression::IsApproximate, &context, true)); + quiz_assert(Function::Builder("g", 1, Rational::Builder(2)).recursivelyMatches(Expression::IsApproximate, &context)); // Clean the storage for other tests Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); diff --git a/poincare/test/expression.cpp b/poincare/test/expression.cpp index a18c0bf5a..bd1ae80fc 100644 --- a/poincare/test/expression.cpp +++ b/poincare/test/expression.cpp @@ -3,7 +3,10 @@ #include #include #include +#include +#include #include "tree/helpers.h" +#include "helper.h" using namespace Poincare; @@ -70,3 +73,20 @@ QUIZ_CASE(poincare_expression_rational_constructor) { Rational f = Rational::Builder(overflow, overflow); assert_pool_size(initialPoolSize+6); } + +QUIZ_CASE(poincare_expression_unit_constructor) { + Unit u = Unit::Second(); + assert_expression_serialize_to(u, "_s"); + + u = Unit::Hour(); + assert_expression_serialize_to(u, "_h"); + + u = Unit::Kilometer(); + assert_expression_serialize_to(u, "_km"); + + u = Unit::Liter(); + assert_expression_serialize_to(u, "_L"); + + u = Unit::Watt(); + assert_expression_serialize_to(u, "_W"); +} diff --git a/poincare/test/expression_properties.cpp b/poincare/test/expression_properties.cpp index 5345e5a66..90b986427 100644 --- a/poincare/test/expression_properties.cpp +++ b/poincare/test/expression_properties.cpp @@ -35,12 +35,12 @@ QUIZ_CASE(poincare_properties_is_parametered_expression) { void assert_expression_has_property(const char * expression, Context * context, Expression::ExpressionTest test) { Expression e = parse_expression(expression, context, false); - quiz_assert_print_if_failure(e.recursivelyMatches(test, context, true), expression); + quiz_assert_print_if_failure(e.recursivelyMatches(test, context), expression); } void assert_expression_has_not_property(const char * expression, Context * context, Expression::ExpressionTest test) { Expression e = parse_expression(expression, context, false); - quiz_assert_print_if_failure(!e.recursivelyMatches(test, context, true), expression); + quiz_assert_print_if_failure(!e.recursivelyMatches(test, context), expression); } QUIZ_CASE(poincare_properties_is_approximate) { @@ -328,7 +328,6 @@ void assert_reduced_expression_has_polynomial_coefficient(const char * expressio int d = e.getPolynomialReducedCoefficients(symbolName, coefficientBuffer, &globalContext, complexFormat, Radian, symbolicComputation); for (int i = 0; i <= d; i++) { Expression f = parse_expression(coefficients[i], &globalContext, false); - quiz_assert(!f.isUninitialized()); coefficientBuffer[i] = coefficientBuffer[i].reduce(ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, SystemForAnalysis, symbolicComputation)); f = f.reduce(ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, SystemForAnalysis, symbolicComputation)); quiz_assert_print_if_failure(coefficientBuffer[i].isIdenticalTo(f), expression); @@ -361,6 +360,7 @@ QUIZ_CASE(poincare_properties_get_polynomial_coefficients) { const char * coefficient7[] = {"4", 0}; assert_reduced_expression_has_polynomial_coefficient("x+1", "x", coefficient7 ); const char * coefficient8[] = {"2", "1", 0}; + assert_reduced_expression_has_polynomial_coefficient("x+2", "x", coefficient8, Real, Radian, DoNotReplaceAnySymbol); assert_reduced_expression_has_polynomial_coefficient("x+2", "x", coefficient8, Real, Radian, ReplaceDefinedFunctionsWithDefinitions); assert_reduced_expression_has_polynomial_coefficient("f(x)", "x", coefficient4, Cartesian, Radian, ReplaceDefinedFunctionsWithDefinitions); @@ -369,28 +369,73 @@ QUIZ_CASE(poincare_properties_get_polynomial_coefficients) { Ion::Storage::sharedStorage()->recordNamed("x.exp").destroy(); } -void assert_reduced_expression_unit(const char * expression, const char * unit, ExpressionNode::SymbolicComputation symbolicComutation) { +void assert_reduced_expression_unit_is(const char * expression, const char * unit) { Shared::GlobalContext globalContext; - ExpressionNode::ReductionContext redContext = ExpressionNode::ReductionContext(&globalContext, Real, Degree, SystemForApproximation, symbolicComutation); + ExpressionNode::ReductionContext redContext(&globalContext, Real, Degree, SystemForApproximation); Expression e = parse_expression(expression, &globalContext, false); e = e.reduce(redContext); - Expression u1 = e.getUnit(); - u1 = u1.reduce(redContext); - Expression u2 = parse_expression(unit, &globalContext, false); - u2 = u2.reduce(redContext); - quiz_assert_print_if_failure(u1.isIdenticalTo(u2), expression); + Expression u1; + e = e.removeUnit(&u1); + Expression e2 = parse_expression(unit, &globalContext, false); + Expression u2; + e2 = e2.reduce(redContext); + e2.removeUnit(&u2); + quiz_assert_print_if_failure(u1.isUninitialized() == u2.isUninitialized() && (u1.isUninitialized() || u1.isIdenticalTo(u2)), expression); } -QUIZ_CASE(poincare_properties_get_unit) { - assert_reduced_expression_unit("_km", "_km", ReplaceAllSymbolsWithUndefinedAndDoNotReplaceUnits); - assert_reduced_expression_unit("_min/_km", "_km^(-1)×_min", ReplaceAllSymbolsWithUndefinedAndDoNotReplaceUnits); - assert_reduced_expression_unit("_km^3", "_km^3", ReplaceAllSymbolsWithUndefinedAndDoNotReplaceUnits); - assert_reduced_expression_unit("1_m+_km", Undefined::Name(), ReplaceAllSymbolsWithUndefinedAndDoNotReplaceUnits); - assert_reduced_expression_unit("_L^2×3×_s", "_L^2×_s", ReplaceAllSymbolsWithUndefinedAndDoNotReplaceUnits); - - assert_reduced_expression_unit("_km", "_m", ReplaceAllSymbolsWithDefinitionsOrUndefined); - assert_reduced_expression_unit("_min/_km", "_m^(-1)×_s", ReplaceAllSymbolsWithDefinitionsOrUndefined); - assert_reduced_expression_unit("_km^3", "_m^3", ReplaceAllSymbolsWithDefinitionsOrUndefined); - assert_reduced_expression_unit("1_m+_km", "_m", ReplaceAllSymbolsWithDefinitionsOrUndefined); - assert_reduced_expression_unit("_L^2×3×_s", "_m^6×_s", ReplaceAllSymbolsWithDefinitionsOrUndefined); +QUIZ_CASE(poincare_properties_remove_unit) { + assert_reduced_expression_unit_is("_km", "_m"); + assert_reduced_expression_unit_is("_min/_km", "_m^(-1)×_s"); + assert_reduced_expression_unit_is("_km^3", "_m^3"); + assert_reduced_expression_unit_is("1_m+_km", "_m"); + assert_reduced_expression_unit_is("_L^2×3×_s", "_m^6×_s"); +} + +void assert_seconds_split_to(double totalSeconds, const char * splittedTime, Context * context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { + Expression time = Unit::BuildTimeSplit(totalSeconds, context, complexFormat, angleUnit); + constexpr static int bufferSize = 100; + char buffer[bufferSize]; + time.serialize(buffer, bufferSize, DecimalMode); + quiz_assert_print_if_failure(strcmp(buffer, splittedTime) == 0, splittedTime); +} + +Expression extract_unit(const char * expression) { + Shared::GlobalContext globalContext; + ExpressionNode::ReductionContext reductionContext = ExpressionNode::ReductionContext(&globalContext, Cartesian, Degree, User, ReplaceAllSymbolsWithUndefined, NoUnitConversion); + Expression e = parse_expression(expression, &globalContext, false).reduce(reductionContext); + Expression unit; + e.removeUnit(&unit); + return unit; +} + +QUIZ_CASE(poincare_expression_unit_helper) { + // 1. Time + Expression s = extract_unit("_s"); + quiz_assert(s.type() == ExpressionNode::Type::Unit && static_cast(s).isSecond()); + quiz_assert(!static_cast(s).isMeter()); + + Shared::GlobalContext globalContext; + assert_seconds_split_to(1234567890, "39×_year+1×_month+13×_day+19×_h+1×_min+30×_s", &globalContext, Cartesian, Degree); + assert_seconds_split_to(-122, "-2×_min-2×_s", &globalContext, Cartesian, Degree); + + // 2. Speed + Expression meterPerSecond = extract_unit("_m×_s^-1"); + quiz_assert(Unit::IsISSpeed(meterPerSecond)); + + // 3. Volume + Expression meter3 = extract_unit("_m^3"); + quiz_assert(Unit::IsISVolume(meter3)); + + // 4. Energy + Expression kilogramMeter2PerSecond2 = extract_unit("_kg×_m^2×_s^-2"); + quiz_assert(Unit::IsISEnergy(kilogramMeter2PerSecond2)); + Expression kilogramMeter3PerSecond2 = extract_unit("_kg×_m^3×_s^-2"); + quiz_assert(!Unit::IsISEnergy(kilogramMeter3PerSecond2)); + + // 5. International System + quiz_assert(Unit::IsIS(kilogramMeter2PerSecond2)); + quiz_assert(Unit::IsIS(meter3)); + quiz_assert(Unit::IsIS(meterPerSecond)); + Expression joule = extract_unit("_J"); + quiz_assert(!Unit::IsIS(joule)); } diff --git a/poincare/test/expression_serialization.cpp b/poincare/test/expression_serialization.cpp index 8972460e6..41eed110e 100644 --- a/poincare/test/expression_serialization.cpp +++ b/poincare/test/expression_serialization.cpp @@ -2,13 +2,6 @@ using namespace Poincare; -void assert_expression_serialize_to(Poincare::Expression expression, const char * serialization, Preferences::PrintFloatMode mode = ScientificMode, int numberOfSignificantDigits = 7) { - constexpr int bufferSize = 500; - char buffer[bufferSize]; - expression.serialize(buffer, bufferSize, mode, numberOfSignificantDigits); - quiz_assert_print_if_failure(strcmp(serialization, buffer) == 0, serialization); -} - QUIZ_CASE(poincare_serialization_based_integer) { assert_expression_serialize_to(BasedInteger::Builder(Integer(23), Integer::Base::Decimal), "23"); assert_expression_serialize_to(BasedInteger::Builder(Integer(23), Integer::Base::Binary), "0b10111"); diff --git a/poincare/test/helper.cpp b/poincare/test/helper.cpp index 67d035b41..5d14942c1 100644 --- a/poincare/test/helper.cpp +++ b/poincare/test/helper.cpp @@ -37,10 +37,10 @@ void quiz_assert_log_if_failure(bool test, TreeHandle tree) { quiz_assert(test); } -void assert_parsed_expression_process_to(const char * expression, const char * result, ExpressionNode::ReductionTarget target, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation, ProcessExpression process, int numberOfSignifiantDigits) { +void assert_parsed_expression_process_to(const char * expression, const char * result, ExpressionNode::ReductionTarget target, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::SymbolicComputation symbolicComputation, ExpressionNode::UnitConversion unitConversion, ProcessExpression process, int numberOfSignifiantDigits) { Shared::GlobalContext globalContext; Expression e = parse_expression(expression, &globalContext, false); - Expression m = process(e, ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, target, symbolicComputation)); + Expression m = process(e, ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, target, symbolicComputation, unitConversion)); constexpr int bufferSize = 500; char buffer[bufferSize]; m.serialize(buffer, bufferSize, DecimalMode, numberOfSignifiantDigits); @@ -72,23 +72,22 @@ void assert_parsed_expression_process_to(const char * expression, const char * r Poincare::Expression parse_expression(const char * expression, Context * context, bool addParentheses) { Expression result = Expression::Parse(expression, context, addParentheses); - quiz_assert(!result.isUninitialized()); + quiz_assert_print_if_failure(!result.isUninitialized(), expression); return result; } void assert_simplify(const char * expression, Preferences::AngleUnit angleUnit, Preferences::ComplexFormat complexFormat, ExpressionNode::ReductionTarget target) { Shared::GlobalContext globalContext; Expression e = parse_expression(expression, &globalContext, false); - quiz_assert_print_if_failure(!e.isUninitialized(), expression); - e = e.simplify(ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, target)); + e = e.reduce(ExpressionNode::ReductionContext(&globalContext, complexFormat, angleUnit, target)); quiz_assert_print_if_failure(!(e.isUninitialized()), expression); } -void assert_parsed_expression_simplify_to(const char * expression, const char * simplifiedExpression, ExpressionNode::ReductionTarget target, Preferences::AngleUnit angleUnit, Preferences::ComplexFormat complexFormat, ExpressionNode::SymbolicComputation symbolicComputation) { - assert_parsed_expression_process_to(expression, simplifiedExpression, target, complexFormat, angleUnit, symbolicComputation, [](Expression e, ExpressionNode::ReductionContext reductionContext) { +void assert_parsed_expression_simplify_to(const char * expression, const char * simplifiedExpression, ExpressionNode::ReductionTarget target, Preferences::AngleUnit angleUnit, Preferences::ComplexFormat complexFormat, ExpressionNode::SymbolicComputation symbolicComputation, ExpressionNode::UnitConversion unitConversion) { + assert_parsed_expression_process_to(expression, simplifiedExpression, target, complexFormat, angleUnit, symbolicComputation, unitConversion, [](Expression e, ExpressionNode::ReductionContext reductionContext) { Expression copy = e.clone(); if (reductionContext.target() == ExpressionNode::ReductionTarget::User) { - copy.simplifyAndApproximate(©, nullptr, reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), reductionContext.symbolicComputation()); + copy.simplifyAndApproximate(©, nullptr, reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), reductionContext.symbolicComputation(), reductionContext.unitConversion()); } else { copy = copy.simplify(reductionContext); } @@ -103,14 +102,14 @@ template void assert_expression_approximates_to(const char * expression, const char * approximation, Preferences::AngleUnit angleUnit, Preferences::ComplexFormat complexFormat, int numberOfSignificantDigits) { int numberOfDigits = sizeof(T) == sizeof(double) ? PrintFloat::k_numberOfStoredSignificantDigits : PrintFloat::k_numberOfPrintedSignificantDigits; numberOfDigits = numberOfSignificantDigits > 0 ? numberOfSignificantDigits : numberOfDigits; - assert_parsed_expression_process_to(expression, approximation, SystemForApproximation, complexFormat, angleUnit, ReplaceAllSymbolsWithDefinitionsOrUndefined, [](Expression e, ExpressionNode::ReductionContext reductionContext) { + assert_parsed_expression_process_to(expression, approximation, SystemForApproximation, complexFormat, angleUnit, ReplaceAllSymbolsWithDefinitionsOrUndefined, DefaultUnitConversion, [](Expression e, ExpressionNode::ReductionContext reductionContext) { return e.approximate(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); }, numberOfDigits); } void assert_expression_simplifies_and_approximates_to(const char * expression, const char * approximation, Preferences::AngleUnit angleUnit, Preferences::ComplexFormat complexFormat, int numberOfSignificantDigits) { int numberOfDigits = numberOfSignificantDigits > 0 ? numberOfSignificantDigits : PrintFloat::k_numberOfStoredSignificantDigits; - assert_parsed_expression_process_to(expression, approximation, SystemForApproximation, complexFormat, angleUnit, ReplaceAllSymbolsWithDefinitionsOrUndefined, [](Expression e, ExpressionNode::ReductionContext reductionContext) { + assert_parsed_expression_process_to(expression, approximation, SystemForApproximation, complexFormat, angleUnit, ReplaceAllSymbolsWithDefinitionsOrUndefined, DefaultUnitConversion, [](Expression e, ExpressionNode::ReductionContext reductionContext) { Expression reduced; Expression approximated; e.simplifyAndApproximate(&reduced, &approximated, reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit(), reductionContext.symbolicComputation()); @@ -122,12 +121,19 @@ template void assert_expression_simplifies_approximates_to(const char * expression, const char * approximation, Preferences::AngleUnit angleUnit, Preferences::ComplexFormat complexFormat, int numberOfSignificantDigits) { int numberOfDigits = sizeof(T) == sizeof(double) ? PrintFloat::k_numberOfStoredSignificantDigits : PrintFloat::k_numberOfPrintedSignificantDigits; numberOfDigits = numberOfSignificantDigits > 0 ? numberOfSignificantDigits : numberOfDigits; - assert_parsed_expression_process_to(expression, approximation, SystemForApproximation, complexFormat, angleUnit, ReplaceAllSymbolsWithDefinitionsOrUndefined, [](Expression e, ExpressionNode::ReductionContext reductionContext) { + assert_parsed_expression_process_to(expression, approximation, SystemForApproximation, complexFormat, angleUnit, ReplaceAllSymbolsWithDefinitionsOrUndefined, DefaultUnitConversion, [](Expression e, ExpressionNode::ReductionContext reductionContext) { e = e.simplify(reductionContext); return e.approximate(reductionContext.context(), reductionContext.complexFormat(), reductionContext.angleUnit()); }, numberOfDigits); } +void assert_expression_serialize_to(Poincare::Expression expression, const char * serialization, Preferences::PrintFloatMode mode, int numberOfSignificantDigits) { + constexpr int bufferSize = 500; + char buffer[bufferSize]; + expression.serialize(buffer, bufferSize, mode, numberOfSignificantDigits); + quiz_assert_print_if_failure(strcmp(serialization, buffer) == 0, serialization); +} + void assert_layout_serialize_to(Poincare::Layout layout, const char * serialization) { constexpr int bufferSize = 255; char buffer[bufferSize]; diff --git a/poincare/test/helper.h b/poincare/test/helper.h index 5e7f5942e..6ef23c361 100644 --- a/poincare/test/helper.h +++ b/poincare/test/helper.h @@ -11,7 +11,11 @@ constexpr Poincare::ExpressionNode::ReductionTarget User = Poincare::ExpressionN constexpr Poincare::ExpressionNode::SymbolicComputation ReplaceAllDefinedSymbolsWithDefinition = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition; constexpr Poincare::ExpressionNode::SymbolicComputation ReplaceAllSymbolsWithDefinitionsOrUndefined = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined; constexpr Poincare::ExpressionNode::SymbolicComputation ReplaceDefinedFunctionsWithDefinitions = Poincare::ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions; -constexpr Poincare::ExpressionNode::SymbolicComputation ReplaceAllSymbolsWithUndefinedAndDoNotReplaceUnits = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefinedAndDoNotReplaceUnits; +constexpr Poincare::ExpressionNode::SymbolicComputation ReplaceAllSymbolsWithUndefined = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithUndefined; +constexpr Poincare::ExpressionNode::SymbolicComputation DoNotReplaceAnySymbol = Poincare::ExpressionNode::SymbolicComputation::DoNotReplaceAnySymbol; +constexpr Poincare::ExpressionNode::UnitConversion NoUnitConversion = Poincare::ExpressionNode::UnitConversion::None; +constexpr Poincare::ExpressionNode::UnitConversion DefaultUnitConversion = Poincare::ExpressionNode::UnitConversion::Default; +constexpr Poincare::ExpressionNode::UnitConversion InternationalSystemUnitConversion = Poincare::ExpressionNode::UnitConversion::InternationalSystem; constexpr Poincare::Preferences::AngleUnit Degree = Poincare::Preferences::AngleUnit::Degree; constexpr Poincare::Preferences::AngleUnit Radian = Poincare::Preferences::AngleUnit::Radian; constexpr Poincare::Preferences::AngleUnit Gradian = Poincare::Preferences::AngleUnit::Gradian; @@ -27,7 +31,7 @@ void quiz_assert_log_if_failure(bool test, Poincare::TreeHandle tree); typedef Poincare::Expression (*ProcessExpression)(Poincare::Expression, Poincare::ExpressionNode::ReductionContext reductionContext); -void assert_parsed_expression_process_to(const char * expression, const char * result, Poincare::ExpressionNode::ReductionTarget target, Poincare::Preferences::ComplexFormat complexFormat, Poincare::Preferences::AngleUnit angleUnit, Poincare::ExpressionNode::SymbolicComputation symbolicComputation, ProcessExpression process, int numberOfSignifiantDigits = Poincare::PrintFloat::k_numberOfStoredSignificantDigits); +void assert_parsed_expression_process_to(const char * expression, const char * result, Poincare::ExpressionNode::ReductionTarget target, Poincare::Preferences::ComplexFormat complexFormat, Poincare::Preferences::AngleUnit angleUnit, Poincare::ExpressionNode::SymbolicComputation symbolicComputation, Poincare::ExpressionNode::UnitConversion unitConversion, ProcessExpression process, int numberOfSignifiantDigits = Poincare::PrintFloat::k_numberOfStoredSignificantDigits); // Parsing @@ -37,7 +41,7 @@ Poincare::Expression parse_expression(const char * expression, Poincare::Context void assert_simplify(const char * expression, Poincare::Preferences::AngleUnit angleUnit = Radian, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, Poincare::ExpressionNode::ReductionTarget target = User); -void assert_parsed_expression_simplify_to(const char * expression, const char * simplifiedExpression, Poincare::ExpressionNode::ReductionTarget target = User, Poincare::Preferences::AngleUnit angleUnit = Radian, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = ReplaceAllDefinedSymbolsWithDefinition); +void assert_parsed_expression_simplify_to(const char * expression, const char * simplifiedExpression, Poincare::ExpressionNode::ReductionTarget target = User, Poincare::Preferences::AngleUnit angleUnit = Radian, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = ReplaceAllDefinedSymbolsWithDefinition, Poincare::ExpressionNode::UnitConversion unitConversion = DefaultUnitConversion); // Approximation @@ -47,6 +51,9 @@ void assert_expression_simplifies_and_approximates_to(const char * expression, c template void assert_expression_simplifies_approximates_to(const char * expression, const char * approximation, Poincare::Preferences::AngleUnit angleUnit = Degree, Poincare::Preferences::ComplexFormat complexFormat = Cartesian, int numberOfSignificantDigits = -1); +// Expression serializing + +void assert_expression_serialize_to(Poincare::Expression expression, const char * serialization, Poincare::Preferences::PrintFloatMode mode = ScientificMode, int numberOfSignificantDigits = 7); // Layout serializing diff --git a/poincare/test/parsing.cpp b/poincare/test/parsing.cpp index 8d01d0f2b..4ee1a88e1 100644 --- a/poincare/test/parsing.cpp +++ b/poincare/test/parsing.cpp @@ -239,6 +239,7 @@ QUIZ_CASE(poincare_parsing_parse) { assert_text_not_parsable("t0000000"); assert_text_not_parsable("[[t0000000["); assert_text_not_parsable("0→x=0"); + assert_text_not_parsable("0→3=0"); assert_text_not_parsable("0=0→x"); assert_text_not_parsable("1ᴇ2ᴇ3"); assert_text_not_parsable("0b001112"); @@ -293,7 +294,9 @@ QUIZ_CASE(poincare_parsing_units) { Expression unit = parse_expression(buffer, nullptr, false); quiz_assert_print_if_failure(unit.type() == ExpressionNode::Type::Unit, "Should be parsed as a Unit"); if (rep->isPrefixable()) { - for (const Unit::Prefix * pre = Unit::AllPrefixes; pre < Unit::AllPrefixes + sizeof(Unit::AllPrefixes)/sizeof(Unit::Prefix); pre++) { + size_t numberOfPrefixes = sizeof(Unit::AllPrefixes)/sizeof(Unit::Prefix *); + for (size_t i = 0; i < numberOfPrefixes; i++) { + const Unit::Prefix * pre = Unit::AllPrefixes[i]; Unit::Builder(dim, rep, pre).serialize(buffer, bufferSize, Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits); Expression unit = parse_expression(buffer, nullptr, false); quiz_assert_print_if_failure(unit.type() == ExpressionNode::Type::Unit, "Should be parsed as a Unit"); @@ -428,9 +431,7 @@ QUIZ_CASE(poincare_parsing_parse_store) { assert_text_not_parsable("1→\1"); // UnknownX assert_text_not_parsable("1→\2"); // UnknownN assert_text_not_parsable("1→acos"); - assert_text_not_parsable("1→f(2)"); assert_text_not_parsable("1→f(f)"); - assert_text_not_parsable("3→f(g(4))"); } QUIZ_CASE(poincare_parsing_parse_unit_convert) { @@ -438,20 +439,6 @@ QUIZ_CASE(poincare_parsing_parse_unit_convert) { assert_parsed_expression_is("1→_m", UnitConvert::Builder(BasedInteger::Builder(1), meter)); Expression kilometer = Expression::Parse("_km", nullptr); assert_parsed_expression_is("1→_m/_km", UnitConvert::Builder(BasedInteger::Builder(1), Division::Builder(meter, kilometer))); - - assert_simplify("_m→a", Radian, Real); - assert_simplify("_m→b", Radian, Real); - assert_text_not_parsable("1_km→a×b"); - - assert_simplify("2→a"); - assert_text_not_parsable("3_m→a×_km"); - assert_simplify("2→f(x)"); - assert_text_not_parsable("3_m→f(2)×_km"); - - // Clean the storage for other tests - Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); - Ion::Storage::sharedStorage()->recordNamed("b.exp").destroy(); - Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); } QUIZ_CASE(poincare_parsing_implicit_multiplication) { diff --git a/poincare/test/simplification.cpp b/poincare/test/simplification.cpp index 3438a414d..32094d201 100644 --- a/poincare/test/simplification.cpp +++ b/poincare/test/simplification.cpp @@ -192,6 +192,7 @@ QUIZ_CASE(poincare_simplification_multiplication) { assert_parsed_expression_simplify_to("[[1,2+𝐢][3,4][5,6]]×[[1,2+𝐢,3,4][5,6+𝐢,7,8]]", "[[11+5×𝐢,13+9×𝐢,17+7×𝐢,20+8×𝐢][23,30+7×𝐢,37,44][35,46+11×𝐢,57,68]]"); assert_parsed_expression_simplify_to("[[1,2][3,4]]×[[1,3][5,6]]×[[2,3][4,6]]", "[[82,123][178,267]]"); assert_parsed_expression_simplify_to("π×confidence(π/5,3)[[1,2]]", "π×confidence(π/5,3)×[[1,2]]"); + assert_parsed_expression_simplify_to("0*[[1,0][0,1]]^500", "0×[[1,0][0,1]]^500"); } QUIZ_CASE(poincare_simplification_units) { @@ -203,6 +204,7 @@ QUIZ_CASE(poincare_simplification_units) { assert_parsed_expression_simplify_to("_K", "1×_K"); assert_parsed_expression_simplify_to("_mol", "1×_mol"); assert_parsed_expression_simplify_to("_cd", "1×_cd"); + assert_parsed_expression_simplify_to("-_s", "-1×_s"); /* Inverses of SI base units */ assert_parsed_expression_simplify_to("_s^-1", "1×_s^\u0012-1\u0013"); @@ -245,7 +247,8 @@ QUIZ_CASE(poincare_simplification_units) { Unit::Builder(dim, rep, &Unit::EmptyPrefix).serialize(buffer+strlen("1×"), bufferSize-strlen("1×"), Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits); assert_parsed_expression_simplify_to(buffer, buffer); if (rep->isPrefixable()) { - for (const Unit::Prefix * pre = rep->outputPrefixes(); pre < rep->outputPrefixesUpperBound(); pre++) { + for (size_t i = 0; i < rep->outputPrefixesLength(); i++) { + const Unit::Prefix * pre = rep->outputPrefixes()[i]; Unit::Builder(dim, rep, pre).serialize(buffer+strlen("1×"), bufferSize-strlen("1×"), Preferences::PrintFloatMode::Decimal, Preferences::VeryShortNumberOfSignificantDigits); assert_parsed_expression_simplify_to(buffer, buffer); } @@ -260,6 +263,10 @@ QUIZ_CASE(poincare_simplification_units) { assert_parsed_expression_simplify_to("_L", "0.001×_m^3"); assert_parsed_expression_simplify_to("_ha", "0.01×_km^2"); + /* Unit sum/subtract */ + assert_parsed_expression_simplify_to("_m+_m", "2×_m"); + assert_parsed_expression_simplify_to("_m-_m", "0×_m"); + /* Usual physical quantities */ assert_parsed_expression_simplify_to("_A×_s×_m^(-3)", "1×_C×_m^\u0012-3\u0013"); // Charge density assert_parsed_expression_simplify_to("_kg×_m×_s^(-3)×_K^(-1)", "1×_N×_K^\u0012-1\u0013×_s^\u0012-1\u0013"); // Thermal conductivity _W×_m^-1×_K^-1 @@ -305,10 +312,10 @@ QUIZ_CASE(poincare_simplification_units) { * expression */ assert_parsed_expression_simplify_to("0×_s", "0×_s"); assert_parsed_expression_simplify_to("inf×_s", "inf×_s"); - //assert_parsed_expression_simplify_to("-inf×_s", "-inf×_s"); + assert_parsed_expression_simplify_to("-inf×_s", "-inf×_s"); assert_parsed_expression_simplify_to("2_s+3_s-5_s", "0×_s"); assert_parsed_expression_simplify_to("normcdf(0,20,3)×_s", "0×_s"); - //assert_parsed_expression_simplify_to("log(0)×_s", "-inf×_s"); + assert_parsed_expression_simplify_to("log(0)×_s", "-inf×_s"); assert_parsed_expression_simplify_to("log(undef)*_s", "undef"); /* Units with invalid exponent */ @@ -414,6 +421,7 @@ QUIZ_CASE(poincare_simplification_units) { /* Valid expressions */ assert_parsed_expression_simplify_to("-2×_A", "-2×_A"); assert_parsed_expression_simplify_to("cos(1_s/1_s)", "cos(1)"); + assert_parsed_expression_simplify_to("1_m+π_m+√(2)_m-cos(15)_m", "6.3154941288217×_m"); } QUIZ_CASE(poincare_simplification_power) { @@ -895,6 +903,9 @@ QUIZ_CASE(poincare_simplification_matrix) { assert_parsed_expression_simplify_to("cos(3a)*abs(transpose(a))", "cos(3×confidence(cos(2)/25,3))×abs(transpose(confidence(cos(2)/25,3)))"); assert_parsed_expression_simplify_to("abs(transpose(a))*cos(3a)", "abs(transpose(confidence(cos(2)/25,3)))×cos(3×confidence(cos(2)/25,3))"); Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); + + // Mix + assert_parsed_expression_simplify_to("1/identity(2)^500", "1/[[1,0][0,1]]^500"); } QUIZ_CASE(poincare_simplification_functions_of_matrices) { @@ -1021,6 +1032,9 @@ QUIZ_CASE(poincare_simplification_unit_convert) { assert_parsed_expression_simplify_to("4×_N×3_N×2_N→_N^3", "24×_N^3"); assert_parsed_expression_simplify_to("1→2", Undefined::Name()); + assert_parsed_expression_simplify_to("1→a+a", Undefined::Name()); + assert_parsed_expression_simplify_to("1→f(2)", Undefined::Name()); + assert_parsed_expression_simplify_to("1→f(g(4))", Undefined::Name()); assert_parsed_expression_simplify_to("1→u(n)", Undefined::Name()); assert_parsed_expression_simplify_to("1→u(n+1)", Undefined::Name()); assert_parsed_expression_simplify_to("1→v(n)", Undefined::Name()); @@ -1039,6 +1053,20 @@ QUIZ_CASE(poincare_simplification_unit_convert) { assert_parsed_expression_simplify_to("1→3_m", Undefined::Name()); assert_parsed_expression_simplify_to("4→_km/_m", Undefined::Name()); assert_parsed_expression_simplify_to("3×_min→_s+1-1", Undefined::Name()); + + assert_simplify("_m→a", Radian, Real); + assert_simplify("_m→b", Radian, Real); + assert_parsed_expression_simplify_to("1_km→a×b", Undefined::Name()); + + assert_simplify("2→a"); + assert_parsed_expression_simplify_to("3_m→a×_km", Undefined::Name()); + assert_simplify("2→f(x)"); + assert_parsed_expression_simplify_to("3_m→f(2)×_km", Undefined::Name()); + + // Clean the storage for other tests + Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); + Ion::Storage::sharedStorage()->recordNamed("b.exp").destroy(); + Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); } QUIZ_CASE(poincare_simplification_complex_format) { @@ -1258,6 +1286,12 @@ QUIZ_CASE(poincare_simplification_reduction_target) { assert_parsed_expression_simplify_to("(2+x)^2", "x^2+4×x+4", User); } +QUIZ_CASE(poincare_simplification_unit_conversion) { + assert_parsed_expression_simplify_to("1000000_cm", "10×_km", User, Degree, Cartesian, ReplaceAllDefinedSymbolsWithDefinition, DefaultUnitConversion); + assert_parsed_expression_simplify_to("1000000_cm", "1000000×_cm", User, Degree, Cartesian, ReplaceAllDefinedSymbolsWithDefinition, NoUnitConversion); + assert_parsed_expression_simplify_to("1000000_cm", "10000×_m", User, Degree, Cartesian, ReplaceAllDefinedSymbolsWithDefinition, InternationalSystemUnitConversion); +} + QUIZ_CASE(poincare_simplification_user_function) { // User defined function // f: x → x*1 diff --git a/poincare/test/tree/helpers.cpp b/poincare/test/tree/helpers.cpp index b7709b9d7..22490d99a 100644 --- a/poincare/test/tree/helpers.cpp +++ b/poincare/test/tree/helpers.cpp @@ -2,10 +2,6 @@ #include #include -#if POINCARE_TREE_LOG -#include -#endif - int pool_size() { return Poincare::TreePool::sharedPool()->numberOfNodes(); } diff --git a/poincare/test/tree/helpers.h b/poincare/test/tree/helpers.h index 75becf6bc..6ac0e1263 100644 --- a/poincare/test/tree/helpers.h +++ b/poincare/test/tree/helpers.h @@ -1,7 +1,3 @@ -#if POINCARE_TREE_LOG -#include -#endif - int pool_size(); void assert_pool_size(int i); diff --git a/python/port/genhdr/qstrdefs.in.h b/python/port/genhdr/qstrdefs.in.h index 9eff06b1c..4dbcf5453 100644 --- a/python/port/genhdr/qstrdefs.in.h +++ b/python/port/genhdr/qstrdefs.in.h @@ -439,8 +439,10 @@ Q(EXE) Q(arrow) Q(axis) Q(bar) +Q(c) Q(grid) Q(grid) +Q(head_width) Q(hist) Q(plot) Q(matplotlib) diff --git a/python/port/mod/kandinsky/modkandinsky.cpp b/python/port/mod/kandinsky/modkandinsky.cpp index 8db94f80f..beb261a73 100644 --- a/python/port/mod/kandinsky/modkandinsky.cpp +++ b/python/port/mod/kandinsky/modkandinsky.cpp @@ -32,7 +32,7 @@ mp_obj_t modkandinsky_color(size_t n_args, const mp_obj_t *args) { assert(n_args == 3); color = mp_obj_new_tuple(n_args, args); } - return TupleForKDColor(MicroPython::ColorParser::ParseColor(color)); + return TupleForKDColor(MicroPython::Color::Parse(color)); } /* Calling ExecutionEnvironment::displaySandbox() hides the console and switches @@ -49,7 +49,7 @@ mp_obj_t modkandinsky_get_pixel(mp_obj_t x, mp_obj_t y) { mp_obj_t modkandinsky_set_pixel(mp_obj_t x, mp_obj_t y, mp_obj_t input) { KDPoint point(mp_obj_get_int(x), mp_obj_get_int(y)); - KDColor kdColor = MicroPython::ColorParser::ParseColor(input); + KDColor kdColor = MicroPython::Color::Parse(input); MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); KDIonContext::sharedContext()->setPixel(point, kdColor); return mp_const_none; @@ -59,8 +59,8 @@ mp_obj_t modkandinsky_set_pixel(mp_obj_t x, mp_obj_t y, mp_obj_t input) { mp_obj_t modkandinsky_draw_string(size_t n_args, const mp_obj_t * args) { const char * text = mp_obj_str_get_str(args[0]); KDPoint point(mp_obj_get_int(args[1]), mp_obj_get_int(args[2])); - KDColor textColor = (n_args >= 4) ? MicroPython::ColorParser::ParseColor(args[3]) : KDColorBlack; - KDColor backgroundColor = (n_args >= 5) ? MicroPython::ColorParser::ParseColor(args[4]) : KDColorWhite; + KDColor textColor = (n_args >= 4) ? MicroPython::Color::Parse(args[3]) : KDColorBlack; + KDColor backgroundColor = (n_args >= 5) ? MicroPython::Color::Parse(args[4]) : KDColorWhite; MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); KDIonContext::sharedContext()->drawString(text, point, KDFont::LargeFont, textColor, backgroundColor); /* Before and after execution of "modkandinsky_draw_string", @@ -90,7 +90,7 @@ mp_obj_t modkandinsky_fill_rect(size_t n_args, const mp_obj_t * args) { y = y - height; } KDRect rect(x, y, width, height); - KDColor color = MicroPython::ColorParser::ParseColor(args[4]); + KDColor color = MicroPython::Color::Parse(args[4]); MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); KDIonContext::sharedContext()->fillRect(rect, color); // Cf comment on modkandinsky_draw_string diff --git a/python/port/mod/matplotlib/pyplot/modpyplot.cpp b/python/port/mod/matplotlib/pyplot/modpyplot.cpp index 4381c2520..c81eeb7bf 100644 --- a/python/port/mod/matplotlib/pyplot/modpyplot.cpp +++ b/python/port/mod/matplotlib/pyplot/modpyplot.cpp @@ -42,7 +42,7 @@ static size_t extractArgumentsAndCheckEqualSize(mp_obj_t x, mp_obj_t y, mp_obj_t * - of the required size */ -size_t extractArgumentAndValidateSize(mp_obj_t arg, size_t requiredlength, mp_obj_t ** items) { +static size_t extractArgumentAndValidateSize(mp_obj_t arg, size_t requiredlength, mp_obj_t ** items) { size_t itemLength = extractArgument(arg, items); if (itemLength > 1 && requiredlength > 1 && itemLength != requiredlength) { mp_raise_ValueError("shape mismatch"); @@ -50,6 +50,18 @@ size_t extractArgumentAndValidateSize(mp_obj_t arg, size_t requiredlength, mp_ob return itemLength; } +// Get color from keyword arguments if possible + +bool colorFromKeywordArgument(mp_map_elem_t * elemColor, KDColor * color) { + if (elemColor != nullptr) { + *color = MicroPython::Color::Parse(elemColor->value); + return true; + } else { + *color = Palette::nextDataColor(&paletteIndex); + return false; + } +} + // Internal functions mp_obj_t modpyplot___init__() { @@ -79,16 +91,34 @@ void modpyplot_flush_used_heap() { } } -/* arrow(x,y,dx,dy) +/* arrow(x,y,dx,dy, KW : head_width, color) * x, y, dx, dy scalars * */ -mp_obj_t modpyplot_arrow(size_t n_args, const mp_obj_t *args) { - assert(n_args == 4); +mp_obj_t modpyplot_arrow(size_t n_args, const mp_obj_t *args, mp_map_t* kw_args) { assert(sPlotStore != nullptr); + sPlotStore->setShow(true); - KDColor color = Palette::nextDataColor(&paletteIndex); - sPlotStore->addSegment(args[0], args[1], mp_obj_float_binary_op(MP_BINARY_OP_INPLACE_ADD, mp_obj_get_float(args[0]), args[2]), mp_obj_float_binary_op(MP_BINARY_OP_INPLACE_ADD, mp_obj_get_float(args[1]), args[3]), color, true); + if (n_args > 4) { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,"arrow() takes 4 positional arguments but %d were given",n_args)); + } + + mp_map_elem_t * elem; + // Setting arrow width + elem = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_head_width), MP_MAP_LOOKUP); + /* Default head_width is 0.0f because we want a default width in pixel + * coordinates which is handled by CurveView::drawArrow. */ + mp_obj_t arrowWidth = (elem == nullptr) ? mp_obj_new_float(0.0f) : elem->value; + + // Setting arrow color + KDColor color; + // color keyword + elem = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color), MP_MAP_LOOKUP); + colorFromKeywordArgument(elem, &color); + + // Adding the object to the plot + assert(n_args >= 4); + sPlotStore->addSegment(args[0], args[1], mp_obj_new_float(mp_obj_get_float(args[0]) + mp_obj_get_float(args[2])), mp_obj_new_float(mp_obj_get_float(args[1]) + mp_obj_get_float(args[3])), color, arrowWidth); return mp_const_none; } @@ -100,7 +130,7 @@ mp_obj_t modpyplot_arrow(size_t n_args, const mp_obj_t *args) { mp_obj_t modpyplot_axis(size_t n_args, const mp_obj_t *args) { assert(sPlotStore != nullptr); - + sPlotStore->setShow(true); if (n_args == 1) { mp_obj_t arg = args[0]; if (mp_obj_is_str(arg)) { @@ -143,23 +173,26 @@ mp_obj_t modpyplot_axis(size_t n_args, const mp_obj_t *args) { return mp_obj_new_tuple(4, coords); } -/* bar(x, height, width, bottom) +/* bar(x, height, width, bottom, KW :color) * 'x', 'height', 'width' and 'bottom' can either be a scalar or an array/tuple of * scalar. * 'width' default value is 0.8 * 'bottom' default value is None * */ -// TODO: accept keyword args? - -mp_obj_t modpyplot_bar(size_t n_args, const mp_obj_t *args) { +mp_obj_t modpyplot_bar(size_t n_args, const mp_obj_t *args, mp_map_t* kw_args) { assert(sPlotStore != nullptr); - +if (n_args > 4) { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,"bar() takes from 2 to 4 positional arguments but %d were given",n_args)); + } + sPlotStore->setShow(true); mp_obj_t * xItems; mp_obj_t * hItems; mp_obj_t * wItems; mp_obj_t * bItems; + assert(n_args >= 2); + // x arg size_t xLength = extractArgument(args[0], &xItems); @@ -184,7 +217,13 @@ mp_obj_t modpyplot_bar(size_t n_args, const mp_obj_t *args) { bItems[0] = mp_obj_new_float(0.0f); } - KDColor color = Palette::nextDataColor(&paletteIndex); + // Setting bar color + // color keyword + KDColor color; + // color keyword + mp_map_elem_t * elem = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color), MP_MAP_LOOKUP); + colorFromKeywordArgument(elem, &color); + for (size_t i=0; i 1 ? i : 0]; mp_obj_t iW = wItems[wLength > 1 ? i : 0]; @@ -196,7 +235,7 @@ mp_obj_t modpyplot_bar(size_t n_args, const mp_obj_t *args) { mp_obj_t rectLeft = mp_obj_new_float(iXf - iWf/2.0f); mp_obj_t rectRight = mp_obj_new_float(iXf + iWf/2.0f); mp_obj_t rectBottom = iB; - mp_obj_t rectTop = mp_obj_float_binary_op(MP_BINARY_OP_INPLACE_ADD, mp_obj_get_float(iH), iB); + mp_obj_t rectTop = mp_obj_new_float(mp_obj_get_float(iH) + mp_obj_get_float(iB)); if (mp_obj_get_float(iH) < 0.0) { mp_obj_t temp = rectTop; rectTop = rectBottom; @@ -209,28 +248,33 @@ mp_obj_t modpyplot_bar(size_t n_args, const mp_obj_t *args) { mp_obj_t modpyplot_grid(size_t n_args, const mp_obj_t *args) { assert(sPlotStore != nullptr); - + sPlotStore->setShow(true); if (n_args == 0) { // Toggle the grid visibility sPlotStore->setGridRequested(!sPlotStore->gridRequested()); } else { + assert(n_args >= 1); sPlotStore->setGridRequested(mp_obj_is_true(args[0])); } return mp_const_none; } -/* hist(x, bins) +/* hist(x, bins KW : color) * 'x' array * 'bins': (default value 10) * - int (number of bins) * - sequence of bins * */ -mp_obj_t modpyplot_hist(size_t n_args, const mp_obj_t *args) { +mp_obj_t modpyplot_hist(size_t n_args, const mp_obj_t *args, mp_map_t* kw_args ) { + if (n_args > 2) { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,"hist() takes from 1 to 2 positional arguments but %d were given",n_args)); + } assert(sPlotStore != nullptr); - + sPlotStore->setShow(true); // Sort data to easily get the minimal and maximal value and count bin sizes mp_obj_t * xItems; + assert(n_args >= 1); size_t xLength = extractArgument(args[0], &xItems); if (xLength == 0) { return mp_const_none; @@ -298,25 +342,44 @@ mp_obj_t modpyplot_hist(size_t n_args, const mp_obj_t *args) { binIndex++; } - KDColor color = Palette::nextDataColor(&paletteIndex); + // Setting hist color + // color keyword + KDColor color; + // color keyword + mp_map_elem_t * elem = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color), MP_MAP_LOOKUP); + colorFromKeywordArgument(elem, &color); + for (size_t i=0; iaddRect(edgeItems[i], edgeItems[i+1], binItems[i], mp_obj_new_float(0.0), color); } return mp_const_none; } -/* scatter(x, y) +/* scatter(x, y, KW : color) * - x, y: list * - x, y: scalar * */ -mp_obj_t modpyplot_scatter(mp_obj_t x, mp_obj_t y) { +mp_obj_t modpyplot_scatter(size_t n_args, const mp_obj_t *args, mp_map_t* kw_args) { assert(sPlotStore != nullptr); - + if (n_args > 2) { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,"scatter() takes 2 positional arguments but %d were given",n_args)); + } + sPlotStore->setShow(true); mp_obj_t * xItems, * yItems; - size_t length = extractArgumentsAndCheckEqualSize(x, y, &xItems, &yItems); + assert(n_args >= 2); + size_t length = extractArgumentsAndCheckEqualSize(args[0], args[1], &xItems, &yItems); + + // Setting scatter color + // color keyword + KDColor color; + // c keyword + mp_map_elem_t * elem = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_c), MP_MAP_LOOKUP); + colorFromKeywordArgument(elem, &color); + // color keyword + elem = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color), MP_MAP_LOOKUP); + colorFromKeywordArgument(elem, &color); - KDColor color = Palette::nextDataColor(&paletteIndex); for (size_t i=0; iaddDot(xItems[i], yItems[i], color); } @@ -324,13 +387,16 @@ mp_obj_t modpyplot_scatter(mp_obj_t x, mp_obj_t y) { return mp_const_none; } -/* plot(x, y) plots the curve (x, y) +/* plot(x, y) plots the curve (x, y, KW : color) * plot(y) plots the curve x as index array ([0,1,2...],y) * */ -mp_obj_t modpyplot_plot(size_t n_args, const mp_obj_t *args) { +mp_obj_t modpyplot_plot(size_t n_args, const mp_obj_t *args,mp_map_t* kw_args) { assert(sPlotStore != nullptr); - + sPlotStore->setShow(true); + if (n_args > 3) { + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,"plot() takes 3 positional arguments but %d were given",n_args)); + } mp_obj_t * xItems, * yItems; size_t length; if (n_args == 1) { @@ -342,13 +408,26 @@ mp_obj_t modpyplot_plot(size_t n_args, const mp_obj_t *args) { xItems[i] = mp_obj_new_float((float)i); } } else { - assert(n_args == 2); + assert(n_args >= 2); length = extractArgumentsAndCheckEqualSize(args[0], args[1], &xItems, &yItems); } - KDColor color = Palette::nextDataColor(&paletteIndex); + // Setting plot color + KDColor color; + bool isUserSet = false; + // c keyword + mp_map_elem_t * elem = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_c), MP_MAP_LOOKUP); + isUserSet = colorFromKeywordArgument(elem, &color); + // color keyword + elem = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color), MP_MAP_LOOKUP); + isUserSet = isUserSet | colorFromKeywordArgument(elem, &color); + // Eventual third positional argument + if (!isUserSet && n_args >= 3) { + color = MicroPython::Color::Parse(args[2]); + } + for (int i=0; i<(int)length-1; i++) { - sPlotStore->addSegment(xItems[i], yItems[i], xItems[i+1], yItems[i+1], color, false); + sPlotStore->addSegment(xItems[i], yItems[i], xItems[i+1], yItems[i+1], color); } return mp_const_none; @@ -356,7 +435,7 @@ mp_obj_t modpyplot_plot(size_t n_args, const mp_obj_t *args) { mp_obj_t modpyplot_text(mp_obj_t x, mp_obj_t y, mp_obj_t s) { assert(sPlotStore != nullptr); - + sPlotStore->setShow(true); // Input parameter validation mp_obj_get_float(x); mp_obj_get_float(y); @@ -368,10 +447,11 @@ mp_obj_t modpyplot_text(mp_obj_t x, mp_obj_t y, mp_obj_t s) { } mp_obj_t modpyplot_show() { - if (sPlotStore->isEmpty()) { + if (!sPlotStore->show()) { return mp_const_none; } MicroPython::ExecutionEnvironment * env = MicroPython::ExecutionEnvironment::currentExecutionEnvironment(); env->displayViewController(sPlotController); + sPlotStore->setShow(false); return mp_const_none; } diff --git a/python/port/mod/matplotlib/pyplot/modpyplot.h b/python/port/mod/matplotlib/pyplot/modpyplot.h index 8ab760f73..77615a3e3 100644 --- a/python/port/mod/matplotlib/pyplot/modpyplot.h +++ b/python/port/mod/matplotlib/pyplot/modpyplot.h @@ -4,12 +4,12 @@ mp_obj_t modpyplot___init__(); void modpyplot_gc_collect(); void modpyplot_flush_used_heap(); -mp_obj_t modpyplot_arrow(size_t n_args, const mp_obj_t *args); +mp_obj_t modpyplot_arrow(size_t n_args, const mp_obj_t *args, mp_map_t* kw_args); mp_obj_t modpyplot_axis(size_t n_args, const mp_obj_t *args); -mp_obj_t modpyplot_bar(size_t n_args, const mp_obj_t *args); +mp_obj_t modpyplot_bar(size_t n_args, const mp_obj_t *args, mp_map_t* kw_args); mp_obj_t modpyplot_grid(size_t n_args, const mp_obj_t *args); -mp_obj_t modpyplot_hist(size_t n_args, const mp_obj_t *args); -mp_obj_t modpyplot_plot(size_t n_args, const mp_obj_t *args); -mp_obj_t modpyplot_scatter(mp_obj_t x, mp_obj_t y); +mp_obj_t modpyplot_hist(size_t n_args, const mp_obj_t *args, mp_map_t* kw_args); +mp_obj_t modpyplot_plot(size_t n_args, const mp_obj_t *args, mp_map_t* kw_args); +mp_obj_t modpyplot_scatter(size_t n_args, const mp_obj_t *args, mp_map_t* kw_args); mp_obj_t modpyplot_text(mp_obj_t x, mp_obj_t y, mp_obj_t s); mp_obj_t modpyplot_show(); diff --git a/python/port/mod/matplotlib/pyplot/modpyplot_table.c b/python/port/mod/matplotlib/pyplot/modpyplot_table.c index 1f4c3152b..893b334e4 100644 --- a/python/port/mod/matplotlib/pyplot/modpyplot_table.c +++ b/python/port/mod/matplotlib/pyplot/modpyplot_table.c @@ -1,13 +1,13 @@ #include "modpyplot.h" STATIC MP_DEFINE_CONST_FUN_OBJ_0(modpyplot___init___obj, modpyplot___init__); -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_arrow_obj, 4, 4, modpyplot_arrow); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(modpyplot_arrow_obj, 4, modpyplot_arrow); STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_axis_obj, 0, 1, modpyplot_axis); -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_bar_obj, 2, 4, modpyplot_bar); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(modpyplot_bar_obj, 2, modpyplot_bar); STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_grid_obj, 0, 1, modpyplot_grid); -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_hist_obj, 1, 2, modpyplot_hist); -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(modpyplot_plot_obj, 1, 2, modpyplot_plot); -STATIC MP_DEFINE_CONST_FUN_OBJ_2(modpyplot_scatter_obj, modpyplot_scatter); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(modpyplot_hist_obj, 1, modpyplot_hist); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(modpyplot_plot_obj, 1, modpyplot_plot); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(modpyplot_scatter_obj, 2, modpyplot_scatter); STATIC MP_DEFINE_CONST_FUN_OBJ_0(modpyplot_show_obj, modpyplot_show); STATIC MP_DEFINE_CONST_FUN_OBJ_3(modpyplot_text_obj, modpyplot_text); diff --git a/python/port/mod/matplotlib/pyplot/plot_store.cpp b/python/port/mod/matplotlib/pyplot/plot_store.cpp index de5996894..e772d407b 100644 --- a/python/port/mod/matplotlib/pyplot/plot_store.cpp +++ b/python/port/mod/matplotlib/pyplot/plot_store.cpp @@ -4,9 +4,7 @@ namespace Matplotlib { PlotStore::PlotStore() : Shared::InteractiveCurveViewRange(), - m_axesRequested(true), - m_axesAuto(true), - m_gridRequested(false) + m_show(false) { flush(); } @@ -21,10 +19,6 @@ void PlotStore::flush() { m_gridRequested = false; } -bool PlotStore::isEmpty() { - return MP_OBJ_SMALL_INT_VALUE(mp_obj_len(m_dots)) == 0 && MP_OBJ_SMALL_INT_VALUE(mp_obj_len(m_segments)) == 0 && MP_OBJ_SMALL_INT_VALUE(mp_obj_len(m_rects)) == 0 && MP_OBJ_SMALL_INT_VALUE(mp_obj_len(m_labels)) == 0; -} - // Iterators template @@ -62,15 +56,6 @@ T PlotStore::ListIterator::operator*() { return T(m_tuples[m_tupleIndex]); }; -void checkFloatType(mp_obj_t * elements, size_t nbOfElements) { - for (size_t i = 0; i < nbOfElements; i++) { - // TODO: we don't take advantage of the fact that we extracted the value at the sametime... Maybe change the way things are done, build the c objects in addItem instead of allocating them on the python heap? Or use float array in python? - mp_float_t value; - if (!mp_obj_get_float_maybe(elements[i], &value)) { - mp_raise_TypeError("argument should be a number"); - } - } -} // Dot @@ -87,7 +72,6 @@ PlotStore::Dot::Dot(mp_obj_t tuple) { void PlotStore::addDot(mp_obj_t x, mp_obj_t y, KDColor c) { mp_obj_t color = mp_obj_new_int(c); mp_obj_t items[3] = {x, y, color}; - checkFloatType(items, 2); mp_obj_t tuple = mp_obj_new_tuple(3, items); mp_obj_list_append(m_dots, tuple); } @@ -103,14 +87,13 @@ PlotStore::Segment::Segment(mp_obj_t tuple) { m_yStart = mp_obj_get_float(elements[1]); m_xEnd = mp_obj_get_float(elements[2]); m_yEnd = mp_obj_get_float(elements[3]); - m_color = KDColor::RGB16(mp_obj_get_int(elements[4])); - m_arrow = elements[5] == mp_const_true; + m_arrowWidth = mp_obj_get_float(elements[4]); + m_color = KDColor::RGB16(mp_obj_get_int(elements[5])); } -void PlotStore::addSegment(mp_obj_t xStart, mp_obj_t yStart, mp_obj_t xEnd, mp_obj_t yEnd, KDColor c, bool arrowEdge) { +void PlotStore::addSegment(mp_obj_t xStart, mp_obj_t yStart, mp_obj_t xEnd, mp_obj_t yEnd, KDColor c, mp_obj_t arrowWidth) { mp_obj_t color = mp_obj_new_int(c); - mp_obj_t items[6] = {xStart, yStart, xEnd, yEnd, color, arrowEdge ? mp_const_true : mp_const_false}; - checkFloatType(items, 4); + mp_obj_t items[6] = {xStart, yStart, xEnd, yEnd, arrowWidth, color}; mp_obj_t tuple = mp_obj_new_tuple(6, items); mp_obj_list_append(m_segments, tuple); } @@ -132,7 +115,6 @@ PlotStore::Rect::Rect(mp_obj_t tuple) { void PlotStore::addRect(mp_obj_t left, mp_obj_t right, mp_obj_t top, mp_obj_t bottom, KDColor c) { mp_obj_t color = mp_obj_new_int(c); mp_obj_t items[5] = {left, right, top, bottom, color}; - checkFloatType(items, 4); mp_obj_t tuple = mp_obj_new_tuple(5, items); mp_obj_list_append(m_rects, tuple); } @@ -151,7 +133,6 @@ PlotStore::Label::Label(mp_obj_t tuple) { void PlotStore::addLabel(mp_obj_t x, mp_obj_t y, mp_obj_t string) { mp_obj_t items[3] = {x, y, string}; - checkFloatType(items, 2); if (!mp_obj_is_str(string)) { mp_raise_TypeError("argument should be a string"); } diff --git a/python/port/mod/matplotlib/pyplot/plot_store.h b/python/port/mod/matplotlib/pyplot/plot_store.h index d74b60cb0..6009bbf76 100644 --- a/python/port/mod/matplotlib/pyplot/plot_store.h +++ b/python/port/mod/matplotlib/pyplot/plot_store.h @@ -13,7 +13,6 @@ class PlotStore : public Shared::InteractiveCurveViewRange { public: PlotStore(); void flush(); - bool isEmpty(); // Iterators @@ -68,18 +67,18 @@ public: float yStart() const { return m_yStart; } float xEnd() const { return m_xEnd; } float yEnd() const { return m_yEnd; } - bool isArrow() const { return m_arrow; } + float arrowWidth() const { return m_arrowWidth; } KDColor color() const { return m_color; } private: float m_xStart; float m_yStart; float m_xEnd; float m_yEnd; - bool m_arrow; + float m_arrowWidth; KDColor m_color; }; - void addSegment(mp_obj_t xStart, mp_obj_t yStart, mp_obj_t xEnd, mp_obj_t yEnd, KDColor c, bool arrowEdge); + void addSegment(mp_obj_t xStart, mp_obj_t yStart, mp_obj_t xEnd, mp_obj_t yEnd, KDColor c, mp_obj_t arrowWidth = mp_obj_new_float(NAN)); Iterable> segments() { return Iterable>(m_segments); } // Rect @@ -123,6 +122,8 @@ public: void setAxesRequested(bool b) { m_axesRequested = b; } bool axesRequested() const { return m_axesRequested; } void setAxesAuto(bool b) { m_axesAuto = b; } + void setShow(bool b) { m_show = b; } + bool show() { return m_show; } void initRange(); void setGridRequested(bool b) { m_gridRequested = b; } @@ -135,6 +136,7 @@ private: bool m_axesRequested; bool m_axesAuto; bool m_gridRequested; + bool m_show; }; } diff --git a/python/port/mod/matplotlib/pyplot/plot_view.cpp b/python/port/mod/matplotlib/pyplot/plot_view.cpp index 4da55f8cf..274e3b132 100644 --- a/python/port/mod/matplotlib/pyplot/plot_view.cpp +++ b/python/port/mod/matplotlib/pyplot/plot_view.cpp @@ -46,10 +46,10 @@ void PlotView::traceSegment(KDContext * ctx, KDRect r, PlotStore::Segment segmen segment.xEnd(), segment.yEnd(), segment.color() ); - if (segment.isArrow()) { + if (!std::isnan(segment.arrowWidth())) { float dx = segment.xEnd() - segment.xStart(); float dy = segment.yEnd() - segment.yStart(); - drawArrow(ctx, r, segment.xEnd(), segment.yEnd(), dx, dy, segment.color()); + drawArrow(ctx, r, segment.xEnd(), segment.yEnd(), dx, dy, segment.color(), segment.arrowWidth()); } } diff --git a/python/port/mod/turtle/modturtle.cpp b/python/port/mod/turtle/modturtle.cpp index e6bafba70..e08aa4dbd 100644 --- a/python/port/mod/turtle/modturtle.cpp +++ b/python/port/mod/turtle/modturtle.cpp @@ -139,7 +139,7 @@ mp_obj_t modturtle_pencolor(size_t n_args, const mp_obj_t *args) { // pencolor() KDColor c = sTurtle.color(); mp_obj_t mp_col[3]; - if(sTurtle.colorMode() == MicroPython::ColorParser::ColorMode::MaxIntensity255){ + if(sTurtle.colorMode() == MicroPython::Color::Mode::MaxIntensity255){ mp_col[0] = mp_obj_new_int_from_uint(c.red()); mp_col[1] = mp_obj_new_int_from_uint(c.green()); mp_col[2] = mp_obj_new_int_from_uint(c.blue()); @@ -161,21 +161,29 @@ mp_obj_t modturtle_pencolor(size_t n_args, const mp_obj_t *args) { assert(n_args == 3); color = mp_obj_new_tuple(n_args, args); } - sTurtle.setColor(MicroPython::ColorParser::ParseColor(color, sTurtle.colorMode())); + sTurtle.setColor(MicroPython::Color::Parse(color, sTurtle.colorMode())); return mp_const_none; } mp_obj_t modturtle_colormode(size_t n_args, const mp_obj_t *args) { if(n_args == 0){ return mp_obj_new_int_from_uint(static_cast(sTurtle.colorMode())); - } else{ - int colorMode = mp_obj_get_int(args[0]); - if (colorMode != static_cast(MicroPython::ColorParser::ColorMode::MaxIntensity1) && - colorMode != static_cast(MicroPython::ColorParser::ColorMode::MaxIntensity255)) { + } else { + // To accept both colormode(1) and colormode(1.0) we try to get args[0] as both int and float + mp_float_t decimalOne = mp_obj_get_float(args[0]); + int colorMode; + // But only 1 is accepted as float, 255 must be int + if (decimalOne == 1.0) { + colorMode = static_cast(MicroPython::Color::Mode::MaxIntensity1); + } else { + colorMode = mp_obj_get_int(args[0]); + } + if (colorMode != static_cast(MicroPython::Color::Mode::MaxIntensity1) && + colorMode != static_cast(MicroPython::Color::Mode::MaxIntensity255)) { mp_raise_ValueError("Colormode can be 1 or 255"); return mp_const_none; } - sTurtle.setColorMode(static_cast(colorMode)); + sTurtle.setColorMode(static_cast(colorMode)); return mp_const_none; } } @@ -193,3 +201,9 @@ mp_obj_t modturtle_hideturtle() { mp_obj_t modturtle_isvisible() { return sTurtle.isVisible() ? mp_const_true : mp_const_false; } + +mp_obj_t modturtle_write(mp_obj_t s) { + const char * string = mp_obj_str_get_str(s); + sTurtle.write(string); + return mp_const_none; +} \ No newline at end of file diff --git a/python/port/mod/turtle/modturtle.h b/python/port/mod/turtle/modturtle.h index 303ea13d4..363a6a47d 100644 --- a/python/port/mod/turtle/modturtle.h +++ b/python/port/mod/turtle/modturtle.h @@ -23,6 +23,7 @@ mp_obj_t modturtle_penup(); mp_obj_t modturtle_isdown(); mp_obj_t modturtle_pensize(size_t n_args, const mp_obj_t *args); mp_obj_t modturtle_isvisible(); +mp_obj_t modturtle_write(mp_obj_t s); mp_obj_t modturtle_pencolor(size_t n_args, const mp_obj_t *args); mp_obj_t modturtle_colormode(size_t n_args, const mp_obj_t *args); diff --git a/python/port/mod/turtle/modturtle_table.c b/python/port/mod/turtle/modturtle_table.c index c190cc540..ccadb1de1 100644 --- a/python/port/mod/turtle/modturtle_table.c +++ b/python/port/mod/turtle/modturtle_table.c @@ -25,6 +25,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(modturtle_reset_obj, modturtle_reset); STATIC MP_DEFINE_CONST_FUN_OBJ_0(modturtle_showturtle_obj, modturtle_showturtle); STATIC MP_DEFINE_CONST_FUN_OBJ_0(modturtle_hideturtle_obj, modturtle_hideturtle); STATIC MP_DEFINE_CONST_FUN_OBJ_0(modturtle_isvisible_obj, modturtle_isvisible); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(modturtle_write_obj, modturtle_write); STATIC MP_DEFINE_CONST_FUN_OBJ_0(modturtle___init___obj, modturtle___init__); @@ -74,6 +75,7 @@ STATIC const mp_rom_map_elem_t modturtle_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_hideturtle), (mp_obj_t)&modturtle_hideturtle_obj }, { MP_ROM_QSTR(MP_QSTR_ht), (mp_obj_t)&modturtle_hideturtle_obj }, { MP_ROM_QSTR(MP_QSTR_isvisible), (mp_obj_t)&modturtle_isvisible_obj }, + { MP_ROM_QSTR(MP_QSTR_write), (mp_obj_t)&modturtle_write_obj }, }; STATIC MP_DEFINE_CONST_DICT(modturtle_module_globals, modturtle_module_globals_table); diff --git a/python/port/mod/turtle/turtle.cpp b/python/port/mod/turtle/turtle.cpp index 811650c3b..0b8f85c90 100644 --- a/python/port/mod/turtle/turtle.cpp +++ b/python/port/mod/turtle/turtle.cpp @@ -175,6 +175,21 @@ void Turtle::setVisible(bool visible) { } } +void Turtle::write(const char * string) { + // We erase the turtle to redraw it on top of the text + erase(); + MicroPython::ExecutionEnvironment::currentExecutionEnvironment()->displaySandbox(); + KDContext * ctx = KDIonContext::sharedContext(); + static constexpr KDCoordinate headOffsetLength = 6; + KDCoordinate headOffsetX = headOffsetLength * std::cos(m_heading * k_headingScale); + KDCoordinate headOffsetY = k_invertedYAxisCoefficient * headOffsetLength * std::sin(m_heading * k_headingScale); + KDPoint headOffset(headOffsetX, headOffsetY); + KDPoint head(-k_iconHeadSize, -k_iconHeadSize); + KDPoint stringOffset = KDPoint(0,-k_font->glyphSize().height()); + ctx->drawString(string, position().translatedBy(headOffset).translatedBy(head).translatedBy(stringOffset)); + draw(true); +} + void Turtle::viewDidDisappear() { m_drawn = false; diff --git a/python/port/mod/turtle/turtle.h b/python/port/mod/turtle/turtle.h index 2c49c6b1d..bf0045ee9 100644 --- a/python/port/mod/turtle/turtle.h +++ b/python/port/mod/turtle/turtle.h @@ -30,7 +30,7 @@ public: m_y(0), m_heading(0), m_color(k_defaultColor), - m_colorMode(MicroPython::ColorParser::ColorMode::MaxIntensity255), + m_colorMode(MicroPython::Color::Mode::MaxIntensity255), m_penDown(true), m_visible(true), m_speed(k_defaultSpeed), @@ -73,11 +73,13 @@ public: void setColor(uint8_t r, uint8_t g, uint8_t b) { m_color = KDColor::RGB888(r, g, b); } - MicroPython::ColorParser::ColorMode colorMode() const {return m_colorMode; } - void setColorMode(MicroPython::ColorParser::ColorMode colorMode){ + MicroPython::Color::Mode colorMode() const {return m_colorMode; } + void setColorMode(MicroPython::Color::Mode colorMode){ m_colorMode = colorMode; } + void write(const char * string); + void viewDidDisappear(); private: @@ -91,6 +93,7 @@ private: static constexpr uint8_t k_maxSpeed = 10; static constexpr KDColor k_defaultColor = KDColorBlack; static constexpr uint8_t k_defaultPenSize = 1; + static constexpr const KDFont * k_font = KDFont::LargeFont; enum class PawType : uint8_t { FrontRight = 0, @@ -141,7 +144,7 @@ private: mp_float_t m_heading; KDColor m_color; - MicroPython::ColorParser::ColorMode m_colorMode; + MicroPython::Color::Mode m_colorMode; bool m_penDown; bool m_visible; diff --git a/python/port/port.cpp b/python/port/port.cpp index bac7595ca..bc2ee4113 100644 --- a/python/port/port.cpp +++ b/python/port/port.cpp @@ -155,40 +155,56 @@ void MicroPython::registerScriptProvider(ScriptProvider * s) { } void MicroPython::collectRootsAtAddress(char * address, int byteLength) { - /* All addresses stored on the stack are aligned on sizeof(void *), as - * asserted. This is a consequence of the alignment requirements of compilers - * (Cf http://www.catb.org/esr/structure-packing/). */ - assert(((unsigned long)address) % ((unsigned long)sizeof(void *)) == 0); - assert(byteLength % sizeof(void *) == 0); - gc_collect_root((void **)address, byteLength / sizeof(void *)); + /* The given address is not necessarily aligned on sizeof(void *). However, + * any pointer stored in the range [address, address + byteLength] will be + * aligned on sizeof(void *). This is a consequence of the alignment + * requirements of compilers (Cf http://www.catb.org/esr/structure-packing/). + * Micropython gc_collect_root scans looking for pointers jumping every + * sizeof(void *). It has to be provided with a sizeof(uintptr_t)-aligned + * address. */ + // Compute the aligned address + // 0b000...00011 with 2 (or 3 for x64 arch) 1s + uintptr_t bitMaskOnes = sizeof(uintptr_t) - 1; + // 0b111...11100 with sizeof(uintptr_t)-1 0s + uintptr_t bitMaskZeros = ~bitMaskOnes; + uintptr_t alignedAddress = reinterpret_cast(address) & bitMaskZeros; + /* Increase the length consequently with the new alignment + * (We don't need to increase the byteLength to a sizeof(uintptr_t)-aligned + * lenght because no pointer can be stored on less than sizeof(uintptr_t) + * bytes.) */ + int alignedByteLength = byteLength; + alignedByteLength += reinterpret_cast(address) & bitMaskOnes; + + assert(alignedAddress % ((uintptr_t)sizeof(uintptr_t)) == 0); + gc_collect_root((void **)alignedAddress, byteLength / sizeof(uintptr_t)); } -KDColor MicroPython::ColorParser::ParseColor(mp_obj_t input, ColorMode ColorMode){ - static constexpr int maxColorIntensity = static_cast(ColorMode::MaxIntensity255); +KDColor MicroPython::Color::Parse(mp_obj_t input, Mode mode){ + static constexpr int maxColorIntensity = static_cast(Mode::MaxIntensity255); if (mp_obj_is_str(input)) { size_t l; const char * color = mp_obj_str_get_data(input, &l); - // TODO add cyan - constexpr NameColorPair pairs[] = { - NameColorPair("blue", KDColorBlue), - NameColorPair("b", KDColorBlue), - NameColorPair("red", KDColorRed), - NameColorPair("r", KDColorRed), - NameColorPair("green", Palette::Green), - NameColorPair("g", Palette::Green), - NameColorPair("yellow", KDColorYellow), - NameColorPair("y", KDColorYellow), - NameColorPair("brown", Palette::Brown), - NameColorPair("black", KDColorBlack), - NameColorPair("k", KDColorBlack), - NameColorPair("white", KDColorWhite), - NameColorPair("w", KDColorWhite), - NameColorPair("pink", Palette::Pink), - NameColorPair("orange", Palette::Orange), - NameColorPair("purple", Palette::Purple), - NameColorPair("grey", Palette::GreyDark) + constexpr NamedColor pairs[] = { + NamedColor("blue", KDColorBlue), + NamedColor("b", KDColorBlue), + NamedColor("red", KDColorRed), + NamedColor("r", KDColorRed), + NamedColor("green", Palette::Green), + NamedColor("g", Palette::Green), + NamedColor("yellow", KDColorYellow), + NamedColor("y", KDColorYellow), + NamedColor("brown", Palette::Brown), + NamedColor("black", KDColorBlack), + NamedColor("k", KDColorBlack), + NamedColor("white", KDColorWhite), + NamedColor("w", KDColorWhite), + NamedColor("pink", Palette::Pink), + NamedColor("orange", Palette::Orange), + NamedColor("purple", Palette::Purple), + NamedColor("grey", Palette::GreyDark), + NamedColor("cyan", Palette::Cyan) }; - for (NameColorPair p : pairs) { + for (NamedColor p : pairs) { if (strcmp(p.name(), color) == 0) { return p.color(); } @@ -221,7 +237,7 @@ KDColor MicroPython::ColorParser::ParseColor(mp_obj_t input, ColorMode ColorMode if (len != 3) { mp_raise_TypeError("Color needs 3 components"); } - int intensityFactor = maxColorIntensity/static_cast(ColorMode); + int intensityFactor = maxColorIntensity/static_cast(mode); return KDColor::RGB888( intensityFactor * mp_obj_get_float(elem[0]), intensityFactor * mp_obj_get_float(elem[1]), @@ -288,7 +304,7 @@ void nlr_jump_fail(void *val) { mp_lexer_t * mp_lexer_new_from_file(const char * filename) { if (sScriptProvider != nullptr) { - const char * script = sScriptProvider->contentOfScript(filename); + const char * script = sScriptProvider->contentOfScript(filename, true); if (script != nullptr) { return mp_lexer_new_from_str_len(qstr_from_str(filename), script, strlen(script), 0 /* size_t free_len*/); } else { @@ -300,7 +316,7 @@ mp_lexer_t * mp_lexer_new_from_file(const char * filename) { } mp_import_stat_t mp_import_stat(const char *path) { - if (sScriptProvider && sScriptProvider->contentOfScript(path)) { + if (sScriptProvider && sScriptProvider->contentOfScript(path, false)) { return MP_IMPORT_STAT_FILE; } return MP_IMPORT_STAT_NO_EXIST; diff --git a/python/port/port.h b/python/port/port.h index 3ab3a603f..75a973887 100644 --- a/python/port/port.h +++ b/python/port/port.h @@ -12,7 +12,7 @@ namespace MicroPython { class ScriptProvider { public: - virtual const char * contentOfScript(const char * name) = 0; + virtual const char * contentOfScript(const char * name, bool markAsFetched) = 0; }; class ExecutionEnvironment { @@ -41,11 +41,18 @@ void deinit(); void registerScriptProvider(ScriptProvider * s); void collectRootsAtAddress(char * address, int len); -class ColorParser { - private: - class NameColorPair { +class Color { +public: + enum class Mode { + MaxIntensity1 = 1, + MaxIntensity255 = 255, + }; + + static KDColor Parse(mp_obj_t input, Mode Mode = Mode::MaxIntensity255); +private: + class NamedColor { public: - constexpr NameColorPair(const char * name, KDColor color) : + constexpr NamedColor(const char * name, KDColor color) : m_name(name), m_color(color) {} @@ -55,14 +62,6 @@ class ColorParser { const char * m_name; KDColor m_color; }; - - public: - enum class ColorMode { - MaxIntensity1 = 1, - MaxIntensity255 = 255, - }; - - static KDColor ParseColor(mp_obj_t input, ColorMode ColorMode = ColorMode::MaxIntensity255); }; diff --git a/python/test/matplotlib.cpp b/python/test/matplotlib.cpp index b673d63ca..3ba0bd6df 100644 --- a/python/test/matplotlib.cpp +++ b/python/test/matplotlib.cpp @@ -35,6 +35,11 @@ QUIZ_CASE(python_matplotlib_pyplot_arrow) { TestExecutionEnvironment env = init_environement(); assert_command_execution_succeeds(env, "from matplotlib.pyplot import *"); assert_command_execution_succeeds(env, "arrow(2,3,4,5)"); + assert_command_execution_fails(env, "arrow(2,3,4,5, 0.1)"); + assert_command_execution_fails(env, "arrow(2,3,4,5, \"width\")"); + assert_command_execution_fails(env, "arrow(2,3,4,5, 0.1, \"#FF00FF\")"); + assert_command_execution_succeeds(env, "arrow(2,3,4,5,head_width=0.3)"); + assert_command_execution_succeeds(env, "arrow(2,3,4,5,color=\"red\")"); assert_command_execution_succeeds(env, "show()"); deinit_environment(); } @@ -59,6 +64,7 @@ QUIZ_CASE(python_matplotlib_pyplot_bar) { assert_command_execution_succeeds(env, "bar([],[])"); assert_command_execution_succeeds(env, "bar([1,2,3],[1,2,3],2,3)"); assert_command_execution_succeeds(env, "bar([1,2,3],[1,2,3],[1,2,3],[1,2,3])"); + assert_command_execution_succeeds(env, "bar([1,2,3],[1,2,3],[1,2,3],[1,2,3], color=\"orange\")"); assert_command_execution_succeeds(env, "show()"); assert_command_execution_fails(env, "bar([1,2,3],[1,2,3,4],[1,2,3],[1,2,3])"); deinit_environment(); @@ -79,6 +85,7 @@ QUIZ_CASE(python_matplotlib_pyplot_hist) { assert_command_execution_succeeds(env, "hist([2,3,4,5,6],23)"); assert_command_execution_succeeds(env, "hist([2,3,4,5,6],[0,2,3])"); assert_command_execution_succeeds(env, "hist([2,3,4,5,6],[0,2,3, 4,5,6,7])"); + assert_command_execution_succeeds(env, "hist([2,3,4,5,6],[0,2,3, 4,5,6,7], color=(0,255,0))"); assert_command_execution_succeeds(env, "show()"); deinit_environment(); } @@ -89,6 +96,7 @@ QUIZ_CASE(python_matplotlib_pyplot_plot) { assert_command_execution_succeeds(env, "plot([2,3,4,5,6])"); assert_command_execution_succeeds(env, "plot(2,3)"); assert_command_execution_succeeds(env, "plot([2,3,4,5,6],[3,4,5,6,7])"); + assert_command_execution_succeeds(env, "plot([2,3,4,5,6],[3,4,5,6,7], color=\"g\")"); assert_command_execution_succeeds(env, "show()"); assert_command_execution_fails(env, "plot([2,3,4,5,6],2)"); deinit_environment(); @@ -99,6 +107,7 @@ QUIZ_CASE(python_matplotlib_pyplot_scatter) { assert_command_execution_succeeds(env, "from matplotlib.pyplot import *"); assert_command_execution_succeeds(env, "scatter(2,3)"); assert_command_execution_succeeds(env, "scatter([2,3,4,5,6],[3,4,5,6,7])"); + assert_command_execution_succeeds(env, "scatter([2,3,4,5,6],[3,4,5,6,7], color=(0,0,255))"); assert_command_execution_succeeds(env, "show()"); assert_command_execution_fails(env, "scatter([2,3,4,5,6],2)"); deinit_environment(); diff --git a/quiz/Makefile b/quiz/Makefile index 25ddd96b1..10ffb7e9f 100644 --- a/quiz/Makefile +++ b/quiz/Makefile @@ -14,6 +14,7 @@ runner_src += $(addprefix quiz/src/, \ assertions.cpp \ i18n.cpp \ runner.cpp \ + stopwatch.cpp \ ) runner_src += $(BUILD_DIR)/quiz/src/tests_symbols.c diff --git a/quiz/include/quiz/stopwatch.h b/quiz/include/quiz/stopwatch.h new file mode 100644 index 000000000..53a582b08 --- /dev/null +++ b/quiz/include/quiz/stopwatch.h @@ -0,0 +1,17 @@ +#ifndef QUIZ_STOPWATCH_H +#define QUIZ_STOPWATCH_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +uint64_t quiz_stopwatch_start(); +void quiz_stopwatch_print_lap(uint64_t startTime); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/quiz/src/runner.cpp b/quiz/src/runner.cpp index dae22605a..ef4b883c3 100644 --- a/quiz/src/runner.cpp +++ b/quiz/src/runner.cpp @@ -1,8 +1,6 @@ #include "quiz.h" #include "symbols.h" -#include #include -#include #include #include #include diff --git a/quiz/src/stopwatch.cpp b/quiz/src/stopwatch.cpp new file mode 100644 index 000000000..f4dafac16 --- /dev/null +++ b/quiz/src/stopwatch.cpp @@ -0,0 +1,36 @@ +#include +#include "quiz.h" +#include +#include + +uint64_t quiz_stopwatch_start() { + return Ion::Timing::millis(); +} + +static size_t uint64ToString(uint64_t n, char buffer[]) { + size_t len = 0; + do { + buffer[len++] = (n % 10) + '0'; + } while ((n /= 10) > 0); + int i = 0; + int j = len - 1; + while (i < j) { + char c = buffer[i]; + buffer[i++] = buffer[j]; + buffer[j--] = c; + } + return len; +} + +void quiz_stopwatch_print_lap(uint64_t startTime) { + constexpr char Time[] = " time: "; + constexpr char Ms[] = "ms"; + constexpr size_t uint64ToStringMaxLength = 20; + constexpr size_t MaxLength = sizeof(Time) + uint64ToStringMaxLength + sizeof(Ms) + 1; + char buffer[MaxLength]; + char * position = buffer; + position += strlcpy(position, Time, sizeof(Time)); + position += uint64ToString(Ion::Timing::millis() - startTime, position); + position += strlcpy(position, Ms, sizeof(Ms)); + quiz_print(buffer); +}