diff --git a/Makefile b/Makefile index 2153d1439..baa2d3734 100644 --- a/Makefile +++ b/Makefile @@ -31,11 +31,11 @@ endef define rule_for ifeq ($(strip $(5)),with_local_version) -$(addprefix $$(BUILD_DIR)/,$(strip $(2))): $(addprefix $$(BUILD_DIR)/,$(strip $(3))) +$(addprefix $$(BUILD_DIR)/,$(strip $(2))): $(addprefix $$(BUILD_DIR)/,$(strip $(3))) | $(if $(findstring official,${MAKECMDGOALS}),official_authorization) @ echo "$(shell printf "%-8s" $(strip $(1)))$$(@:$$(BUILD_DIR)/%=%)" $(Q) $(4) endif -$(addprefix $$(BUILD_DIR)/,$(strip $(2))): $(strip $(3)) | $$$$(@D)/. +$(addprefix $$(BUILD_DIR)/,$(strip $(2))): $(strip $(3)) | $$$$(@D)/. $(if $(findstring official,${MAKECMDGOALS}),official_authorization) @ echo "$(shell printf "%-8s" $(strip $(1)))$$(@:$$(BUILD_DIR)/%=%)" $(Q) $(4) endef diff --git a/apps/Makefile b/apps/Makefile index ec6f067c2..efc2c58d0 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -31,6 +31,10 @@ app_src += $(addprefix apps/,\ variable_box_empty_controller.cpp \ ) +tests_src += apps/exam_mode_configuration_official.cpp +apps_official += apps/exam_mode_configuration_official.cpp +apps_non_official += apps/exam_mode_configuration_non_official.cpp + apps_launch_on_boarding_src += apps/apps_container_launch_on_boarding.cpp apps_launch_default_src += apps/apps_container_launch_default.cpp apps_prompt_none_src += apps/apps_container_prompt_none.cpp @@ -90,12 +94,13 @@ $(BUILD_DIR)/apps/i18n.h: $(BUILD_DIR)/apps/i18n.cpp $(eval $(call depends_on_image,apps/title_bar_view.cpp,apps/exam_icon.png)) -all_app_src = $(app_src) $(epsilon_src) $(apps_launch_on_boarding_src) $(apps_launch_default_src) $(apps_prompt_none_src) $(apps_prompt_update_src) $(apps_prompt_beta_src) $(tests_src) +all_app_src = $(app_src) $(epsilon_src) $(apps_launch_on_boarding_src) $(apps_launch_default_src) $(apps_prompt_none_src) $(apps_prompt_update_src) $(apps_prompt_beta_src) $(apps_official) $(apps_non_official) $(tests_src) $(call object_for,$(all_app_src)): $(BUILD_DIR)/apps/i18n.h $(call object_for,$(all_app_src)): $(BUILD_DIR)/python/port/genhdr/qstrdefs.generated.h -apps_tests_src = $(app_calculation_test_src) $(app_probability_test_src) $(app_regression_test_src) $(app_sequence_test_src) $(app_shared_test_src) $(app_statistics_test_src) $(app_solver_test_src) +apps_tests_src = $(app_calculation_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/,\ global_preferences.cpp \ ) @@ -110,10 +115,15 @@ $(foreach img,$(image_list), $(eval $(call rule_for, \ # Configure variants apps_all_src = $(app_src) -apps_all_src += $(apps_launch_default_src) $(apps_launch_on_boarding_src +apps_all_src += $(apps_official) $(apps_non_official) +apps_all_src += $(apps_launch_default_src) $(apps_launch_on_boarding_src) apps_all_src += $(apps_prompt_none_src) $(apps_prompt_update_src) $(apps_prompt_beta_src) -apps_default_src = $(app_src) $(apps_launch_default_src) $(apps_prompt_none_src) -apps_onboarding_src = $(app_src) $(apps_launch_on_boarding_src) $(apps_prompt_none_src) -apps_onboarding_update_src = $(app_src) $(apps_launch_on_boarding_src) $(apps_prompt_update_src) -apps_onboarding_beta_src = $(app_src) $(apps_launch_on_boarding_src) $(apps_prompt_beta_src) +apps_default_src = $(app_src) $(apps_non_official) $(apps_launch_default_src) $(apps_prompt_none_src) +apps_official_default_src = $(app_src) $(apps_official) $(apps_launch_default_src) $(apps_prompt_none_src) +apps_onboarding_src = $(app_src) $(apps_non_official) $(apps_launch_on_boarding_src) $(apps_prompt_none_src) +apps_official_onboarding_src = $(app_src) $(apps_official) $(apps_launch_on_boarding_src) $(apps_prompt_none_src) +apps_onboarding_update_src = $(app_src) $(apps_non_official) $(apps_launch_on_boarding_src) $(apps_prompt_update_src) +apps_official_onboarding_update_src = $(app_src) $(apps_official) $(apps_launch_on_boarding_src) $(apps_prompt_update_src) +apps_onboarding_beta_src = $(app_src) $(apps_non_official) $(apps_launch_on_boarding_src) $(apps_prompt_beta_src) +apps_official_onboarding_beta_src = $(app_src) $(apps_official) $(apps_launch_on_boarding_src) $(apps_prompt_beta_src) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index 36bc16e6f..6b48bed7d 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -1,6 +1,7 @@ #include "apps_container.h" #include "apps_container_storage.h" #include "global_preferences.h" +#include "exam_mode_configuration.h" #include #include #include @@ -35,7 +36,7 @@ AppsContainer::AppsContainer() : m_hardwareTestSnapshot(), m_usbConnectedSnapshot() { - m_emptyBatteryWindow.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height)); + m_emptyBatteryWindow.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height), false); #if __EMSCRIPTEN__ /* AppsContainer::poincareCircuitBreaker uses Ion::Keyboard::scan(), which * calls emscripten_sleep. If we set the poincare circuit breaker, we would @@ -246,7 +247,7 @@ bool AppsContainer::switchTo(App::Snapshot * snapshot) { void AppsContainer::run() { KDRect screenRect = KDRect(0, 0, Ion::Display::Width, Ion::Display::Height); - window()->setFrame(screenRect); + window()->setFrame(screenRect, false); /* We push a white screen here, because fetching the exam mode takes some time * and it is visible when reflashing a N0100 (there is some noise on the * screen before the logo appears). */ @@ -363,6 +364,7 @@ void AppsContainer::redrawWindow(bool force) { } void AppsContainer::activateExamMode(GlobalPreferences::ExamMode examMode) { +<<<<<<< HEAD assert(examMode == GlobalPreferences::ExamMode::Standard || examMode == GlobalPreferences::ExamMode::Dutch || examMode == GlobalPreferences::ExamMode::NoSym); reset(); Preferences * preferences = Preferences::sharedPreferences(); @@ -397,6 +399,11 @@ void AppsContainer::activateExamMode(GlobalPreferences::ExamMode examMode) { * confusing states when the battery is charging and states when the Dutch * exam mode is on. */ // Ion::LED::setColor(examMode == GlobalPreferences::ExamMode::Dutch ? KDColorYellow : KDColorRed); +======= + assert(examMode != GlobalPreferences::ExamMode::Off && examMode != GlobalPreferences::ExamMode::Unknown); + reset(); + Ion::LED::setColor(ExamModeConfiguration::examModeColor(examMode)); +>>>>>>> upstream/master Ion::LED::setBlinking(1000, 0.1f); } diff --git a/apps/apps_window.cpp b/apps/apps_window.cpp index f3457f515..68a150133 100644 --- a/apps/apps_window.cpp +++ b/apps/apps_window.cpp @@ -58,11 +58,11 @@ View * AppsWindow::subviewAtIndex(int index) { return m_contentView; } -void AppsWindow::layoutSubviews() { +void AppsWindow::layoutSubviews(bool force) { KDCoordinate titleHeight = m_hideTitleBarView ? 0 : Metric::TitleBarHeight; - m_titleBarView.setFrame(KDRect(0, 0, bounds().width(), titleHeight)); + m_titleBarView.setFrame(KDRect(0, 0, bounds().width(), titleHeight), force); if (m_contentView != nullptr) { - m_contentView->setFrame(KDRect(0, titleHeight, bounds().width(), bounds().height()-titleHeight)); + m_contentView->setFrame(KDRect(0, titleHeight, bounds().width(), bounds().height()-titleHeight), force); } } diff --git a/apps/apps_window.h b/apps/apps_window.h index 093d63d74..08f1bb651 100644 --- a/apps/apps_window.h +++ b/apps/apps_window.h @@ -17,7 +17,7 @@ public: void hideTitleBarView(bool hide); private: int numberOfSubviews() const override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; View * subviewAtIndex(int index) override; TitleBarView m_titleBarView; bool m_hideTitleBarView; diff --git a/apps/calculation/Makefile b/apps/calculation/Makefile index e48925422..078fb317e 100644 --- a/apps/calculation/Makefile +++ b/apps/calculation/Makefile @@ -7,12 +7,25 @@ app_calculation_test_src += $(addprefix apps/calculation/,\ ) app_calculation_src = $(addprefix apps/calculation/,\ + additional_outputs/complex_graph_cell.cpp \ + additional_outputs/complex_model.cpp \ + additional_outputs/complex_list_controller.cpp \ + additional_outputs/expression_with_equal_sign_view.cpp \ + additional_outputs/expressions_list_controller.cpp \ + additional_outputs/illustrated_list_controller.cpp \ + additional_outputs/illustration_cell.cpp \ + additional_outputs/integer_list_controller.cpp \ + additional_outputs/scrollable_three_expressions_cell.cpp \ + additional_outputs/list_controller.cpp \ + additional_outputs/rational_list_controller.cpp \ + additional_outputs/trigonometry_graph_cell.cpp \ + additional_outputs/trigonometry_list_controller.cpp \ + additional_outputs/trigonometry_model.cpp \ app.cpp \ edit_expression_controller.cpp \ expression_field.cpp \ history_view_cell.cpp \ history_controller.cpp \ - scrollable_expression_view.cpp \ selectable_table_view.cpp \ ) diff --git a/apps/calculation/additional_outputs/complex_graph_cell.cpp b/apps/calculation/additional_outputs/complex_graph_cell.cpp new file mode 100644 index 000000000..aabe9b630 --- /dev/null +++ b/apps/calculation/additional_outputs/complex_graph_cell.cpp @@ -0,0 +1,98 @@ +#include "complex_graph_cell.h" + +using namespace Shared; +using namespace Poincare; + +namespace Calculation { + +ComplexGraphView::ComplexGraphView(ComplexModel * complexModel) : + CurveView(complexModel), + m_complex(complexModel) +{ +} + +void ComplexGraphView::drawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(rect, KDColorWhite); + + // Draw grid, axes and graduations + drawGrid(ctx, rect); + drawAxes(ctx, rect); + drawLabelsAndGraduations(ctx, rect, Axis::Vertical, true); + drawLabelsAndGraduations(ctx, rect, Axis::Horizontal, true); + + float real = m_complex->real(); + float imag = m_complex->imag(); + + assert(!std::isnan(real) && !std::isnan(imag) && !std::isinf(real) && !std::isinf(imag)); + /* Draw the segment from the origin to the dot (real, imag) of equation + * x(t) = t*real and y(t) = t*imag with t in [0,1] */ + drawCurve(ctx, rect, 0.0f, 1.0f, 0.01f, + [](float t, void * model, void * context) { + ComplexModel * complexModel = (ComplexModel *)model; + return Poincare::Coordinate2D(complexModel->real()*t, complexModel->imag()*t); + }, m_complex, nullptr, false, Palette::GreyDark, false); + + /* Draw the partial ellipse indicating the angle θ + * - the ellipse parameters are a = |real|/5 and b = |imag|/5, + * - the parametric ellipse equation is x(t) = a*cos(th*t) and y(t) = b*sin(th*t) + * with th computed in order to be the intersection of the line forming an + * angle θ with the abscissa and the ellipsis + * - we draw the ellipse for t in [0,1] to represent it from the abscissa axis + * to the phase of the complex + */ + /* Compute th: th is the intersection of ellipsis of equation (a*cos(t), b*sin(t)) + * and the line of equation (real*t,imag*t). + * (a*cos(t), b*sin(t)) = (real*t,imag*t) --> tan(t) = sign(a)*sign(b) (± π) + * --> t = π/4 [π/2] according to sign(a) and sign(b). */ + float th = real < 0.0f ? 3.0f*M_PI/4.0f : M_PI/4.0f; + th = imag < 0.0f ? -th : th; + // Compute ellipsis parameters a and b + float factor = 5.0f; + float a = std::fabs(real)/factor; + float b = std::fabs(imag)/factor; + // Avoid flat ellipsis for edge cases (for real = 0, the case imag = 0 is excluded) + if (real == 0.0f) { + a = 1.0f/factor; + th = imag < 0.0f ? -M_PI/2.0f : M_PI/2.0f; + } + std::complex parameters(a,b); + drawCurve(ctx, rect, 0.0f, 1.0f, 0.01f, + [](float t, void * model, void * context) { + std::complex parameters = *(std::complex *)model; + float th = *(float *)context; + float a = parameters.real(); + float b = parameters.imag(); + return Poincare::Coordinate2D(a*std::cos(t*th), b*std::sin(t*th)); + }, ¶meters, &th, false, Palette::GreyDark, false); + + // Draw dashed segment to indicate real and imaginary + drawSegment(ctx, rect, Axis::Vertical, real, 0.0f, imag, Palette::Red, 1, 3); + drawSegment(ctx, rect, Axis::Horizontal, imag, 0.0f, real, Palette::Red, 1, 3); + + // Draw complex position on the plan + drawDot(ctx, rect, real, imag, Palette::Red, Size::Large); + + // Draw labels + // 're(z)' label + drawLabel(ctx, rect, real, 0.0f, "re(z)", Palette::Red, CurveView::RelativePosition::None, imag >= 0.0f ? CurveView::RelativePosition::Before : CurveView::RelativePosition::After); + // 'im(z)' label + drawLabel(ctx, rect, 0.0f, imag, "im(θ)", Palette::Red, real >= 0.0f ? CurveView::RelativePosition::Before : CurveView::RelativePosition::After, CurveView::RelativePosition::None); + // '|z|' label, the relative horizontal position of this label depends on the quadrant + CurveView::RelativePosition verticalPosition = real*imag < 0.0f ? CurveView::RelativePosition::Before : CurveView::RelativePosition::After; + if (real == 0.0f) { + // Edge case: pure imaginary + verticalPosition = CurveView::RelativePosition::None; + } + drawLabel(ctx, rect, real/2.0f, imag/2.0f, "|z|", Palette::Red, CurveView::RelativePosition::None, verticalPosition); + // 'arg(z)' label, the absolute and relative horizontal/vertical positions of this label depends on the quadrant + CurveView::RelativePosition horizontalPosition = real >= 0.0f ? CurveView::RelativePosition::After : CurveView::RelativePosition::None; + verticalPosition = imag >= 0.0f ? CurveView::RelativePosition::After : CurveView::RelativePosition::Before; + /* anglePositionRatio is the ratio of the angle where we position the label + * For the right half plan, we position the label close to the abscissa axis + * and for the left half plan, we position the label at the half angle. The + * relative position is chosen accordingly. */ + float anglePositionRatio = real >= 0.0f ? 0.0f : 0.5f; + drawLabel(ctx, rect, a*std::cos(anglePositionRatio*th), b*std::sin(anglePositionRatio*th), "arg(z)", Palette::Red, horizontalPosition, verticalPosition); +} + +} diff --git a/apps/calculation/additional_outputs/complex_graph_cell.h b/apps/calculation/additional_outputs/complex_graph_cell.h new file mode 100644 index 000000000..bf440202f --- /dev/null +++ b/apps/calculation/additional_outputs/complex_graph_cell.h @@ -0,0 +1,37 @@ +#ifndef CALCULATION_ADDITIONAL_OUTPUTS_COMPLEX_GRAPH_CELL_H +#define CALCULATION_ADDITIONAL_OUTPUTS_COMPLEX_GRAPH_CELL_H + +#include "../../shared/curve_view.h" +#include "complex_model.h" +#include "illustration_cell.h" + +namespace Calculation { + +class ComplexGraphView : public Shared::CurveView { +public: + ComplexGraphView(ComplexModel * complexModel); + void drawRect(KDContext * ctx, KDRect rect) const override; +private: + char * label(Axis axis, int index) const override { + return (axis == Axis::Horizontal ? (char *)m_xLabels[index] : (char *)m_yLabels[index]); + } + // '-' + significant digits + ".E-" + 2 digits (the represented dot is a float, so it is bounded by 1E38 and 1E-38 + size_t labelMaxGlyphLengthSize() const override { return 1 + Poincare::Preferences::VeryShortNumberOfSignificantDigits + 3 + 2; } + char m_xLabels[k_maxNumberOfXLabels][k_labelBufferMaxSize]; + char m_yLabels[k_maxNumberOfYLabels][k_labelBufferMaxSize]; + ComplexModel * m_complex; +}; + +class ComplexGraphCell : public IllustrationCell { +public: + ComplexGraphCell(ComplexModel * complexModel) : m_view(complexModel) {} + void reload() { m_view.reload(); } +private: + View * view() override { return &m_view; } + ComplexGraphView m_view; +}; + +} + +#endif + diff --git a/apps/calculation/additional_outputs/complex_list_controller.cpp b/apps/calculation/additional_outputs/complex_list_controller.cpp new file mode 100644 index 000000000..9bfd3e960 --- /dev/null +++ b/apps/calculation/additional_outputs/complex_list_controller.cpp @@ -0,0 +1,44 @@ +#include "complex_list_controller.h" +#include "../app.h" +#include "../../shared/poincare_helpers.h" +#include +#include +#include "complex_list_controller.h" + +using namespace Poincare; +using namespace Shared; + +namespace Calculation { + +void ComplexListController::viewWillAppear() { + IllustratedListController::viewWillAppear(); + m_complexGraphCell.reload(); // compute labels +} + +void ComplexListController::setExpression(Poincare::Expression e) { + IllustratedListController::setExpression(e); + + Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); + Poincare::Preferences::ComplexFormat currentComplexFormat = preferences->complexFormat(); + if (currentComplexFormat == Poincare::Preferences::ComplexFormat::Real) { + // Temporary change complex format to avoid all additional expressions to be "unreal" + preferences->setComplexFormat(Poincare::Preferences::ComplexFormat::Cartesian); + } + Poincare::Context * context = App::app()->localContext(); + // Fill Calculation Store + m_calculationStore.push("im(z)", context); + m_calculationStore.push("re(z)", context); + m_calculationStore.push("arg(z)", context); + m_calculationStore.push("abs(z)", context); + + // Set Complex illustration + // Compute a and b as in Expression::hasDefinedComplexApproximation to ensure the same defined result + float a = Shared::PoincareHelpers::ApproximateToScalar(RealPart::Builder(e.clone()), context); + float b = Shared::PoincareHelpers::ApproximateToScalar(ImaginaryPart::Builder(e.clone()), context); + m_model.setComplex(std::complex(a,b)); + + // Reset complex format as before + preferences->setComplexFormat(currentComplexFormat); +} + +} diff --git a/apps/calculation/additional_outputs/complex_list_controller.h b/apps/calculation/additional_outputs/complex_list_controller.h new file mode 100644 index 000000000..1c7d2a654 --- /dev/null +++ b/apps/calculation/additional_outputs/complex_list_controller.h @@ -0,0 +1,30 @@ +#ifndef CALCULATION_ADDITIONAL_OUTPUTS_COMPLEX_LIST_CONTROLLER_H +#define CALCULATION_ADDITIONAL_OUTPUTS_COMPLEX_LIST_CONTROLLER_H + +#include "complex_graph_cell.h" +#include "complex_model.h" +#include "illustrated_list_controller.h" + +namespace Calculation { + +class ComplexListController : public IllustratedListController { +public: + ComplexListController(EditExpressionController * editExpressionController) : + IllustratedListController(nullptr, editExpressionController), + m_complexGraphCell(&m_model) {} + + // ViewController + void viewWillAppear() override; + + void setExpression(Poincare::Expression e) override; +private: + CodePoint expressionSymbol() const override { return 'z'; } + HighlightCell * illustrationCell() override { return &m_complexGraphCell; } + ComplexGraphCell m_complexGraphCell; + ComplexModel m_model; +}; + +} + +#endif + diff --git a/apps/calculation/additional_outputs/complex_model.cpp b/apps/calculation/additional_outputs/complex_model.cpp new file mode 100644 index 000000000..6d59f3810 --- /dev/null +++ b/apps/calculation/additional_outputs/complex_model.cpp @@ -0,0 +1,43 @@ +#include "complex_model.h" + +namespace Calculation { + +ComplexModel::ComplexModel(std::complex c) : + Shared::CurveViewRange(), + std::complex(c) +{ +} + +float ComplexModel::rangeBound(float direction, bool horizontal) const { + float minFactor = k_minVerticalMarginFactor; + float maxFactor = k_maxVerticalMarginFactor; + float value = imag(); + if (horizontal) { + minFactor = k_minHorizontalMarginFactor; + 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; + return factor*value; +} + +float ComplexModel::xMin() const { + return rangeBound(-1.0f, true); +} + +float ComplexModel::xMax() const { + return rangeBound(1.0f, true); +} + +float ComplexModel::yMin() const { + return rangeBound(-1.0f, false); +} + +float ComplexModel::yMax() const { + return rangeBound(1.0f, false); +} + +} diff --git a/apps/calculation/additional_outputs/complex_model.h b/apps/calculation/additional_outputs/complex_model.h new file mode 100644 index 000000000..827418c1e --- /dev/null +++ b/apps/calculation/additional_outputs/complex_model.h @@ -0,0 +1,32 @@ +#ifndef CALCULATION_ADDITIONAL_OUTPUTS_COMPLEX_MODEL_H +#define CALCULATION_ADDITIONAL_OUTPUTS_COMPLEX_MODEL_H + +#include "../../shared/curve_view_range.h" +#include + +namespace Calculation { + +class ComplexModel : public Shared::CurveViewRange, public std::complex { +public: + ComplexModel(std::complex c = std::complex(NAN, NAN)); + // CurveViewRange + float xMin() const override; + float xMax() const override; + float yMin() const override; + float yMax() const override; + + void setComplex(std::complex c) { *this = ComplexModel(c); } + + + static constexpr float k_minVerticalMarginFactor = -0.5f; + static constexpr float k_maxVerticalMarginFactor = 1.2f; + static constexpr float k_minHorizontalMarginFactor = -1.0f; + static constexpr float k_maxHorizontalMarginFactor = 2.0f; + +private: + float rangeBound(float direction, bool horizontal) const; +}; + +} + +#endif diff --git a/apps/calculation/additional_outputs/expression_with_equal_sign_view.cpp b/apps/calculation/additional_outputs/expression_with_equal_sign_view.cpp new file mode 100644 index 000000000..2e480bb39 --- /dev/null +++ b/apps/calculation/additional_outputs/expression_with_equal_sign_view.cpp @@ -0,0 +1,33 @@ +#include "expression_with_equal_sign_view.h" + +namespace Calculation { + +KDSize ExpressionWithEqualSignView::minimalSizeForOptimalDisplay() const { + KDSize expressionSize = ExpressionView::minimalSizeForOptimalDisplay(); + KDSize equalSize = m_equalSign.minimalSizeForOptimalDisplay(); + return KDSize(expressionSize.width() + equalSize.width() + Metric::CommonLargeMargin, expressionSize.height()); +} + +void ExpressionWithEqualSignView::drawRect(KDContext * ctx, KDRect rect) const { + if (m_layout.isUninitialized()) { + return; + } + // Do not color the whole background to avoid coloring behind the equal symbol + KDSize expressionSize = ExpressionView::minimalSizeForOptimalDisplay(); + ctx->fillRect(KDRect(0, 0, expressionSize), m_backgroundColor); + m_layout.draw(ctx, drawingOrigin(), m_textColor, m_backgroundColor, m_selectionStart, m_selectionEnd, Palette::Select); +} + +View * ExpressionWithEqualSignView::subviewAtIndex(int index) { + assert(index == 0); + return &m_equalSign; +} + +void ExpressionWithEqualSignView::layoutSubviews(bool force) { + KDSize expressionSize = ExpressionView::minimalSizeForOptimalDisplay(); + KDSize equalSize = m_equalSign.minimalSizeForOptimalDisplay(); + KDCoordinate expressionBaseline = layout().baseline(); + m_equalSign.setFrame(KDRect(expressionSize.width() + Metric::CommonLargeMargin, expressionBaseline - equalSize.height()/2, equalSize), force); +} + +} diff --git a/apps/calculation/additional_outputs/expression_with_equal_sign_view.h b/apps/calculation/additional_outputs/expression_with_equal_sign_view.h new file mode 100644 index 000000000..865a7393f --- /dev/null +++ b/apps/calculation/additional_outputs/expression_with_equal_sign_view.h @@ -0,0 +1,26 @@ +#ifndef CALCULATION_EXPRESSION_WITH_EQUAL_SIGN_VIEW_H +#define CALCULATION_EXPRESSION_WITH_EQUAL_SIGN_VIEW_H + +#include +#include +#include + +namespace Calculation { + +class ExpressionWithEqualSignView : public ExpressionView { +public: + ExpressionWithEqualSignView() : + m_equalSign(KDFont::LargeFont, I18n::Message::Equal, 0.5f, 0.5f, KDColorBlack) + {} + KDSize minimalSizeForOptimalDisplay() const override; + void drawRect(KDContext * ctx, KDRect rect) const override; +private: + View * subviewAtIndex(int index) override; + void layoutSubviews(bool force = false) override; + int numberOfSubviews() const override { return 1; } + MessageTextView m_equalSign; +}; + +} + +#endif diff --git a/apps/calculation/additional_outputs/expressions_list_controller.cpp b/apps/calculation/additional_outputs/expressions_list_controller.cpp new file mode 100644 index 000000000..515ec3b4a --- /dev/null +++ b/apps/calculation/additional_outputs/expressions_list_controller.cpp @@ -0,0 +1,66 @@ +#include "expressions_list_controller.h" +#include "../app.h" + +using namespace Poincare; + +namespace Calculation { + +/* Expressions list controller */ + +ExpressionsListController::ExpressionsListController(Responder * parentResponder, EditExpressionController * editExpressionController) : + ListController(parentResponder, editExpressionController), + m_cells{} +{ + for (int i = 0; i < k_maxNumberOfCells; i++) { + m_cells[i].setParentResponder(m_listController.selectableTableView()); + } +} + +void ExpressionsListController::didEnterResponderChain(Responder * previousFirstResponder) { + selectCellAtLocation(0, 0); +} + +int ExpressionsListController::reusableCellCount(int type) { + return k_maxNumberOfCells; +} + +HighlightCell * ExpressionsListController::reusableCell(int index, int type) { + return &m_cells[index]; +} + +KDCoordinate ExpressionsListController::rowHeight(int j) { + Layout l = layoutAtIndex(j); + if (l.isUninitialized()) { + return 0; + } + return l.layoutSize().height() + 2 * Metric::CommonSmallMargin + Metric::CellSeparatorThickness; +} + +void ExpressionsListController::willDisplayCellForIndex(HighlightCell * cell, int index) { + ExpressionTableCellWithPointer * myCell = static_cast(cell); + myCell->setLayout(layoutAtIndex(index)); + myCell->setAccessoryMessage(messageAtIndex(index)); + myCell->reloadScroll(); +} + +void ExpressionsListController::setExpression(Poincare::Expression e) { + // Reinitialize memoization + for (int i = 0; i < k_maxNumberOfCells; i++) { + m_layouts[i] = Layout(); + } + m_expression = e; +} + +Poincare::Layout ExpressionsListController::layoutAtIndex(int index) { + if (m_layouts[index].isUninitialized()) { + computeLayoutAtIndex(index); + } + assert(!m_layouts[index].isUninitialized()); + return m_layouts[index]; +} + +int ExpressionsListController::textAtIndex(char * buffer, size_t bufferSize, int index) { + return m_layouts[index].serializeParsedExpression(buffer, bufferSize, App::app()->localContext()); +} + +} diff --git a/apps/calculation/additional_outputs/expressions_list_controller.h b/apps/calculation/additional_outputs/expressions_list_controller.h new file mode 100644 index 000000000..64152f6be --- /dev/null +++ b/apps/calculation/additional_outputs/expressions_list_controller.h @@ -0,0 +1,45 @@ +#ifndef CALCULATION_ADDITIONAL_OUTPUTS_EXPRESSIONS_LIST_CONTROLLER_H +#define CALCULATION_ADDITIONAL_OUTPUTS_EXPRESSIONS_LIST_CONTROLLER_H + +#include +#include +#include +#include "list_controller.h" + +namespace Calculation { + +class ExpressionsListController : public ListController { +public: + ExpressionsListController(Responder * parentResponder, EditExpressionController * editExpressionController); + + // Responder + void didEnterResponderChain(Responder * previousFirstResponder) override; + + //ListViewDataSource + int reusableCellCount(int type) override; + HighlightCell * reusableCell(int index, int type) override; + KDCoordinate rowHeight(int j) override; + int typeAtLocation(int i, int j) override { return 0; } + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + + // IllustratedListController + void setExpression(Poincare::Expression e) override; + +protected: + constexpr static int k_maxNumberOfCells = 4; + virtual int textAtIndex(char * buffer, size_t bufferSize, int index) override; + Poincare::Expression m_expression; + // Memoization of layouts + mutable Poincare::Layout m_layouts[k_maxNumberOfCells]; +private: + Poincare::Layout layoutAtIndex(int index); + virtual void computeLayoutAtIndex(int index) = 0; + virtual I18n::Message messageAtIndex(int index) = 0; + // Cells + ExpressionTableCellWithPointer m_cells[k_maxNumberOfCells]; +}; + +} + +#endif + diff --git a/apps/calculation/additional_outputs/illustrated_list_controller.cpp b/apps/calculation/additional_outputs/illustrated_list_controller.cpp new file mode 100644 index 000000000..67bc939e6 --- /dev/null +++ b/apps/calculation/additional_outputs/illustrated_list_controller.cpp @@ -0,0 +1,128 @@ +#include "illustrated_list_controller.h" +#include +#include "../app.h" + +using namespace Poincare; + +namespace Calculation { + +/* Illustrated list controller */ + +IllustratedListController::IllustratedListController(Responder * parentResponder, EditExpressionController * editExpressionController) : + ListController(parentResponder, editExpressionController, this), + m_additionalCalculationCells{} +{ + for (int i = 0; i < k_maxNumberOfAdditionalCalculations; i++) { + m_additionalCalculationCells[i].setParentResponder(m_listController.selectableTableView()); + } +} + +void IllustratedListController::didEnterResponderChain(Responder * previousFirstResponder) { + // Select the left subview on all cells and reinitialize scroll + for (int i = 0; i < k_maxNumberOfAdditionalCalculations; i++) { + m_additionalCalculationCells[i].reinitSelection(); + } + selectCellAtLocation(0, 1); +} + +void IllustratedListController::viewDidDisappear() { + StackViewController::viewDidDisappear(); + // Reset the context as it was before displaying the IllustratedListController + Poincare::Context * context = App::app()->localContext(); + if (m_savedExpression.isUninitialized()) { + /* If no expression was stored in the symbol used by the + * IllustratedListController, we delete the record we stored */ + char symbolName[3]; + size_t length = UTF8Decoder::CodePointToChars(expressionSymbol(), symbolName, 3); + assert(length < 3); + symbolName[length] = 0; + const char * const extensions[2] = {"exp", "func"}; + Ion::Storage::sharedStorage()->recordBaseNamedWithExtensions(symbolName, extensions, 2).destroy(); + } else { + Poincare::Symbol s = Poincare::Symbol::Builder(expressionSymbol()); + context->setExpressionForSymbolAbstract(m_savedExpression, s); + } +} + +int IllustratedListController::numberOfRows() const { + return m_calculationStore.numberOfCalculations() + 1; +} + +int IllustratedListController::reusableCellCount(int type) { + assert(type < 2); + if (type == 0) { + return 1; + } + return k_maxNumberOfAdditionalCalculations; +} + +HighlightCell * IllustratedListController::reusableCell(int index, int type) { + assert(type < 2); + assert(index >= 0); + if (type == 0) { + return illustrationCell(); + } + return &m_additionalCalculationCells[index]; +} + +KDCoordinate IllustratedListController::rowHeight(int j) { + if (j == 0) { + return k_illustrationHeight; + } + int calculationIndex = j-1; + if (calculationIndex >= m_calculationStore.numberOfCalculations()) { + return 0; + } + Shared::ExpiringPointer calculation = m_calculationStore.calculationAtIndex(calculationIndex); + return calculation->height(App::app()->localContext(), true, true) + 2 * Metric::CommonSmallMargin + Metric::CellSeparatorThickness; +} + +int IllustratedListController::typeAtLocation(int i, int j) { + return j == 0 ? 0 : 1; +} + +void IllustratedListController::willDisplayCellForIndex(HighlightCell * cell, int index) { + if (index == 0) { + // TODO ? + return; + } + Poincare::Context * context = App::app()->localContext(); + ScrollableThreeExpressionsCell * myCell = (ScrollableThreeExpressionsCell *)cell; + Calculation * c = m_calculationStore.calculationAtIndex(index-1).pointer(); + myCell->setCalculation(c); + myCell->setDisplayCenter(c->displayOutput(context) != Calculation::DisplayOutput::ApproximateOnly); + //myCell->setHighlighted(myCell->isHighlighted()); //TODO?? +} + +void IllustratedListController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { + if (withinTemporarySelection) { + return; + } + // Forbid selecting Illustration cell + if (t->selectedRow() == 0) { + t->selectCellAtLocation(0, 1); + } + /* But scroll to the top when we select the first + * ScrollableThreeExpressionsCell in order display the + * illustration cell. */ + if (t->selectedRow() == 1) { + t->scrollToCell(0, 0); + } +} + +void IllustratedListController::setExpression(Poincare::Expression e) { + m_calculationStore.deleteAll(); + Poincare::Context * context = App::app()->localContext(); + Poincare::Symbol s = Poincare::Symbol::Builder(expressionSymbol()); + m_savedExpression = context->expressionForSymbolAbstract(s, false); + context->setExpressionForSymbolAbstract(e, s); +} + +int IllustratedListController::textAtIndex(char * buffer, size_t bufferSize, int index) { + ScrollableThreeExpressionsCell * myCell = static_cast(m_listController.selectableTableView()->selectedCell()); + Shared::ExpiringPointer c = m_calculationStore.calculationAtIndex(index-1); + const char * text = myCell->selectedSubviewPosition() == ScrollableThreeExpressionsView::SubviewPosition::Right ? c->approximateOutputText(Calculation::NumberOfSignificantDigits::Maximal) : c->exactOutputText(); + return strlcpy(buffer, text, bufferSize); +} + +} diff --git a/apps/calculation/additional_outputs/illustrated_list_controller.h b/apps/calculation/additional_outputs/illustrated_list_controller.h new file mode 100644 index 000000000..fdac26ddd --- /dev/null +++ b/apps/calculation/additional_outputs/illustrated_list_controller.h @@ -0,0 +1,50 @@ +#ifndef CALCULATION_ADDITIONAL_OUTPUTS_ILLUSTRATED_LIST_CONTROLLER_H +#define CALCULATION_ADDITIONAL_OUTPUTS_ILLUSTRATED_LIST_CONTROLLER_H + +#include +#include "scrollable_three_expressions_cell.h" +#include "list_controller.h" +#include "../calculation_store.h" +#include + +namespace Calculation { + +class IllustratedListController : public ListController, public SelectableTableViewDelegate { +public: + IllustratedListController(Responder * parentResponder, EditExpressionController * editExpressionController); + + // Responder + void viewDidDisappear() override; + void didEnterResponderChain(Responder * previousFirstResponder) override; + + //ListViewDataSource + int numberOfRows() const override; + int reusableCellCount(int type) override; + HighlightCell * reusableCell(int index, int type) override; + KDCoordinate rowHeight(int j) override; + int typeAtLocation(int i, int j) override; + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + + // SelectableTableViewDelegate + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; + + // IllustratedListController + void setExpression(Poincare::Expression e) override; + + constexpr static KDCoordinate k_illustrationHeight = 100; +protected: + Poincare::Expression m_savedExpression; + CalculationStore m_calculationStore; +private: + int textAtIndex(char * buffer, size_t bufferSize, int index) override; + virtual CodePoint expressionSymbol() const = 0; + constexpr static int k_maxNumberOfAdditionalCalculations = 4; + // Cells + virtual HighlightCell * illustrationCell() = 0; + ScrollableThreeExpressionsCell m_additionalCalculationCells[k_maxNumberOfAdditionalCalculations]; +}; + +} + +#endif + diff --git a/apps/calculation/additional_outputs/illustration_cell.cpp b/apps/calculation/additional_outputs/illustration_cell.cpp new file mode 100644 index 000000000..bd2471710 --- /dev/null +++ b/apps/calculation/additional_outputs/illustration_cell.cpp @@ -0,0 +1,16 @@ +#include "illustration_cell.h" + +using namespace Shared; +using namespace Poincare; + +namespace Calculation { + +void IllustrationCell::layoutSubviews(bool force) { + view()->setFrame(KDRect(Metric::CellSeparatorThickness, Metric::CellSeparatorThickness, bounds().width() - 2*Metric::CellSeparatorThickness, bounds().height() - 2*Metric::CellSeparatorThickness), force); +} + +void IllustrationCell::drawRect(KDContext * ctx, KDRect rect) const { + drawBorderOfRect(ctx, bounds(), Palette::GreyBright); +} + +} diff --git a/apps/calculation/additional_outputs/illustration_cell.h b/apps/calculation/additional_outputs/illustration_cell.h new file mode 100644 index 000000000..5878053e3 --- /dev/null +++ b/apps/calculation/additional_outputs/illustration_cell.h @@ -0,0 +1,23 @@ +#ifndef CALCULATION_ADDITIONAL_OUTPUTS_ILLUSTRATION_CELL_H +#define CALCULATION_ADDITIONAL_OUTPUTS_ILLUSTRATION_CELL_H + +#include +#include + +namespace Calculation { + +class IllustrationCell : public Bordered, public HighlightCell { +public: + void setHighlighted(bool highlight) override { return; } + void drawRect(KDContext * ctx, KDRect rect) const override; +private: + int numberOfSubviews() const override { return 1; } + View * subviewAtIndex(int index) override { return view(); } + void layoutSubviews(bool force = false) override; + virtual View * view() = 0; +}; + +} + +#endif + diff --git a/apps/calculation/additional_outputs/integer_list_controller.cpp b/apps/calculation/additional_outputs/integer_list_controller.cpp new file mode 100644 index 000000000..1e5bb0cb8 --- /dev/null +++ b/apps/calculation/additional_outputs/integer_list_controller.cpp @@ -0,0 +1,70 @@ +#include "integer_list_controller.h" +#include +#include +#include +#include +#include "../app.h" +#include "../../shared/poincare_helpers.h" + +using namespace Poincare; +using namespace Shared; + +namespace Calculation { + +int IntegerListController::numberOfRows() const { + return 3 + factorExpressionIsComputable(); +} + +Integer::Base baseAtIndex(int index) { + switch (index) { + case 0: + return Integer::Base::Decimal; + case 1: + return Integer::Base::Hexadecimal; + default: + assert(index == 2); + return Integer::Base::Binary; + } +} + +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); + Integer i = static_cast(m_expression).integer(); + m_layouts[index] = i.createLayout(baseAtIndex(index)); +} + +I18n::Message IntegerListController::messageAtIndex(int index) { + switch (index) { + case 0: + return I18n::Message::DecimalBase; + case 1: + return I18n::Message::HexadecimalBase; + case 2: + return I18n::Message::BinaryBase; + default: + return I18n::Message::PrimeFactors; + } +} + +bool IntegerListController::factorExpressionIsComputable() const { + if (!m_layouts[k_indexOfFactorExpression].isUninitialized()) { + // The factor expression is already memoized + return !m_layouts[k_indexOfFactorExpression].isEmpty(); + } + Poincare::Context * context = App::app()->localContext(); + Expression factor = Factor::Builder(m_expression.clone()); + PoincareHelpers::Simplify(&factor, context, ExpressionNode::ReductionTarget::User); + if (!factor.isUndefined()) { + m_layouts[k_indexOfFactorExpression] = PoincareHelpers::CreateLayout(factor); + return true; + } + m_layouts[k_indexOfFactorExpression] = EmptyLayout::Builder(); + return false; + +} +} diff --git a/apps/calculation/additional_outputs/integer_list_controller.h b/apps/calculation/additional_outputs/integer_list_controller.h new file mode 100644 index 000000000..a0208762d --- /dev/null +++ b/apps/calculation/additional_outputs/integer_list_controller.h @@ -0,0 +1,26 @@ +#ifndef CALCULATION_ADDITIONAL_OUTPUTS_INTEGER_LIST_CONTROLLER_H +#define CALCULATION_ADDITIONAL_OUTPUTS_INTEGER_LIST_CONTROLLER_H + +#include "expressions_list_controller.h" + +namespace Calculation { + +class IntegerListController : public ExpressionsListController { +public: + IntegerListController(EditExpressionController * editExpressionController) : + ExpressionsListController(nullptr, editExpressionController) {} + + //ListViewDataSource + int numberOfRows() const override; +private: + static constexpr int k_indexOfFactorExpression = 3; + void computeLayoutAtIndex(int index) override; + I18n::Message messageAtIndex(int index) override; + bool factorExpressionIsComputable() const; +}; + +} + +#endif + + diff --git a/apps/calculation/additional_outputs/list_controller.cpp b/apps/calculation/additional_outputs/list_controller.cpp new file mode 100644 index 000000000..c709b9f2d --- /dev/null +++ b/apps/calculation/additional_outputs/list_controller.cpp @@ -0,0 +1,47 @@ +#include "list_controller.h" +#include "../edit_expression_controller.h" + +using namespace Poincare; + +namespace Calculation { + +/* Inner List Controller */ + +ListController::InnerListController::InnerListController(ListController * dataSource, SelectableTableViewDelegate * delegate) : + ViewController(dataSource), + m_selectableTableView(this, dataSource, dataSource, delegate) +{ + m_selectableTableView.setMargins(0); + m_selectableTableView.setDecoratorType(ScrollView::Decorator::Type::None); +} + +void ListController::InnerListController::didBecomeFirstResponder() { + m_selectableTableView.reloadData(); +} + +/* List Controller */ + +ListController::ListController(Responder * parentResponder, EditExpressionController * editExpressionController, SelectableTableViewDelegate * delegate) : + StackViewController(parentResponder, &m_listController, KDColorWhite, Palette::PurpleBright, Palette::PurpleDark), + m_listController(this, delegate), + m_editExpressionController(editExpressionController) +{ +} + +bool ListController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::OK || event == Ion::Events::EXE) { + char buffer[Constant::MaxSerializedExpressionSize]; + textAtIndex(buffer, Constant::MaxSerializedExpressionSize, selectedRow()); + m_editExpressionController->insertTextBody(buffer); + Container::activeApp()->dismissModalViewController(); + Container::activeApp()->setFirstResponder(m_editExpressionController); + return true; + } + return false; +} + +void ListController::didBecomeFirstResponder() { + Container::activeApp()->setFirstResponder(&m_listController); +} + +} diff --git a/apps/calculation/additional_outputs/list_controller.h b/apps/calculation/additional_outputs/list_controller.h new file mode 100644 index 000000000..86f24b227 --- /dev/null +++ b/apps/calculation/additional_outputs/list_controller.h @@ -0,0 +1,41 @@ +#ifndef CALCULATION_ADDITIONAL_OUTPUTS_LIST_CONTROLLER_H +#define CALCULATION_ADDITIONAL_OUTPUTS_LIST_CONTROLLER_H + +#include +#include + +namespace Calculation { + +class EditExpressionController; + +class ListController : public StackViewController, public ListViewDataSource, public SelectableTableViewDataSource { +public: + ListController(Responder * parentResponder, EditExpressionController * editExpressionController, SelectableTableViewDelegate * delegate = nullptr); + + // Responder + bool handleEvent(Ion::Events::Event event) override; + void didBecomeFirstResponder() override; + + // ListController + virtual void setExpression(Poincare::Expression e) = 0; + +protected: + class InnerListController : public ViewController { + public: + InnerListController(ListController * dataSource, SelectableTableViewDelegate * delegate = nullptr); + const char * title() override { return I18n::translate(I18n::Message::AdditionalResults); } + View * view() override { return &m_selectableTableView; } + void didBecomeFirstResponder() override; + SelectableTableView * selectableTableView() { return &m_selectableTableView; } + private: + SelectableTableView m_selectableTableView; + }; + virtual int textAtIndex(char * buffer, size_t bufferSize, int index) = 0; + InnerListController m_listController; + EditExpressionController * m_editExpressionController; +}; + +} + +#endif + diff --git a/apps/calculation/additional_outputs/rational_list_controller.cpp b/apps/calculation/additional_outputs/rational_list_controller.cpp new file mode 100644 index 000000000..3fc762954 --- /dev/null +++ b/apps/calculation/additional_outputs/rational_list_controller.cpp @@ -0,0 +1,63 @@ +#include "rational_list_controller.h" +#include "../app.h" +#include "../../shared/poincare_helpers.h" +#include +#include + +using namespace Poincare; +using namespace Shared; + +namespace Calculation { + +int RationalListController::numberOfRows() const { + return 2; +} + +Integer extractInteger(const Expression e) { + assert(e.type() == ExpressionNode::Type::BasedInteger); + return static_cast(e).integer(); +} + +void RationalListController::computeLayoutAtIndex(int index) { + bool negative = false; + Expression div = m_expression; + if (m_expression.type() == ExpressionNode::Type::Opposite) { + negative = true; + div = m_expression.childAtIndex(0); + } + assert(div.type() == ExpressionNode::Type::Division); + Integer numerator = extractInteger(div.childAtIndex(0)); + numerator.setNegative(negative); + Integer denominator = extractInteger(div.childAtIndex(1)); + Expression e; + if (index == 0) { + e = Integer::CreateMixedFraction(numerator, denominator); + } else { + assert(index == 1); + e = Integer::CreateEuclideanDivision(numerator, denominator); + } + m_layouts[index] = PoincareHelpers::CreateLayout(e); +} + +I18n::Message RationalListController::messageAtIndex(int index) { + switch (index) { + case 0: + return I18n::Message::MixedFraction; + default: + return I18n::Message::EuclideanDivision; + } +} + +int RationalListController::textAtIndex(char * buffer, size_t bufferSize, int index) { + int length = ExpressionsListController::textAtIndex(buffer, bufferSize, index); + if (index == 1) { + // Get rid of the left part of the equality + char * equalPosition = strchr(buffer, '='); + assert(equalPosition != nullptr); + strlcpy(buffer, equalPosition + 1, bufferSize); + return buffer + length - 1 - equalPosition; + } + return length; +} + +} diff --git a/apps/calculation/additional_outputs/rational_list_controller.h b/apps/calculation/additional_outputs/rational_list_controller.h new file mode 100644 index 000000000..fac0f06eb --- /dev/null +++ b/apps/calculation/additional_outputs/rational_list_controller.h @@ -0,0 +1,25 @@ +#ifndef CALCULATION_ADDITIONAL_OUTPUTS_RATIONAL_LIST_CONTROLLER_H +#define CALCULATION_ADDITIONAL_OUTPUTS_RATIONAL_LIST_CONTROLLER_H + +#include "expressions_list_controller.h" + +namespace Calculation { + +class RationalListController : public ExpressionsListController { +public: + RationalListController(EditExpressionController * editExpressionController) : + ExpressionsListController(nullptr, editExpressionController) {} + + //ListViewDataSource + int numberOfRows() const override; +private: + void computeLayoutAtIndex(int index) override; + I18n::Message messageAtIndex(int index) override; + int textAtIndex(char * buffer, size_t bufferSize, int index) override; +}; + +} + +#endif + + diff --git a/apps/calculation/additional_outputs/scrollable_three_expressions_cell.cpp b/apps/calculation/additional_outputs/scrollable_three_expressions_cell.cpp new file mode 100644 index 000000000..e5bc0b540 --- /dev/null +++ b/apps/calculation/additional_outputs/scrollable_three_expressions_cell.cpp @@ -0,0 +1,84 @@ +#include "scrollable_three_expressions_cell.h" +#include +#include "../app.h" + +namespace Calculation { + +void ScrollableThreeExpressionsView::setCalculation(Calculation * calculation) { + Poincare::Context * context = App::app()->localContext(); + + // Clean the layouts to make room in the pool + setLayouts(Poincare::Layout(), Poincare::Layout(), Poincare::Layout()); + + // Create the input layout + Poincare::Layout inputLayout = calculation->createInputLayout(); + + // Create the exact output layout + Poincare::Layout exactOutputLayout = Poincare::Layout(); + if (Calculation::DisplaysExact(calculation->displayOutput(context))) { + bool couldNotCreateExactLayout = false; + exactOutputLayout = calculation->createExactOutputLayout(&couldNotCreateExactLayout); + if (couldNotCreateExactLayout) { + if (calculation->displayOutput(context) == ::Calculation::Calculation::DisplayOutput::ExactOnly) { + Poincare::ExceptionCheckpoint::Raise(); + } else { + calculation->forceDisplayOutput(::Calculation::Calculation::DisplayOutput::ApproximateOnly); + } + } + } + Calculation::DisplayOutput displayOutput = calculation->displayOutput(context); + + // Create the approximate output layout + Poincare::Layout approximateOutputLayout = Poincare::Layout(); + if (displayOutput == Calculation::DisplayOutput::ExactOnly) { + approximateOutputLayout = exactOutputLayout; + } else { + bool couldNotCreateApproximateLayout = false; + approximateOutputLayout = calculation->createApproximateOutputLayout(context, &couldNotCreateApproximateLayout); + if (couldNotCreateApproximateLayout) { + if (calculation->displayOutput(context) == ::Calculation::Calculation::DisplayOutput::ApproximateOnly) { + Poincare::ExceptionCheckpoint::Raise(); + } else { + /* 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); + exactOutputLayout = Poincare::Layout(); + couldNotCreateApproximateLayout = false; + approximateOutputLayout = calculation->createApproximateOutputLayout(context, &couldNotCreateApproximateLayout); + if (couldNotCreateApproximateLayout) { + Poincare::ExceptionCheckpoint::Raise(); + } + } + } + + } + setLayouts(inputLayout, exactOutputLayout, approximateOutputLayout); + I18n::Message equalMessage = calculation->exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal ? I18n::Message::Equal : I18n::Message::AlmostEqual; + setEqualMessage(equalMessage); + + /* The displayed input and outputs have changed. We need to re-layout the cell + * and re-initialize the scroll. */ + layoutSubviews(); +} + +void ScrollableThreeExpressionsCell::didBecomeFirstResponder() { + reinitSelection(); + Container::activeApp()->setFirstResponder(&m_view); +} + +void ScrollableThreeExpressionsCell::reinitSelection() { + m_view.setSelectedSubviewPosition(ScrollableThreeExpressionsView::SubviewPosition::Left); + m_view.reloadScroll(); +} + +void ScrollableThreeExpressionsCell::setCalculation(Calculation * calculation) { + m_view.setCalculation(calculation); + layoutSubviews(); +} + +void ScrollableThreeExpressionsCell::setDisplayCenter(bool display) { + m_view.setDisplayCenter(display); + layoutSubviews(); +} + +} diff --git a/apps/calculation/additional_outputs/scrollable_three_expressions_cell.h b/apps/calculation/additional_outputs/scrollable_three_expressions_cell.h new file mode 100644 index 000000000..6bb8155f1 --- /dev/null +++ b/apps/calculation/additional_outputs/scrollable_three_expressions_cell.h @@ -0,0 +1,67 @@ +#ifndef CALCULATION_SCROLLABLE_THREE_EXPRESSIONS_CELL_H +#define CALCULATION_SCROLLABLE_THREE_EXPRESSIONS_CELL_H + +#include +#include "../../shared/scrollable_multiple_expressions_view.h" +#include "../calculation.h" +#include "expression_with_equal_sign_view.h" + +namespace Calculation { + +class ScrollableThreeExpressionsView : public Shared::AbstractScrollableMultipleExpressionsView { +public: + ScrollableThreeExpressionsView(Responder * parentResponder) : Shared::AbstractScrollableMultipleExpressionsView(parentResponder, &m_contentCell), m_contentCell() { + setMargins(Metric::CommonSmallMargin, Metric::CommonSmallMargin, Metric::CommonSmallMargin, Metric::CommonSmallMargin); // Left Right margins are already added by TableCell + setBackgroundColor(KDColorWhite); + } + void setCalculation(Calculation * calculation); +private: + class ContentCell : public Shared::AbstractScrollableMultipleExpressionsView::ContentCell { + public: + ContentCell() : m_leftExpressionView() {} + KDColor backgroundColor() const override { return KDColorWhite; } + void setEven(bool even) override { return; } + ExpressionView * leftExpressionView() const override { return const_cast(&m_leftExpressionView); } + private: + ExpressionWithEqualSignView m_leftExpressionView; + }; + + ContentCell * contentCell() override { return &m_contentCell; }; + const ContentCell * constContentCell() const override { return &m_contentCell; }; + ContentCell m_contentCell; +}; + +class ScrollableThreeExpressionsCell : public TableCell, public Responder { +public: + ScrollableThreeExpressionsCell() : + Responder(nullptr), + m_view(this) {} + + // Cell + Poincare::Layout layout() const override { return m_view.layout(); } + + // Responder cell + Responder * responder() override { + return this; + } + void didBecomeFirstResponder() override; + + // Table cell + View * labelView() const override { return (View *)&m_view; } + + void setHighlighted(bool highlight) override { m_view.evenOddCell()->setHighlighted(highlight); } + void setCalculation(Calculation * calculation); + void setDisplayCenter(bool display); + ScrollableThreeExpressionsView::SubviewPosition selectedSubviewPosition() { return m_view.selectedSubviewPosition(); } + void setSelectedSubviewPosition(ScrollableThreeExpressionsView::SubviewPosition subviewPosition) { m_view.setSelectedSubviewPosition(subviewPosition); } + + void reinitSelection(); +private: + // Remove label margin added by TableCell because they're already handled by ScrollableThreeExpressionsView + KDCoordinate labelMargin() const override { return 0; } + ScrollableThreeExpressionsView m_view; +}; + +} + +#endif diff --git a/apps/calculation/additional_outputs/trigonometry_graph_cell.cpp b/apps/calculation/additional_outputs/trigonometry_graph_cell.cpp new file mode 100644 index 000000000..41deb62bf --- /dev/null +++ b/apps/calculation/additional_outputs/trigonometry_graph_cell.cpp @@ -0,0 +1,37 @@ +#include "trigonometry_graph_cell.h" + +using namespace Shared; +using namespace Poincare; + +namespace Calculation { + +TrigonometryGraphView::TrigonometryGraphView(TrigonometryModel * model) : + CurveView(model), + m_model(model) +{ +} + +void TrigonometryGraphView::drawRect(KDContext * ctx, KDRect rect) const { + float s = std::sin(m_model->angle()); + float c = std::cos(m_model->angle()); + ctx->fillRect(rect, KDColorWhite); + drawGrid(ctx, rect); + drawAxes(ctx, rect); + // Draw the circle + drawCurve(ctx, rect, 0.0f, 2.0f*M_PI, M_PI/180.0f, [](float t, void * model, void * context) { + return Poincare::Coordinate2D(std::cos(t), std::sin(t)); + }, nullptr, nullptr, true, Palette::GreyDark, false); + // Draw dashed segment to indicate sine and cosine + drawSegment(ctx, rect, Axis::Vertical, c, 0.0f, s, Palette::Red, 1, 3); + drawSegment(ctx, rect, Axis::Horizontal, s, 0.0f, c, Palette::Red, 1, 3); + // Draw angle position on the circle + drawDot(ctx, rect, c, s, Palette::Red, Size::Large); + // Draw graduations + drawLabelsAndGraduations(ctx, rect, Axis::Vertical, false, true); + drawLabelsAndGraduations(ctx, rect, Axis::Horizontal, false, true); + // Draw labels + drawLabel(ctx, rect, 0.0f, s, "sin(θ)", Palette::Red, c >= 0.0f ? CurveView::RelativePosition::Before : CurveView::RelativePosition::After, CurveView::RelativePosition::None); + drawLabel(ctx, rect, c, 0.0f, "cos(θ)", Palette::Red, CurveView::RelativePosition::None, s >= 0.0f ? CurveView::RelativePosition::Before : CurveView::RelativePosition::After); +} + +} diff --git a/apps/calculation/additional_outputs/trigonometry_graph_cell.h b/apps/calculation/additional_outputs/trigonometry_graph_cell.h new file mode 100644 index 000000000..1bf126707 --- /dev/null +++ b/apps/calculation/additional_outputs/trigonometry_graph_cell.h @@ -0,0 +1,30 @@ +#ifndef CALCULATION_ADDITIONAL_OUTPUTS_TRIGONOMETRY_GRAPH_CELL_H +#define CALCULATION_ADDITIONAL_OUTPUTS_TRIGONOMETRY_GRAPH_CELL_H + +#include "../../shared/curve_view.h" +#include "trigonometry_model.h" +#include "illustration_cell.h" + +namespace Calculation { + +class TrigonometryGraphView : public Shared::CurveView { +public: + TrigonometryGraphView(TrigonometryModel * model); + void drawRect(KDContext * ctx, KDRect rect) const override; +private: + char * label(Axis axis, int index) const override { return nullptr; } + TrigonometryModel * m_model; +}; + +class TrigonometryGraphCell : public IllustrationCell { +public: + TrigonometryGraphCell(TrigonometryModel * model) : m_view(model) {} +private: + View * view() override { return &m_view; } + TrigonometryGraphView m_view; +}; + +} + +#endif + diff --git a/apps/calculation/additional_outputs/trigonometry_list_controller.cpp b/apps/calculation/additional_outputs/trigonometry_list_controller.cpp new file mode 100644 index 000000000..a7e0ffa64 --- /dev/null +++ b/apps/calculation/additional_outputs/trigonometry_list_controller.cpp @@ -0,0 +1,23 @@ +#include "trigonometry_list_controller.h" +#include "../app.h" + +using namespace Poincare; + +namespace Calculation { + +void TrigonometryListController::setExpression(Poincare::Expression e) { + assert(e.type() == ExpressionNode::Type::Cosine || e.type() == ExpressionNode::Type::Sine); + IllustratedListController::setExpression(e.childAtIndex(0)); + + // Fill calculation store + Poincare::Context * context = App::app()->localContext(); + m_calculationStore.push("sin(θ)", context); + m_calculationStore.push("cos(θ)", context); + m_calculationStore.push("θ", context); + + // Set trigonometry illustration + float angle = Shared::PoincareHelpers::ApproximateToScalar(m_calculationStore.calculationAtIndex(0)->approximateOutput(context, Calculation::NumberOfSignificantDigits::Maximal), context); + m_model.setAngle(angle); +} + +} diff --git a/apps/calculation/additional_outputs/trigonometry_list_controller.h b/apps/calculation/additional_outputs/trigonometry_list_controller.h new file mode 100644 index 000000000..dfec810d9 --- /dev/null +++ b/apps/calculation/additional_outputs/trigonometry_list_controller.h @@ -0,0 +1,25 @@ +#ifndef CALCULATION_ADDITIONAL_OUTPUTS_TRIGONOMETRY_LIST_CONTROLLER_H +#define CALCULATION_ADDITIONAL_OUTPUTS_TRIGONOMETRY_LIST_CONTROLLER_H + +#include "trigonometry_graph_cell.h" +#include "trigonometry_model.h" +#include "illustrated_list_controller.h" + +namespace Calculation { + +class TrigonometryListController : public IllustratedListController { +public: + TrigonometryListController(EditExpressionController * editExpressionController) : + IllustratedListController(nullptr, editExpressionController), + m_graphCell(&m_model) {} + void setExpression(Poincare::Expression e) override; +private: + CodePoint expressionSymbol() const override { return UCodePointGreekSmallLetterTheta; } + HighlightCell * illustrationCell() override { return &m_graphCell; } + TrigonometryGraphCell m_graphCell; + TrigonometryModel m_model; +}; + +} + +#endif diff --git a/apps/calculation/additional_outputs/trigonometry_model.cpp b/apps/calculation/additional_outputs/trigonometry_model.cpp new file mode 100644 index 000000000..ef7c708c3 --- /dev/null +++ b/apps/calculation/additional_outputs/trigonometry_model.cpp @@ -0,0 +1,11 @@ +#include "trigonometry_model.h" + +namespace Calculation { + +TrigonometryModel::TrigonometryModel() : + Shared::CurveViewRange(), + m_angle(NAN) +{ +} + +} diff --git a/apps/calculation/additional_outputs/trigonometry_model.h b/apps/calculation/additional_outputs/trigonometry_model.h new file mode 100644 index 000000000..cf31fb1f8 --- /dev/null +++ b/apps/calculation/additional_outputs/trigonometry_model.h @@ -0,0 +1,40 @@ +#ifndef CALCULATION_ADDITIONAL_OUTPUTS_TRIGONOMETRY_MODEL_H +#define CALCULATION_ADDITIONAL_OUTPUTS_TRIGONOMETRY_MODEL_H + +#include "../../shared/curve_view_range.h" +#include "illustrated_list_controller.h" +#include +#include + +namespace Calculation { + +class TrigonometryModel : public Shared::CurveViewRange { +public: + TrigonometryModel(); + // CurveViewRange + float xMin() const override { return -k_xHalfRange; } + float xMax() const override { return k_xHalfRange; } + float yMin() const override { return yCenter() - yHalfRange(); } + float yMax() const override { return yCenter() + yHalfRange(); } + + void setAngle(float f) { m_angle = f; } + float angle() const { return m_angle*M_PI/Poincare::Trigonometry::PiInAngleUnit(Poincare::Preferences::sharedPreferences()->angleUnit()); } +private: + constexpr static float k_xHalfRange = 2.1f; + // We center the yRange around the semi-circle where the angle is + float yCenter() const { return std::sin(angle()) >= 0.0f ? 0.5f : -0.5f; } + + /* We want to normalize the displayed trigonometry circle: + * - On the X axis, we display 4.4 units on an available pixel width of + * (Ion::Display::Width - Metric::PopUpRightMargin - Metric::PopUpLeftMargin) + * - On the Y axis, the available pixel height is + * IllustratedListController::k_illustrationHeight + */ + float yHalfRange() const { return IllustratedListController::k_illustrationHeight*k_xHalfRange/(Ion::Display::Width - Metric::PopUpRightMargin - Metric::PopUpLeftMargin); } + + float m_angle; +}; + +} + +#endif diff --git a/apps/calculation/app.cpp b/apps/calculation/app.cpp index 2196d43ab..8947c679e 100644 --- a/apps/calculation/app.cpp +++ b/apps/calculation/app.cpp @@ -66,7 +66,7 @@ bool App::layoutFieldDidReceiveEvent(::LayoutField * layoutField, Ion::Events::E bool App::isAcceptableExpression(const Poincare::Expression expression) { { Expression ansExpression = static_cast(snapshot())->calculationStore()->ansExpression(localContext()); - if (!TextFieldDelegateApp::ExpressionCanBeSerialized(expression, true, ansExpression)) { + if (!TextFieldDelegateApp::ExpressionCanBeSerialized(expression, true, ansExpression, localContext())) { return false; } } diff --git a/apps/calculation/app.h b/apps/calculation/app.h index e22222904..e250d7a5f 100644 --- a/apps/calculation/app.h +++ b/apps/calculation/app.h @@ -34,7 +34,6 @@ public: bool textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) override; bool layoutFieldDidReceiveEvent(::LayoutField * layoutField, Ion::Events::Event event) override; // TextFieldDelegateApp - bool isAcceptableExpression(const Poincare::Expression expression) override; private: App(Snapshot * snapshot); diff --git a/apps/calculation/base.de.i18n b/apps/calculation/base.de.i18n index 252d9bed0..e71cdc958 100644 --- a/apps/calculation/base.de.i18n +++ b/apps/calculation/base.de.i18n @@ -1,2 +1,9 @@ CalculApp = "Berechnung" CalculAppCapital = "BERECHNUNG" +AdditionalResults = "????" +DecimalBase = "????" +HexadecimalBase = "????" +BinaryBase = "????" +PrimeFactors = "????" +MixedFraction = "????" +EuclideanDivision = "????" diff --git a/apps/calculation/base.en.i18n b/apps/calculation/base.en.i18n index cbf97b0d9..399532fb7 100644 --- a/apps/calculation/base.en.i18n +++ b/apps/calculation/base.en.i18n @@ -1,2 +1,9 @@ CalculApp = "Calculation" CalculAppCapital = "CALCULATION" +AdditionalResults = "Additional results" +DecimalBase = "Decimal" +HexadecimalBase = "Hexadecimal" +BinaryBase = "Binary" +PrimeFactors = "Prime factors" +MixedFraction = "Mixed fraction" +EuclideanDivision = "Euclidean division" diff --git a/apps/calculation/base.es.i18n b/apps/calculation/base.es.i18n index 1b4b273ee..36d7a8bdb 100644 --- a/apps/calculation/base.es.i18n +++ b/apps/calculation/base.es.i18n @@ -1,2 +1,9 @@ CalculApp = "Cálculo" CalculAppCapital = "CÁLCULO" +AdditionalResults = "????" +DecimalBase = "????" +HexadecimalBase = "????" +BinaryBase = "????" +PrimeFactors = "????" +MixedFraction = "????" +EuclideanDivision = "????" diff --git a/apps/calculation/base.fr.i18n b/apps/calculation/base.fr.i18n index 53a12449b..ca8e28739 100644 --- a/apps/calculation/base.fr.i18n +++ b/apps/calculation/base.fr.i18n @@ -1,2 +1,9 @@ CalculApp = "Calculs" CalculAppCapital = "CALCULS" +AdditionalResults = "Résultats complémentaires" +DecimalBase = "Décimal" +HexadecimalBase = "Hexadécimal" +BinaryBase = "Binaire" +PrimeFactors = "Facteurs premiers" +MixedFraction = "Fraction mixte" +EuclideanDivision = "Division euclidienne" diff --git a/apps/calculation/base.pt.i18n b/apps/calculation/base.pt.i18n index 1b4b273ee..36d7a8bdb 100644 --- a/apps/calculation/base.pt.i18n +++ b/apps/calculation/base.pt.i18n @@ -1,2 +1,9 @@ CalculApp = "Cálculo" CalculAppCapital = "CÁLCULO" +AdditionalResults = "????" +DecimalBase = "????" +HexadecimalBase = "????" +BinaryBase = "????" +PrimeFactors = "????" +MixedFraction = "????" +EuclideanDivision = "????" diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index 711408b3e..a5fe1e80b 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -1,6 +1,7 @@ #include "calculation.h" #include "../shared/poincare_helpers.h" #include "../global_preferences.h" +#include "../exam_mode_configuration.h" #include #include #include @@ -16,7 +17,8 @@ static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { retur bool Calculation::operator==(const Calculation& c) { return strcmp(inputText(), c.inputText()) == 0 - && strcmp(approximateOutputText(), c.approximateOutputText()) == 0 + && strcmp(approximateOutputText(NumberOfSignificantDigits::Maximal), c.approximateOutputText(NumberOfSignificantDigits::Maximal)) == 0 + && strcmp(approximateOutputText(NumberOfSignificantDigits::UserDefined), c.approximateOutputText(NumberOfSignificantDigits::UserDefined)) == 0 /* Some calculations can make appear trigonometric functions in their * exact output. Their argument will be different with the angle unit * preferences but both input and approximate output will be the same. @@ -28,8 +30,8 @@ bool Calculation::operator==(const Calculation& c) { Calculation * Calculation::next() const { const char * result = reinterpret_cast(this) + sizeof(Calculation); - for (int i = 0; i < 3; i++) { - result = result + strlen(result) + 1; // Pass inputText, exactOutputText, ApproximateOutputText + for (int i = 0; i < k_numberOfExpressions; i++) { + result = result + strlen(result) + 1; // Pass inputText, exactOutputText, ApproximateOutputText x2 } return reinterpret_cast(const_cast(result)); } @@ -41,13 +43,17 @@ void Calculation::tidy() { m_expandedHeight = -1; } -const char * Calculation::approximateOutputText() const { +const char * Calculation::approximateOutputText(NumberOfSignificantDigits numberOfSignificantDigits) const { const char * exactOutput = exactOutputText(); - return exactOutput + strlen(exactOutput) + 1; + const char * approximateOutputTextWithMaxNumberOfDigits = exactOutput + strlen(exactOutput) + 1; + if (numberOfSignificantDigits == NumberOfSignificantDigits::Maximal) { + return approximateOutputTextWithMaxNumberOfDigits; + } + return approximateOutputTextWithMaxNumberOfDigits + strlen(approximateOutputTextWithMaxNumberOfDigits) + 1; } Expression Calculation::input() { - return Expression::Parse(m_inputText); + return Expression::Parse(m_inputText, nullptr); } Expression Calculation::exactOutput() { @@ -55,64 +61,159 @@ Expression Calculation::exactOutput() { * thereby avoid turning cos(Pi/4) into sqrt(2)/2 and displaying * 'sqrt(2)/2 = 0.999906' (which is totally wrong) instead of * 'cos(pi/4) = 0.999906' (which is true in degree). */ - Expression exactOutput = Expression::Parse(exactOutputText()); + Expression exactOutput = Expression::Parse(exactOutputText(), nullptr); assert(!exactOutput.isUninitialized()); return exactOutput; } -Expression Calculation::approximateOutput(Context * context) { - /* To ensure that the expression 'm_output' is a matrix or a complex, we - * call 'evaluate'. */ - Expression exp = Expression::Parse(approximateOutputText()); +Expression Calculation::approximateOutput(Context * context, NumberOfSignificantDigits numberOfSignificantDigits) { + Expression exp = Expression::Parse(approximateOutputText(numberOfSignificantDigits), nullptr); assert(!exp.isUninitialized()); - return PoincareHelpers::Approximate(exp, context); + /* Warning: + * Since quite old versions of Epsilon, the Expression 'exp' was used to be + * approximated again to ensure its content was in the expected form - a + * linear combination of Decimal. + * However, since the approximate output may contain units and that a + * Poincare::Unit approximates to undef, thus it must not be approximated + * anymore. + * We have to keep two serializations of the approximation outputs: + * - one with the maximal significant digits, to be used by 'ans' or when + * handling 'OK' event on the approximation output. + * - one with the displayed number of significant digits that we parse to + * create the displayed layout. If we used the other serialization to + * create the layout, the result of the parsing could be an Integer which + * does not take the number of significant digits into account when creating + * its layout. This would lead to wrong number of significant digits in the + * layout. + * For instance: + * Number of asked significant digits: 7 + * Input: "123456780", Approximate output: "1.234567E8" + * + * |--------------------------------------------------------------------------------------| + * | Number of significant digits | Approximate text | Parse expression | Layout | + * |------------------------------+------------------+---------------------+--------------| + * | Maximal | "123456780" | Integer(123456780) | "123456780" | + * |------------------------------+------------------+---------------------+--------------| + * | User defined | "1.234567E8" | Decimal(1.234567E8) | "1.234567E8" | + * |--------------------------------------------------------------------------------------| + * + */ + return exp; } Layout Calculation::createInputLayout() { return input().createLayout(Preferences::PrintFloatMode::Decimal, PrintFloat::k_numberOfStoredSignificantDigits); } -Layout Calculation::createExactOutputLayout() { - return PoincareHelpers::CreateLayout(exactOutput()); +Layout Calculation::createExactOutputLayout(bool * couldNotCreateExactLayout) { + Poincare::ExceptionCheckpoint ecp; + if (ExceptionRun(ecp)) { + return PoincareHelpers::CreateLayout(exactOutput()); + } else { + *couldNotCreateExactLayout = true; + return Layout(); + } } -Layout Calculation::createApproximateOutputLayout(Context * context) { - return PoincareHelpers::CreateLayout(approximateOutput(context)); +Layout Calculation::createApproximateOutputLayout(Context * context, bool * couldNotCreateApproximateLayout) { + Poincare::ExceptionCheckpoint ecp; + if (ExceptionRun(ecp)) { + return PoincareHelpers::CreateLayout(approximateOutput(context, NumberOfSignificantDigits::UserDefined)); + } else { + *couldNotCreateApproximateLayout = true; + return Layout(); + } } -KDCoordinate Calculation::height(Context * context, bool expanded) { - KDCoordinate result = expanded ? m_expandedHeight : m_height; - if (result < 0) { - DisplayOutput display = displayOutput(context); - Layout inputLayout = createInputLayout(); - KDCoordinate inputHeight = inputLayout.layoutSize().height(); - if (display == DisplayOutput::ExactOnly) { - KDCoordinate exactOutputHeight = createExactOutputLayout().layoutSize().height(); - result = inputHeight+exactOutputHeight; - } else if (display == DisplayOutput::ApproximateOnly || (!expanded && display == DisplayOutput::ExactAndApproximateToggle)) { - KDCoordinate approximateOutputHeight = createApproximateOutputLayout(context).layoutSize().height(); - result = inputHeight+approximateOutputHeight; - } else { - assert(display == DisplayOutput::ExactAndApproximate || (display == DisplayOutput::ExactAndApproximateToggle && expanded)); - Layout approximateLayout = createApproximateOutputLayout(context); - Layout exactLayout = createExactOutputLayout(); - KDCoordinate approximateOutputHeight = approximateLayout.layoutSize().height(); - KDCoordinate exactOutputHeight = exactLayout.layoutSize().height(); - KDCoordinate outputHeight = maxCoordinate(exactLayout.baseline(), approximateLayout.baseline()) + maxCoordinate(exactOutputHeight-exactLayout.baseline(), approximateOutputHeight-approximateLayout.baseline()); - result = inputHeight + outputHeight; +KDCoordinate Calculation::height(Context * context, bool expanded, bool allExpressionsInline) { + KDCoordinate result = expanded ? m_expandedHeight : m_height; + if (result >= 0) { + // Height already computed + return result; + } + + // Get input height + Layout inputLayout = createInputLayout(); + KDCoordinate inputHeight = inputLayout.layoutSize().height(); + KDCoordinate inputBaseline = inputLayout.baseline(); + + // Get exact output height if needed + Poincare::Layout exactLayout; + bool couldNotCreateExactLayout = false; + if (DisplaysExact(displayOutput(context))) { + // Create the exact output layout + exactLayout = createExactOutputLayout(&couldNotCreateExactLayout); + if (couldNotCreateExactLayout) { + if (displayOutput(context) != DisplayOutput::ExactOnly) { + forceDisplayOutput(DisplayOutput::ApproximateOnly); + } else { + /* We should only display the exact result, but we cannot create it + * -> raise an exception. */ + ExceptionCheckpoint::Raise(); + } } - /* For all display output except ExactAndApproximateToggle, the selected - * height and the usual height are identical. We update both heights in - * theses cases. */ - if (display != DisplayOutput::ExactAndApproximateToggle) { - m_height = result; + } + + if (displayOutput(context) == DisplayOutput::ExactOnly) { + KDCoordinate exactOutputHeight = exactLayout.layoutSize().height(); + if (allExpressionsInline) { + KDCoordinate exactOutputBaseline = exactLayout.baseline(); + result = maxCoordinate(inputBaseline, exactOutputBaseline) + maxCoordinate(inputHeight - inputBaseline, exactOutputHeight-exactOutputBaseline); + } else { + result = inputHeight+exactOutputHeight; + } + } else { + bool couldNotCreateApproximateLayout = false; + Layout approximateLayout = createApproximateOutputLayout(context, &couldNotCreateApproximateLayout); + if (couldNotCreateApproximateLayout) { + if (displayOutput(context) == DisplayOutput::ApproximateOnly) { + Poincare::ExceptionCheckpoint::Raise(); + } else { + /* Set the display output to ApproximateOnly, make room in the pool by + * erasing the exact layout, and retry to create the approximate layout */ + forceDisplayOutput(DisplayOutput::ApproximateOnly); + exactLayout = Poincare::Layout(); + couldNotCreateApproximateLayout = false; + approximateLayout = createApproximateOutputLayout(context, &couldNotCreateApproximateLayout); + if (couldNotCreateApproximateLayout) { + Poincare::ExceptionCheckpoint::Raise(); + } + } + } + + KDCoordinate approximateOutputHeight = approximateLayout.layoutSize().height(); + if (displayOutput(context) == DisplayOutput::ApproximateOnly || (!expanded && displayOutput(context) == DisplayOutput::ExactAndApproximateToggle)) { + if (allExpressionsInline) { + KDCoordinate approximateOutputBaseline = approximateLayout.baseline(); + result = maxCoordinate(inputBaseline, approximateOutputBaseline) + maxCoordinate(inputHeight - inputBaseline, approximateOutputHeight-approximateOutputBaseline); + } else { + result = inputHeight+approximateOutputHeight; + } + } else { + assert(displayOutput(context) == DisplayOutput::ExactAndApproximate || (displayOutput(context) == DisplayOutput::ExactAndApproximateToggle && expanded)); + KDCoordinate exactOutputHeight = exactLayout.layoutSize().height(); + KDCoordinate exactOutputBaseline = exactLayout.baseline(); + KDCoordinate approximateOutputBaseline = approximateLayout.baseline(); + if (allExpressionsInline) { + result = maxCoordinate(inputBaseline, maxCoordinate(exactOutputBaseline, approximateOutputBaseline)) + maxCoordinate(inputHeight - inputBaseline, maxCoordinate(exactOutputHeight - exactOutputBaseline, approximateOutputHeight-approximateOutputBaseline)); + } else { + KDCoordinate outputHeight = maxCoordinate(exactOutputBaseline, approximateOutputBaseline) + maxCoordinate(exactOutputHeight-exactOutputBaseline, approximateOutputHeight-approximateOutputBaseline); + result = inputHeight + outputHeight; + } + } + } + + /* For all display outputs except ExactAndApproximateToggle, the selected + * height and the usual height are identical. We update both heights in + * theses cases. */ + if (displayOutput(context) != DisplayOutput::ExactAndApproximateToggle) { + m_height = result; + m_expandedHeight = result; + } else { + if (expanded) { m_expandedHeight = result; } else { - if (expanded) { - m_expandedHeight = result; - } else { - m_height = result; - } + m_height = result; } } return result; @@ -124,15 +225,31 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) { } if (shouldOnlyDisplayExactOutput()) { m_displayOutput = DisplayOutput::ExactOnly; - // Force all results to be ApproximateOnly in Dutch exam mode - } else if (GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::Dutch || + } else if ( + /* If the exact and approximate outputs are equal (with the + * UserDefined number of significant digits), do not display the exact + * output. Indeed, in this case, the layouts are identical. */ + strcmp(exactOutputText(), approximateOutputText(NumberOfSignificantDigits::UserDefined)) == 0 + || + // If the approximate output is 'unreal' or the exact result is 'undef' + strcmp(exactOutputText(), Undefined::Name()) == 0 || + strcmp(approximateOutputText(NumberOfSignificantDigits::Maximal), Unreal::Name()) == 0 + || + /* If the approximate output is 'undef' and the input and exactOutput are + * equal */ + (strcmp(approximateOutputText(NumberOfSignificantDigits::Maximal), Undefined::Name()) == 0 && + strcmp(inputText(), exactOutputText()) == 0) + || + // Force all outputs to be ApproximateOnly if required by the exam mode configuration + ExamModeConfiguration::exactExpressionsAreForbidden(GlobalPreferences::sharedGlobalPreferences()->examMode()) + || + /* If the input contains the following types, we only display the + * approximate output. */ input().recursivelyMatches( [](const Expression e, Context * c) { - constexpr int approximateOnlyTypesCount = 9; - /* If the input contains the following types, we only display the - * approximate output. */ - ExpressionNode::Type approximateOnlyTypes[approximateOnlyTypesCount] = { + ExpressionNode::Type approximateOnlyTypes[] = { ExpressionNode::Type::Random, + ExpressionNode::Type::Unit, ExpressionNode::Type::Round, ExpressionNode::Type::FracPart, ExpressionNode::Type::Integral, @@ -142,27 +259,11 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) { ExpressionNode::Type::ConfidenceInterval, ExpressionNode::Type::PredictionInterval }; - return e.isOfType(approximateOnlyTypes, approximateOnlyTypesCount); - }, context, true)) + return e.isOfType(approximateOnlyTypes, sizeof(approximateOnlyTypes)/sizeof(ExpressionNode::Type)); + }, context, true) + ) { m_displayOutput = DisplayOutput::ApproximateOnly; - } else if (strcmp(exactOutputText(), approximateOutputText()) == 0) { - /* If the exact and approximate results' texts are equal and their layouts - * too, do not display the exact result. If the two layouts are not equal - * because of the number of significant digits, we display both. */ - m_displayOutput = exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal ? DisplayOutput::ApproximateOnly : DisplayOutput::ExactAndApproximate; - } else if (strcmp(exactOutputText(), Undefined::Name()) == 0 - || strcmp(approximateOutputText(), Unreal::Name()) == 0 - || exactOutput().type() == ExpressionNode::Type::Undefined) - { - // If the approximate result is 'unreal' or the exact result is 'undef' - m_displayOutput = DisplayOutput::ApproximateOnly; - } else if (strcmp(approximateOutputText(), Undefined::Name()) == 0 - && strcmp(inputText(), exactOutputText()) == 0) - { - /* If the approximate result is 'undef' and the input and exactOutput are - * equal */ - m_displayOutput = DisplayOutput::ApproximateOnly; } else if (input().recursivelyMatches(Expression::IsApproximate, context) || exactOutput().recursivelyMatches(Expression::IsApproximate, context)) { @@ -196,15 +297,9 @@ Calculation::EqualSign Calculation::exactAndApproximateDisplayedOutputsAreEqual( * Store in the exactOutput. */ Poincare::ExceptionCheckpoint ecp; if (ExceptionRun(ecp)) { - constexpr int bufferSize = Constant::MaxSerializedExpressionSize + 30; - char buffer[bufferSize]; Preferences * preferences = Preferences::sharedPreferences(); - Expression exactOutputExpression = PoincareHelpers::ParseAndSimplify(exactOutputText(), context, false); - if (exactOutputExpression.isUninitialized()) { - exactOutputExpression = Undefined::Builder(); - } Preferences::ComplexFormat complexFormat = Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), m_inputText); - m_equalSign = exactOutputExpression.isEqualToItsApproximationLayout(approximateOutput(context), buffer, bufferSize, complexFormat, preferences->angleUnit(), preferences->displayMode(), preferences->numberOfSignificantDigits(), context) ? EqualSign::Equal : EqualSign::Approximation; + m_equalSign = Expression::ParsedExpressionsAreEqual(exactOutputText(), approximateOutputText(NumberOfSignificantDigits::UserDefined), context, complexFormat, preferences->angleUnit()) ? EqualSign::Equal : EqualSign::Approximation; return m_equalSign; } else { /* Do not override m_equalSign in case there is enough room in the pool @@ -213,4 +308,41 @@ Calculation::EqualSign Calculation::exactAndApproximateDisplayedOutputsAreEqual( } } +Calculation::AdditionalInformationType Calculation::additionalInformationType(Context * context) { + Preferences * preferences = Preferences::sharedPreferences(); + Preferences::ComplexFormat complexFormat = Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), m_inputText); + Expression i = input(); + Expression o = exactOutput(); + /* Special case for Equal and Store: + * Equal/Store nodes have to be at the root of the expression, which prevents + * from creating new expressions with equal/store node as a child. We don't + * return any additional outputs for them to avoid bothering with special + * cases. */ + if (i.type() == ExpressionNode::Type::Equal || i.type() == ExpressionNode::Type::Store) { + return AdditionalInformationType::None; + } + /* Trigonometry additional results are displayed if either input or output is a sin or a cos. Indeed, we want to capture both cases: + * - > input: cos(60) + * > output: 1/2 + * - > input: 2cos(2) - cos(2) + * > output: cos(2) + */ + if (input().isDefinedCosineOrSine(context, complexFormat, preferences->angleUnit()) || o.isDefinedCosineOrSine(context, complexFormat, preferences->angleUnit())) { + return AdditionalInformationType::Trigonometry; + } + + // TODO: return AdditionalInformationType::Unit + if (o.isBasedIntegerCappedBy(k_maximalIntegerWithAdditionalInformation)) { + return AdditionalInformationType::Integer; + } + // Find forms like [12]/[23] or -[12]/[23] + if (o.isDivisionOfIntegers() || (o.type() == ExpressionNode::Type::Opposite && o.childAtIndex(0).isDivisionOfIntegers())) { + return AdditionalInformationType::Rational; + } + if (o.hasDefinedComplexApproximation(context, complexFormat, preferences->angleUnit())) { + return AdditionalInformationType::Complex; + } + return AdditionalInformationType::None; +} + } diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index 89dbd52a1..98c352f21 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -5,6 +5,7 @@ #include #include #include +#include "../shared/poincare_helpers.h" namespace Calculation { @@ -18,6 +19,7 @@ class CalculationStore; * */ class Calculation { +friend CalculationStore; public: enum class EqualSign : uint8_t { Unknown, @@ -32,6 +34,15 @@ public: ExactAndApproximate, ExactAndApproximateToggle }; + enum class AdditionalInformationType { + None = 0, + Integer, + Rational, + Trigonometry, + Unit, + Complex + }; + static bool DisplaysExact(DisplayOutput d) { return d != DisplayOutput::ApproximateOnly; } /* It is not really the minimal size, but it clears enough space for most * calculations instead of clearing less space, then fail to serialize, clear @@ -53,26 +64,40 @@ public: void tidy(); // Texts + enum class NumberOfSignificantDigits { + Maximal, + UserDefined + }; const char * inputText() const { return m_inputText; } const char * exactOutputText() const { return m_inputText + strlen(m_inputText) + 1; } - const char * approximateOutputText() const; + // See comment in approximateOutput implementation explaining the need of two approximateOutputTexts + const char * approximateOutputText(NumberOfSignificantDigits numberOfSignificantDigits) const; // Expressions Poincare::Expression input(); Poincare::Expression exactOutput(); - Poincare::Expression approximateOutput(Poincare::Context * context); + Poincare::Expression approximateOutput(Poincare::Context * context, NumberOfSignificantDigits numberOfSignificantDigits); // Layouts Poincare::Layout createInputLayout(); - Poincare::Layout createExactOutputLayout(); - Poincare::Layout createApproximateOutputLayout(Poincare::Context * context); + Poincare::Layout createExactOutputLayout(bool * couldNotCreateExactLayout); + Poincare::Layout createApproximateOutputLayout(Poincare::Context * context, bool * couldNotCreateApproximateLayout); - KDCoordinate height(Poincare::Context * context, bool expanded = false); + // Memoization of height + KDCoordinate height(Poincare::Context * context, bool expanded = false, bool allExpressionsInline = false); + + // Displayed output DisplayOutput displayOutput(Poincare::Context * context); + void forceDisplayOutput(DisplayOutput d) { m_displayOutput = d; } bool shouldOnlyDisplayExactOutput(); EqualSign exactAndApproximateDisplayedOutputsAreEqual(Poincare::Context * context); + + // Additional Information + AdditionalInformationType additionalInformationType(Poincare::Context * context); private: + static constexpr int k_numberOfExpressions = 4; static constexpr KDCoordinate k_heightComputationFailureHeight = 50; + static constexpr const char * k_maximalIntegerWithAdditionalInformation = "10000000000000000"; /* 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 10505479a..0bdc95279 100644 --- a/apps/calculation/calculation_store.cpp +++ b/apps/calculation/calculation_store.cpp @@ -79,8 +79,8 @@ ExpiringPointer CalculationStore::push(const char * text, Context * * want to keep Ans symbol in the calculation store. */ const char * inputSerialization = nextSerializationLocation; { - Expression input = Expression::Parse(text).replaceSymbolWithExpression(Symbol::Ans(), ans); - if (!serializeExpression(input, nextSerializationLocation, &newCalculationsLocation)) { + Expression input = Expression::Parse(text, context).replaceSymbolWithExpression(Symbol::Ans(), ans); + if (!pushSerializeExpression(input, nextSerializationLocation, &newCalculationsLocation)) { /* If the input does not fit in the store (event if the current * calculation is the only calculation), just replace the calculation with * undef. */ @@ -90,16 +90,34 @@ ExpiringPointer CalculationStore::push(const char * text, Context * } // Compute and serialize the outputs + /* The serialized outputs are: + * - the exact ouput + * - the approximate output with the maximal number of significant digits + * - the approximate output with the displayed number of significant digits */ { +<<<<<<< HEAD Expression outputs[] = {Expression(), Expression()}; PoincareHelpers::ParseAndSimplifyAndApproximate(inputSerialization, &(outputs[0]), &(outputs[1]), context, GlobalPreferences::sharedGlobalPreferences()->isInExamModeSymbolic()); // Symbolic computation for (int i = 0; i < 2; i++) { if (!serializeExpression(outputs[i], nextSerializationLocation, &newCalculationsLocation)) { +======= + // Outputs hold exact output, approximate output and its duplicate + constexpr static int numberOfOutputs = Calculation::k_numberOfExpressions - 1; + Expression outputs[numberOfOutputs] = {Expression(), Expression(), Expression()}; + PoincareHelpers::ParseAndSimplifyAndApproximate(inputSerialization, &(outputs[0]), &(outputs[1]), context, Poincare::ExpressionNode::SymbolicComputation::ReplaceAllSymbolsWithDefinitionsOrUndefined); + outputs[2] = outputs[1]; + int numberOfSignificantDigits = Poincare::PrintFloat::k_numberOfStoredSignificantDigits; + for (int i = 0; i < numberOfOutputs; i++) { + if (i == numberOfOutputs - 1) { + numberOfSignificantDigits = Poincare::Preferences::sharedPreferences()->numberOfSignificantDigits(); + } + if (!pushSerializeExpression(outputs[i], nextSerializationLocation, &newCalculationsLocation, numberOfSignificantDigits)) { +>>>>>>> upstream/master /* If the exat/approximate output does not fit in the store (event if the * current calculation is the only calculation), replace the output with * undef if it fits, else replace the whole calcualtion with undef. */ Expression undef = Undefined::Builder(); - if (!serializeExpression(undef, nextSerializationLocation, &newCalculationsLocation)) { + if (!pushSerializeExpression(undef, nextSerializationLocation, &newCalculationsLocation)) { return emptyStoreAndPushUndef(context); } } @@ -163,11 +181,10 @@ Expression CalculationStore::ansExpression(Context * context) { * To avoid turning 'ans->A' in '2->A->A' or '2=A->A' (which cannot be * parsed), ans is replaced by the approximation output when any Store or * Equal expression appears. */ - bool exactOuptutInvolvesStoreEqual = mostRecentCalculation->exactOutput().recursivelyMatches([](const Expression e, Context * context) { - return e.type() == ExpressionNode::Type::Store || e.type() == ExpressionNode::Type::Equal; - }, context, false); + Expression e = mostRecentCalculation->exactOutput(); + bool exactOuptutInvolvesStoreEqual = e.type() == ExpressionNode::Type::Store || e.type() == ExpressionNode::Type::Equal; if (mostRecentCalculation->input().recursivelyMatches(Expression::IsApproximate, context) || exactOuptutInvolvesStoreEqual) { - return mostRecentCalculation->approximateOutput(context); + return mostRecentCalculation->approximateOutput(context, Calculation::NumberOfSignificantDigits::Maximal); } return mostRecentCalculation->exactOutput(); } @@ -184,12 +201,20 @@ Calculation * CalculationStore::bufferCalculationAtIndex(int i) { return nullptr; } -bool CalculationStore::serializeExpression(Expression e, char * location, char * * newCalculationsLocation) { +bool CalculationStore::pushSerializeExpression(Expression e, char * location, char * * newCalculationsLocation, int numberOfSignificantDigits) { assert(m_slidedBuffer); - return pushExpression( - [](char * location, size_t locationSize, void * e) { - return PoincareHelpers::Serialize(*(Expression *)e, location, locationSize) < (int)locationSize-1; - }, &e, location, newCalculationsLocation); + assert(*newCalculationsLocation <= m_buffer + k_bufferSize); + bool expressionIsPushed = false; + while (true) { + size_t locationSize = *newCalculationsLocation - location; + expressionIsPushed = (PoincareHelpers::Serialize(e, location, locationSize, numberOfSignificantDigits) < (int)locationSize-1); + if (expressionIsPushed || *newCalculationsLocation >= m_buffer + k_bufferSize) { + break; + } + *newCalculationsLocation = *newCalculationsLocation + deleteLastCalculation(); + assert(*newCalculationsLocation <= m_buffer + k_bufferSize); + } + return expressionIsPushed; } char * CalculationStore::slideCalculationsToEndOfBuffer() { @@ -232,20 +257,6 @@ const char * CalculationStore::lastCalculationPosition(const char * calculations return reinterpret_cast(c); } -bool CalculationStore::pushExpression(ValueCreator valueCreator, Expression * expression, char * location, char * * newCalculationsLocation) { - assert(*newCalculationsLocation <= m_buffer + k_bufferSize); - bool expressionIsPushed = false; - while (true) { - expressionIsPushed = valueCreator(location, *newCalculationsLocation - location, expression); - if (expressionIsPushed || *newCalculationsLocation >= m_buffer + k_bufferSize) { - break; - } - *newCalculationsLocation = *newCalculationsLocation + deleteLastCalculation(); - assert(*newCalculationsLocation <= m_buffer + k_bufferSize); - } - return expressionIsPushed; -} - Shared::ExpiringPointer CalculationStore::emptyStoreAndPushUndef(Context * context) { /* We end up here as a result of a failed calculation push. The store * attributes are not necessarily clean, so we need to reset them. */ diff --git a/apps/calculation/calculation_store.h b/apps/calculation/calculation_store.h index 0103ce08d..9c2185f88 100644 --- a/apps/calculation/calculation_store.h +++ b/apps/calculation/calculation_store.h @@ -3,6 +3,7 @@ #include "calculation.h" #include +#include namespace Calculation { @@ -35,7 +36,7 @@ public: void tidy(); private: static constexpr int k_maxNumberOfCalculations = 25; - static constexpr int k_bufferSize = 10 * 3 * Constant::MaxSerializedExpressionSize; + static constexpr int k_bufferSize = 10 * Calculation::k_numberOfExpressions * Constant::MaxSerializedExpressionSize; class CalculationIterator { public: @@ -55,12 +56,10 @@ private: Calculation * bufferCalculationAtIndex(int i); int remainingBufferSize() const { assert(m_bufferEnd >= m_buffer); return k_bufferSize - (m_bufferEnd - m_buffer); } - bool serializeExpression(Poincare::Expression e, char * location, char * * newCalculationsLocation); + bool pushSerializeExpression(Poincare::Expression e, char * location, char * * newCalculationsLocation, int numberOfSignificantDigits = Poincare::PrintFloat::k_numberOfStoredSignificantDigits); char * slideCalculationsToEndOfBuffer(); // returns the new position of the calculations size_t deleteLastCalculation(const char * calculationsStart = nullptr); const char * lastCalculationPosition(const char * calculationsStart) const; - typedef bool (*ValueCreator)(char * location, size_t locationSize, void * e); - bool pushExpression(ValueCreator valueCrator, Poincare::Expression * expression, char * location, char * * newCalculationsLocation); Shared::ExpiringPointer emptyStoreAndPushUndef(Poincare::Context * context); char m_buffer[k_bufferSize]; diff --git a/apps/calculation/edit_expression_controller.cpp b/apps/calculation/edit_expression_controller.cpp index f631b8f84..22f76615b 100644 --- a/apps/calculation/edit_expression_controller.cpp +++ b/apps/calculation/edit_expression_controller.cpp @@ -25,12 +25,12 @@ View * EditExpressionController::ContentView::subviewAtIndex(int index) { return &m_expressionField; } -void EditExpressionController::ContentView::layoutSubviews() { +void EditExpressionController::ContentView::layoutSubviews(bool force) { KDCoordinate inputViewFrameHeight = m_expressionField.minimalSizeForOptimalDisplay().height(); KDRect mainViewFrame(0, 0, bounds().width(), bounds().height() - inputViewFrameHeight); - m_mainView->setFrame(mainViewFrame); + m_mainView->setFrame(mainViewFrame, force); KDRect inputViewFrame(0, bounds().height() - inputViewFrameHeight, bounds().width(), inputViewFrameHeight); - m_expressionField.setFrame(inputViewFrame); + m_expressionField.setFrame(inputViewFrame, force); } void EditExpressionController::ContentView::reload() { @@ -42,25 +42,24 @@ EditExpressionController::EditExpressionController(Responder * parentResponder, ViewController(parentResponder), m_historyController(historyController), m_calculationStore(calculationStore), - m_contentView(this, (TableView *)m_historyController->view(), inputEventHandlerDelegate, this, this), - m_inputViewHeightIsMaximal(false) + m_contentView(this, (TableView *)m_historyController->view(), inputEventHandlerDelegate, this, this) { m_cacheBuffer[0] = 0; } -View * EditExpressionController::view() { - return &m_contentView; -} - void EditExpressionController::insertTextBody(const char * text) { - ((ContentView *)view())->expressionField()->handleEventWithText(text, false, true); + 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); - ((ContentView *)view())->expressionField()->setEditing(true, false); - Container::activeApp()->setFirstResponder(((ContentView *)view())->expressionField()); + m_contentView.expressionField()->setEditing(true, false); + Container::activeApp()->setFirstResponder(m_contentView.expressionField()); +} + +void EditExpressionController::viewWillAppear() { + m_historyController->viewWillAppear(); } bool EditExpressionController::textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) { @@ -96,23 +95,23 @@ bool EditExpressionController::layoutFieldDidAbortEditing(::LayoutField * layout } void EditExpressionController::layoutFieldDidChangeSize(::LayoutField * layoutField) { - /* Reload the view only if the ExpressionField height actually changes, i.e. - * not if the height is already maximal and stays maximal. */ - if (view()) { - bool newInputViewHeightIsMaximal = static_cast(view())->expressionField()->heightIsMaximal(); - if (!m_inputViewHeightIsMaximal || !newInputViewHeightIsMaximal) { - m_inputViewHeightIsMaximal = newInputViewHeightIsMaximal; - reloadView(); - } + if (m_contentView.expressionField()->inputViewHeightDidChange()) { + /* Reload the whole view only if the ExpressionField's height did actually + * change. */ + reloadView(); + } else { + /* The input view is already at maximal size so we do not need to relayout + * the view underneath, but the view inside the input view might still need + * to be relayouted. + * We force the relayout because the frame stays the same but we need to + * propagate a relayout to the content of the field scroll view. */ + m_contentView.expressionField()->layoutSubviews(true); } } void EditExpressionController::reloadView() { - ((ContentView *)view())->reload(); + m_contentView.reload(); m_historyController->reload(); - if (m_historyController->numberOfRows() > 0) { - ((ContentView *)view())->mainView()->scrollToCell(0, m_historyController->numberOfRows()-1); - } } bool EditExpressionController::inputViewDidReceiveEvent(Ion::Events::Event event, bool shouldDuplicateLastCalculation) { @@ -125,13 +124,12 @@ bool EditExpressionController::inputViewDidReceiveEvent(Ion::Events::Event event } m_calculationStore->push(m_cacheBuffer, myApp->localContext()); m_historyController->reload(); - ((ContentView *)view())->mainView()->scrollToCell(0, m_historyController->numberOfRows()-1); return true; } if (event == Ion::Events::Up) { if (m_calculationStore->numberOfCalculations() > 0) { m_cacheBuffer[0] = 0; - ((ContentView *)view())->expressionField()->setEditing(false, false); + m_contentView.expressionField()->setEditing(false, false); Container::activeApp()->setFirstResponder(m_historyController); } return true; @@ -141,29 +139,25 @@ bool EditExpressionController::inputViewDidReceiveEvent(Ion::Events::Event event bool EditExpressionController::inputViewDidFinishEditing(const char * text, Layout layoutR) { + Context * context = textFieldDelegateApp()->localContext(); if (layoutR.isUninitialized()) { assert(text); strlcpy(m_cacheBuffer, text, k_cacheBufferSize); } else { - layoutR.serializeParsedExpression(m_cacheBuffer, k_cacheBufferSize); + layoutR.serializeParsedExpression(m_cacheBuffer, k_cacheBufferSize, context); } - m_calculationStore->push(m_cacheBuffer, textFieldDelegateApp()->localContext()); + m_calculationStore->push(m_cacheBuffer, context); m_historyController->reload(); - ((ContentView *)view())->mainView()->scrollToCell(0, m_historyController->numberOfRows()-1); - ((ContentView *)view())->expressionField()->setEditing(true, true); + m_contentView.expressionField()->setEditing(true, true); return true; } bool EditExpressionController::inputViewDidAbortEditing(const char * text) { if (text != nullptr) { - ((ContentView *)view())->expressionField()->setEditing(true, true); - ((ContentView *)view())->expressionField()->setText(text); + m_contentView.expressionField()->setEditing(true, true); + m_contentView.expressionField()->setText(text); } return false; } -void EditExpressionController::viewDidDisappear() { - m_historyController->viewDidDisappear(); -} - } diff --git a/apps/calculation/edit_expression_controller.h b/apps/calculation/edit_expression_controller.h index 159679468..fe2d4c44c 100644 --- a/apps/calculation/edit_expression_controller.h +++ b/apps/calculation/edit_expression_controller.h @@ -10,15 +10,14 @@ #include "calculation_store.h" namespace Calculation { -class HistoryController; /* TODO: implement a split view */ class EditExpressionController : public ViewController, public Shared::TextFieldDelegate, public Shared::LayoutFieldDelegate { public: EditExpressionController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, HistoryController * historyController, CalculationStore * calculationStore); - View * view() override; + View * view() override { return &m_contentView; } void didBecomeFirstResponder() override; - void viewDidDisappear() override; + void viewWillAppear() override; void insertTextBody(const char * text); /* TextFieldDelegate */ @@ -39,11 +38,10 @@ private: void reload(); TableView * mainView() { return m_mainView; } ExpressionField * expressionField() { return &m_expressionField; } - /* View */ + private: int numberOfSubviews() const override { return 2; } View * subviewAtIndex(int index) override; - void layoutSubviews() override; - private: + void layoutSubviews(bool force = false) override; TableView * m_mainView; ExpressionField m_expressionField; }; @@ -56,7 +54,6 @@ private: HistoryController * m_historyController; CalculationStore * m_calculationStore; ContentView m_contentView; - bool m_inputViewHeightIsMaximal; }; } diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index 2f300aaef..a817a6598 100644 --- a/apps/calculation/history_controller.cpp +++ b/apps/calculation/history_controller.cpp @@ -3,14 +3,19 @@ #include using namespace Shared; +using namespace Poincare; namespace Calculation { -HistoryController::HistoryController(Responder * parentResponder, CalculationStore * calculationStore) : - ViewController(parentResponder), +HistoryController::HistoryController(EditExpressionController * editExpressionController, CalculationStore * calculationStore) : + ViewController(editExpressionController), m_selectableTableView(this, this, this, this), m_calculationHistory{}, - m_calculationStore(calculationStore) + m_calculationStore(calculationStore), + m_complexController(editExpressionController), + m_integerController(editExpressionController), + m_rationalController(editExpressionController), + m_trigonometryController(editExpressionController) { for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) { m_calculationHistory[i].setParentResponder(&m_selectableTableView); @@ -20,6 +25,19 @@ HistoryController::HistoryController(Responder * parentResponder, CalculationSto void HistoryController::reload() { m_selectableTableView.reloadData(); + /* TODO + * Replace the following by selectCellAtLocation in order to avoid laying out + * the table view twice. + */ + if (numberOfRows() > 0) { + m_selectableTableView.scrollToCell(0, numberOfRows()-1); + // Force to reload last added cell (hide the burger and exact output if necessary) + tableViewDidChangeSelection(&m_selectableTableView, 0, numberOfRows()-1); + } +} + +void HistoryController::viewWillAppear() { + reload(); } void HistoryController::didBecomeFirstResponder() { @@ -28,6 +46,9 @@ void HistoryController::didBecomeFirstResponder() { } void HistoryController::willExitResponderChain(Responder * nextFirstResponder) { + if (nextFirstResponder == nullptr) { + return; + } if (nextFirstResponder == parentResponder()) { m_selectableTableView.deselectTable(); } @@ -47,20 +68,43 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { HistoryViewCell * selectedCell = (HistoryViewCell *)m_selectableTableView.selectedCell(); SubviewType subviewType = selectedSubviewType(); EditExpressionController * editController = (EditExpressionController *)parentResponder(); - m_selectableTableView.deselectTable(); - Container::activeApp()->setFirstResponder(editController); - Shared::ExpiringPointer calculation = calculationAtIndex(focusRow); if (subviewType == SubviewType::Input) { - editController->insertTextBody(calculation->inputText()); - } else { - ScrollableExactApproximateExpressionsView::SubviewPosition outputSubviewPosition = selectedCell->outputView()->selectedSubviewPosition(); - if (outputSubviewPosition == ScrollableExactApproximateExpressionsView::SubviewPosition::Right + 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 && !calculation->shouldOnlyDisplayExactOutput()) { - editController->insertTextBody(calculation->approximateOutputText()); + editController->insertTextBody(calculation->approximateOutputText(Calculation::NumberOfSignificantDigits::Maximal)); } else { editController->insertTextBody(calculation->exactOutputText()); } + } else { + assert(subviewType == SubviewType::Ellipsis); + Calculation::AdditionalInformationType additionalInfoType = selectedCell->additionalInformationType(); + ListController * vc = nullptr; + Expression e = calculationAtIndex(focusRow)->exactOutput(); + if (additionalInfoType == Calculation::AdditionalInformationType::Complex) { + vc = &m_complexController; + } else if (additionalInfoType == Calculation::AdditionalInformationType::Trigonometry) { + vc = &m_trigonometryController; + // Find which of the input or output is the cosine/sine + ExpressionNode::Type t = e.type(); + e = t == ExpressionNode::Type::Cosine || t == ExpressionNode::Type::Sine ? e : calculationAtIndex(focusRow)->input(); + } else if (additionalInfoType == Calculation::AdditionalInformationType::Integer) { + vc = &m_integerController; + } else if (additionalInfoType == Calculation::AdditionalInformationType::Rational) { + vc = &m_rationalController; + } + if (vc) { + vc->setExpression(e); + Container::activeApp()->displayModalViewController(vc, 0.f, 0.f, Metric::CommonTopMargin, Metric::PopUpLeftMargin, 0, Metric::PopUpRightMargin); + } } return true; } @@ -113,11 +157,13 @@ void HistoryController::tableViewDidChangeSelection(SelectableTableView * t, int return; } if (previousSelectedCellY == -1) { - setSelectedSubviewType(SubviewType::Output); + setSelectedSubviewType(SubviewType::Output, false, previousSelectedCellX, previousSelectedCellY); } else if (selectedRow() < previousSelectedCellY) { - setSelectedSubviewType(SubviewType::Output); + setSelectedSubviewType(SubviewType::Output, false, previousSelectedCellX, previousSelectedCellY); } else if (selectedRow() > previousSelectedCellY) { - setSelectedSubviewType(SubviewType::Input); + setSelectedSubviewType(SubviewType::Input, false, previousSelectedCellX, previousSelectedCellY); + } else if (selectedRow() == -1) { + setSelectedSubviewType(SubviewType::Input, false, previousSelectedCellX, previousSelectedCellY); } HistoryViewCell * selectedCell = (HistoryViewCell *)(t->selectedCell()); if (selectedCell == nullptr) { @@ -146,7 +192,7 @@ void HistoryController::willDisplayCellForIndex(HighlightCell * cell, int index) HistoryViewCell * myCell = (HistoryViewCell *)cell; myCell->setCalculation(calculationAtIndex(index).pointer(), index == selectedRow() && selectedSubviewType() == SubviewType::Output); myCell->setEven(index%2 == 0); - myCell->setHighlighted(myCell->isHighlighted()); + myCell->reloadSubviewHighlight(); } KDCoordinate HistoryController::rowHeight(int j) { @@ -165,12 +211,27 @@ void HistoryController::scrollToCell(int i, int j) { m_selectableTableView.scrollToCell(i, j); } -HistoryViewCell * HistoryController::historyViewCellDidChangeSelection() { - /* Update the whole table as the height of the selected cell row might have - * changed. */ - m_selectableTableView.reloadData(); - // Return the selected cell if one - return static_cast(m_selectableTableView.selectedCell()); +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::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. */ + if ((type == SubviewType::Output || previousType == SubviewType::Output) && (calculationAtIndexToggles(selectedRow()) || calculationAtIndexToggles(previousSelectedCellY))) { + m_selectableTableView.reloadData(); + } + + // Fill the selected cell and the previous selected cell because cells repartition might have changed + *cell = static_cast(m_selectableTableView.selectedCell()); + *previousCell = static_cast(m_selectableTableView.cellAtLocation(previousSelectedCellX, previousSelectedCellY)); + /* 'reloadData' calls 'willDisplayCellForIndex' for each cell while the table + * has been deselected. To reload the expanded cell, we call one more time + * 'willDisplayCellForIndex' but once the right cell has been selected. */ + if (*cell) { + willDisplayCellForIndex(*cell, selectedRow()); + } } } diff --git a/apps/calculation/history_controller.h b/apps/calculation/history_controller.h index f75c20015..d0bed934e 100644 --- a/apps/calculation/history_controller.h +++ b/apps/calculation/history_controller.h @@ -5,6 +5,10 @@ #include "history_view_cell.h" #include "calculation_store.h" #include "selectable_table_view.h" +#include "additional_outputs/complex_list_controller.h" +#include "additional_outputs/integer_list_controller.h" +#include "additional_outputs/rational_list_controller.h" +#include "additional_outputs/trigonometry_list_controller.h" namespace Calculation { @@ -12,9 +16,10 @@ class App; class HistoryController : public ViewController, public ListViewDataSource, public SelectableTableViewDataSource, public SelectableTableViewDelegate, public HistoryViewCellDataSource { public: - HistoryController(Responder * parentResponder, CalculationStore * calculationStore); + HistoryController(EditExpressionController * editExpressionController, CalculationStore * calculationStore); View * view() override { return &m_selectableTableView; } bool handleEvent(Ion::Events::Event event) override; + void viewWillAppear() override; void didBecomeFirstResponder() override; void willExitResponderChain(Responder * nextFirstResponder) override; void reload(); @@ -30,11 +35,16 @@ private: int storeIndex(int i) { return numberOfRows() - i - 1; } Shared::ExpiringPointer calculationAtIndex(int i); CalculationSelectableTableView * selectableTableView(); - HistoryViewCell * historyViewCellDidChangeSelection() override; + bool calculationAtIndexToggles(int index); + void historyViewCellDidChangeSelection(HistoryViewCell ** cell, HistoryViewCell ** previousCell, int previousSelectedCellX, int previousSelectedCellY, SubviewType type, SubviewType previousType) override; constexpr static int k_maxNumberOfDisplayedRows = 5; CalculationSelectableTableView m_selectableTableView; HistoryViewCell m_calculationHistory[k_maxNumberOfDisplayedRows]; CalculationStore * m_calculationStore; + ComplexListController m_complexController; + IntegerListController m_integerController; + RationalListController m_rationalController; + TrigonometryListController m_trigonometryController; }; } diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index 2ed43ed74..b40bb30ff 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -2,6 +2,7 @@ #include "app.h" #include "../constant.h" #include "selectable_table_view.h" +#include #include #include @@ -15,12 +16,23 @@ static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { retur HistoryViewCellDataSource::HistoryViewCellDataSource() : m_selectedSubviewType(SubviewType::Output) {} -void HistoryViewCellDataSource::setSelectedSubviewType(SubviewType subviewType) { +void HistoryViewCellDataSource::setSelectedSubviewType(SubviewType subviewType, bool sameCell, int previousSelectedCellX, int previousSelectedCellY) { + HistoryViewCell * selectedCell = nullptr; + HistoryViewCell * previouslySelectedCell = nullptr; + SubviewType previousSubviewType = m_selectedSubviewType; m_selectedSubviewType = subviewType; - HistoryViewCell * cell = historyViewCellDidChangeSelection(); - if (cell) { - cell->setHighlighted(cell->isHighlighted()); - cell->cellDidSelectSubview(subviewType); + /* We need to notify the whole table that the selection changed if it + * involves the selection/deselection of an output. Indeed, only them can + * trigger change in the displayed expressions. */ + historyViewCellDidChangeSelection(&selectedCell, &previouslySelectedCell, previousSelectedCellX, previousSelectedCellY, subviewType, previousSubviewType); + + previousSubviewType = sameCell ? previousSubviewType : SubviewType::None; + if (selectedCell) { + selectedCell->reloadSubviewHighlight(); + selectedCell->cellDidSelectSubview(subviewType, previousSubviewType); + } + if (previouslySelectedCell) { + previouslySelectedCell->cellDidSelectSubview(SubviewType::Input); } } @@ -29,14 +41,15 @@ void HistoryViewCellDataSource::setSelectedSubviewType(SubviewType subviewType) HistoryViewCell::HistoryViewCell(Responder * parentResponder) : Responder(parentResponder), m_calculationDisplayOutput(Calculation::DisplayOutput::Unknown), + m_calculationAdditionInformation(Calculation::AdditionalInformationType::None), m_calculationExpanded(false), - m_inputView(this), + m_inputView(this, Metric::CommonLargeMargin, Metric::CommonSmallMargin), m_scrollableOutputView(this) { m_calculationCRC32 = 0; } -Shared::ScrollableExactApproximateExpressionsView * HistoryViewCell::outputView() { +Shared::ScrollableTwoExpressionsView * HistoryViewCell::outputView() { return &m_scrollableOutputView; } @@ -45,18 +58,32 @@ void HistoryViewCell::setEven(bool even) { m_inputView.setBackgroundColor(backgroundColor()); m_scrollableOutputView.setBackgroundColor(backgroundColor()); m_scrollableOutputView.evenOddCell()->setEven(even); + m_ellipsis.setEven(even); } void HistoryViewCell::setHighlighted(bool highlight) { - assert(m_dataSource); + if (m_highlighted == highlight) { + return; + } m_highlighted = highlight; + reloadSubviewHighlight(); + // Re-layout as the ellispsis subview might have appear/disappear + layoutSubviews(); +} + +void HistoryViewCell::reloadSubviewHighlight() { + assert(m_dataSource); m_inputView.setExpressionBackgroundColor(backgroundColor()); m_scrollableOutputView.evenOddCell()->setHighlighted(false); + m_ellipsis.setHighlighted(false); if (isHighlighted()) { if (m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Input) { m_inputView.setExpressionBackgroundColor(Palette::ListCellBackgroundSelected); - } else { + } else if (m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Output) { m_scrollableOutputView.evenOddCell()->setHighlighted(true); + } else { + assert(m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Ellipsis); + m_ellipsis.setHighlighted(true); } } } @@ -75,30 +102,35 @@ void HistoryViewCell::reloadScroll() { m_scrollableOutputView.reloadScroll(); } -void HistoryViewCell::reloadOutputSelection() { +void HistoryViewCell::reloadOutputSelection(HistoryViewCellDataSource::SubviewType previousType) { /* Select the right output according to the calculation display output. This * will reload the scroll to display the selected output. */ if (m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximate) { - m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Left); + m_scrollableOutputView.setSelectedSubviewPosition( + previousType == HistoryViewCellDataSource::SubviewType::Ellipsis ? + Shared::ScrollableTwoExpressionsView::SubviewPosition::Right : + Shared::ScrollableTwoExpressionsView::SubviewPosition::Center + ); } else { assert((m_calculationDisplayOutput == Calculation::DisplayOutput::ApproximateOnly) || (m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximateToggle) || (m_calculationDisplayOutput == Calculation::DisplayOutput::ExactOnly)); - m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Right); + m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableTwoExpressionsView::SubviewPosition::Right); } } -void HistoryViewCell::cellDidSelectSubview(HistoryViewCellDataSource::SubviewType type) { +void HistoryViewCell::cellDidSelectSubview(HistoryViewCellDataSource::SubviewType type, HistoryViewCellDataSource::SubviewType previousType) { // Init output selection if (type == HistoryViewCellDataSource::SubviewType::Output) { - reloadOutputSelection(); + reloadOutputSelection(previousType); } + // Update m_calculationExpanded + m_calculationExpanded = (type == HistoryViewCellDataSource::SubviewType::Output && m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximateToggle); /* The selected subview has changed. The displayed outputs might have changed. * For example, for the calculation 1.2+2 --> 3.2, selecting the output would * display 1.2+2 --> 16/5 = 3.2. */ - m_calculationExpanded = (type == HistoryViewCellDataSource::SubviewType::Output); - m_scrollableOutputView.setDisplayLeftLayout(displayLeftLayout()); + m_scrollableOutputView.setDisplayCenter(m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximate || m_calculationExpanded); /* The displayed outputs have changed. We need to re-layout the cell * and re-initialize the scroll. */ @@ -112,56 +144,121 @@ KDColor HistoryViewCell::backgroundColor() const { } int HistoryViewCell::numberOfSubviews() const { - return 2; + return 2 + displayedEllipsis(); } View * HistoryViewCell::subviewAtIndex(int index) { - View * views[2] = {&m_inputView, &m_scrollableOutputView}; + /* The order of the subviews should not matter here as they don't overlap. + * However, the order determines the order of redrawing as well. For several + * reasons listed after, changing subview selection often redraws the entire + * m_scrollableOutputView even if it seems unecessary: + * - Before feeding new Layouts to ExpressionViews, we reset the hold layouts + * in order to empty the Poincare pool and have more space to compute new + * layouts. + * - Even if we did not do that, ExpressionView::setLayout doesn't avoid + * redrawing when the previous expression is identical (for reasons + * explained in expression_view.cpp) + * - Because of the toggling burger view, ExpressionViews often have the same + * absolute frame but a different relative frame which leads to redrawing + * them anyway. + * All these reasons cause a blinking which can be avoided if we redraw the + * output view before the input view (starting with redrawing the more + * complex view enables to redraw it before the vblank thereby preventing + * blinking). + * TODO: this is a dirty hack which should be fixed! */ + View * views[3] = {&m_scrollableOutputView, &m_inputView, &m_ellipsis}; return views[index]; } -void HistoryViewCell::layoutSubviews() { +void HistoryViewCell::layoutSubviews(bool force) { KDCoordinate maxFrameWidth = bounds().width(); + if (displayedEllipsis()) { + m_ellipsis.setFrame(KDRect(maxFrameWidth - Metric::EllipsisCellWidth, 0, Metric::EllipsisCellWidth, bounds().height()), force); + maxFrameWidth -= Metric::EllipsisCellWidth; + } else { + m_ellipsis.setFrame(KDRectZero, force); // Required to mark previous rect as dirty + } KDSize inputSize = m_inputView.minimalSizeForOptimalDisplay(); m_inputView.setFrame(KDRect( - 0, - 0, + 0, 0, minCoordinate(maxFrameWidth, inputSize.width()), - inputSize.height() - )); + inputSize.height()), + force); KDSize outputSize = m_scrollableOutputView.minimalSizeForOptimalDisplay(); m_scrollableOutputView.setFrame(KDRect( maxCoordinate(0, maxFrameWidth - outputSize.width()), inputSize.height(), minCoordinate(maxFrameWidth, outputSize.width()), - outputSize.height() - )); + outputSize.height()), + force); } void HistoryViewCell::setCalculation(Calculation * calculation, bool expanded) { uint32_t newCalculationCRC = Ion::crc32Byte((const uint8_t *)calculation, ((char *)calculation->next()) - ((char *) calculation)); - if (m_calculationExpanded == expanded && newCalculationCRC == m_calculationCRC32) { + if (newCalculationCRC == m_calculationCRC32 && m_calculationExpanded == expanded) { return; } Poincare::Context * context = App::app()->localContext(); // Clean the layouts to make room in the pool + // TODO: maybe do this only when the layout won't change to avoid blinking m_inputView.setLayout(Poincare::Layout()); - m_scrollableOutputView.setLayouts(Poincare::Layout(), Poincare::Layout()); + m_scrollableOutputView.setLayouts(Poincare::Layout(), Poincare::Layout(), Poincare::Layout()); // Memoization m_calculationCRC32 = newCalculationCRC; - m_calculationExpanded = expanded; - m_calculationDisplayOutput = calculation->displayOutput(context); + m_calculationExpanded = expanded && calculation->displayOutput(context) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximateToggle; + m_calculationAdditionInformation = calculation->additionalInformationType(context); m_inputView.setLayout(calculation->createInputLayout()); - /* Both output expressions have to be updated at the same time. Otherwise, + + /* All expressions have to be updated at the same time. Otherwise, * when updating one layout, if the second one still points to a deleted * layout, calling to layoutSubviews() would fail. */ - Poincare::Layout leftOutputLayout = calculation->createExactOutputLayout(); - Poincare::Layout rightOutputLayout = (m_calculationDisplayOutput == Calculation::DisplayOutput::ExactOnly) ? leftOutputLayout : - calculation->createApproximateOutputLayout(context); - m_scrollableOutputView.setDisplayLeftLayout(displayLeftLayout()); // Must be before the setLayouts fo the reload - m_scrollableOutputView.setLayouts(rightOutputLayout, leftOutputLayout); + + // Create the exact output layout + Poincare::Layout exactOutputLayout = Poincare::Layout(); + if (Calculation::DisplaysExact(calculation->displayOutput(context))) { + bool couldNotCreateExactLayout = false; + exactOutputLayout = calculation->createExactOutputLayout(&couldNotCreateExactLayout); + if (couldNotCreateExactLayout) { + if (calculation->displayOutput(context) != ::Calculation::Calculation::DisplayOutput::ExactOnly) { + calculation->forceDisplayOutput(::Calculation::Calculation::DisplayOutput::ApproximateOnly); + } else { + /* We should only display the exact result, but we cannot create it + * -> raise an exception. */ + Poincare::ExceptionCheckpoint::Raise(); + } + } + } + + // Create the approximate output layout + Poincare::Layout approximateOutputLayout; + if (calculation->displayOutput(context) == ::Calculation::Calculation::DisplayOutput::ExactOnly) { + approximateOutputLayout = exactOutputLayout; + } else { + bool couldNotCreateApproximateLayout = false; + approximateOutputLayout = calculation->createApproximateOutputLayout(context, &couldNotCreateApproximateLayout); + if (couldNotCreateApproximateLayout) { + if (calculation->displayOutput(context) == ::Calculation::Calculation::DisplayOutput::ApproximateOnly) { + Poincare::ExceptionCheckpoint::Raise(); + } else { + /* 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); + exactOutputLayout = Poincare::Layout(); + couldNotCreateApproximateLayout = false; + approximateOutputLayout = calculation->createApproximateOutputLayout(context, &couldNotCreateApproximateLayout); + if (couldNotCreateApproximateLayout) { + Poincare::ExceptionCheckpoint::Raise(); + } + } + } + } + m_calculationDisplayOutput = calculation->displayOutput(context); + + // We must set which subviews are displayed before setLayouts to mark the right rectangle as dirty + m_scrollableOutputView.setDisplayCenter(m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximate || m_calculationExpanded); + m_scrollableOutputView.setLayouts(Poincare::Layout(), exactOutputLayout, approximateOutputLayout); I18n::Message equalMessage = calculation->exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal ? I18n::Message::Equal : I18n::Message::AlmostEqual; m_scrollableOutputView.setEqualMessage(equalMessage); @@ -175,17 +272,30 @@ void HistoryViewCell::didBecomeFirstResponder() { assert(m_dataSource); if (m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Input) { Container::activeApp()->setFirstResponder(&m_inputView); - } else { + } else if (m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Output) { Container::activeApp()->setFirstResponder(&m_scrollableOutputView); } } bool HistoryViewCell::handleEvent(Ion::Events::Event event) { assert(m_dataSource); - if ((event == Ion::Events::Down && m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Input) || - (event == Ion::Events::Up && m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Output)) { - HistoryViewCellDataSource::SubviewType otherSubviewType = m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Input ? HistoryViewCellDataSource::SubviewType::Output : HistoryViewCellDataSource::SubviewType::Input; - m_dataSource->setSelectedSubviewType(otherSubviewType); + HistoryViewCellDataSource::SubviewType type = m_dataSource->selectedSubviewType(); + if ((event == Ion::Events::Down && type == HistoryViewCellDataSource::SubviewType::Input) || + (event == Ion::Events::Up && type == HistoryViewCellDataSource::SubviewType::Output) || + (event == Ion::Events::Right && type != HistoryViewCellDataSource::SubviewType::Ellipsis && displayedEllipsis()) || + (event == Ion::Events::Left && type == HistoryViewCellDataSource::SubviewType::Ellipsis)) { + HistoryViewCellDataSource::SubviewType otherSubviewType; + if (event == Ion::Events::Down) { + otherSubviewType = HistoryViewCellDataSource::SubviewType::Output; + } else if (event == Ion::Events::Up) { + otherSubviewType = HistoryViewCellDataSource::SubviewType::Input; + } else if (event == Ion::Events::Right) { + otherSubviewType = HistoryViewCellDataSource::SubviewType::Ellipsis; + } else { + assert(event == Ion::Events::Left); + otherSubviewType = HistoryViewCellDataSource::SubviewType::Output; + } + m_dataSource->setSelectedSubviewType(otherSubviewType, true); CalculationSelectableTableView * tableView = (CalculationSelectableTableView *)parentResponder(); tableView->scrollToSubviewOfTypeOfCellAtLocation(otherSubviewType, tableView->selectedColumn(), tableView->selectedRow()); Container::activeApp()->setFirstResponder(this); @@ -194,9 +304,8 @@ bool HistoryViewCell::handleEvent(Ion::Events::Event event) { return false; } -bool HistoryViewCell::displayLeftLayout() const { - return (m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximate) - || (m_calculationDisplayOutput == Calculation::DisplayOutput::ExactAndApproximateToggle && m_calculationExpanded); +bool HistoryViewCell::displayedEllipsis() const { + return m_highlighted && m_calculationAdditionInformation != Calculation::AdditionalInformationType::None; } } diff --git a/apps/calculation/history_view_cell.h b/apps/calculation/history_view_cell.h index e1c0f099d..42e59719c 100644 --- a/apps/calculation/history_view_cell.h +++ b/apps/calculation/history_view_cell.h @@ -3,8 +3,7 @@ #include #include "calculation.h" -#include "scrollable_expression_view.h" -#include "../shared/scrollable_exact_approximate_expressions_view.h" +#include "../shared/scrollable_multiple_expressions_view.h" namespace Calculation { @@ -13,50 +12,56 @@ class HistoryViewCell; class HistoryViewCellDataSource { public: enum class SubviewType { + None, Input, - Output + Output, + Ellipsis }; HistoryViewCellDataSource(); - void setSelectedSubviewType(SubviewType subviewType); + void setSelectedSubviewType(SubviewType subviewType, bool sameCell, int previousSelectedX = -1, int previousSelectedY = -1); SubviewType selectedSubviewType() { return m_selectedSubviewType; } private: /* This method should belong to a delegate instead of a data source but as * both the data source and the delegate will be the same controller, we * avoid keeping 2 pointers in HistoryViewCell. */ // It returns the selected cell at the end of the method - virtual HistoryViewCell * historyViewCellDidChangeSelection() = 0; + virtual void historyViewCellDidChangeSelection(HistoryViewCell ** cell, HistoryViewCell ** previousCell, int previousSelectedCellX, int previousSelectedCellY, SubviewType type, SubviewType previousType) = 0; SubviewType m_selectedSubviewType; }; class HistoryViewCell : public ::EvenOddCell, public Responder { public: HistoryViewCell(Responder * parentResponder = nullptr); - void cellDidSelectSubview(HistoryViewCellDataSource::SubviewType type); + void cellDidSelectSubview(HistoryViewCellDataSource::SubviewType type, HistoryViewCellDataSource::SubviewType previousType = HistoryViewCellDataSource::SubviewType::None); void setEven(bool even) override; void setHighlighted(bool highlight) override; + void reloadSubviewHighlight(); void setDataSource(HistoryViewCellDataSource * dataSource) { m_dataSource = dataSource; } Responder * responder() override { return this; } Poincare::Layout layout() const override; KDColor backgroundColor() const override; - void setCalculation(Calculation * calculation, bool expanded = false); + void setCalculation(Calculation * calculation, bool expanded); int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; void didBecomeFirstResponder() override; bool handleEvent(Ion::Events::Event event) override; - Shared::ScrollableExactApproximateExpressionsView * outputView(); + Shared::ScrollableTwoExpressionsView * outputView(); + Calculation::AdditionalInformationType additionalInformationType() const { return m_calculationAdditionInformation; } private: constexpr static KDCoordinate k_resultWidth = 80; void reloadScroll(); - void reloadOutputSelection(); - bool displayLeftLayout() const; + void reloadOutputSelection(HistoryViewCellDataSource::SubviewType previousType); + bool displayedEllipsis() const; uint32_t m_calculationCRC32; Calculation::DisplayOutput m_calculationDisplayOutput; + Calculation::AdditionalInformationType m_calculationAdditionInformation; bool m_calculationExpanded; ScrollableExpressionView m_inputView; - Shared::ScrollableExactApproximateExpressionsView m_scrollableOutputView; + Shared::ScrollableTwoExpressionsView m_scrollableOutputView; + EvenOddCellWithEllipsis m_ellipsis; HistoryViewCellDataSource * m_dataSource; }; diff --git a/apps/calculation/scrollable_expression_view.h b/apps/calculation/scrollable_expression_view.h deleted file mode 100644 index 95fc67350..000000000 --- a/apps/calculation/scrollable_expression_view.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef CALCULATION_SCROLLABLE_EXPRESSION_VIEW_H -#define CALCULATION_SCROLLABLE_EXPRESSION_VIEW_H - -#include - -namespace Calculation { - -class ScrollableExpressionView : public ScrollableView, public ScrollViewDataSource { -public: - ScrollableExpressionView(Responder * parentResponder); - Poincare::Layout layout() const; - void setLayout(Poincare::Layout layout); - void setBackgroundColor(KDColor backgroundColor) override; - void setExpressionBackgroundColor(KDColor backgroundColor); -private: - ExpressionView m_expressionView; -}; - -} - -#endif diff --git a/apps/calculation/test/calculation_store.cpp b/apps/calculation/test/calculation_store.cpp index 7603d0e6e..48e1af129 100644 --- a/apps/calculation/test/calculation_store.cpp +++ b/apps/calculation/test/calculation_store.cpp @@ -50,12 +50,12 @@ QUIZ_CASE(calculation_ans) { store.push("ans+0.22", &globalContext); lastCalculation = store.calculationAtIndex(0); quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximateToggle); - quiz_assert(strcmp(lastCalculation->approximateOutputText(),"2.6366666666667") == 0); + quiz_assert(strcmp(lastCalculation->approximateOutputText(::Calculation::Calculation::NumberOfSignificantDigits::Maximal),"2.6366666666667") == 0); store.deleteAll(); } -void assertCalculationDisplay(const char * input, ::Calculation::Calculation::DisplayOutput display, ::Calculation::Calculation::EqualSign sign, const char * exactOutput, const char * approximateOutput, Context * context, CalculationStore * store) { +void assertCalculationIs(const char * input, ::Calculation::Calculation::DisplayOutput display, ::Calculation::Calculation::EqualSign sign, const char * exactOutput, const char * displayedApproximateOutput, const char * storedApproximateOutput, Context * context, CalculationStore * store) { store->push(input, context); Shared::ExpiringPointer<::Calculation::Calculation> lastCalculation = store->calculationAtIndex(0); quiz_assert(lastCalculation->displayOutput(context) == display); @@ -65,37 +65,49 @@ void assertCalculationDisplay(const char * input, ::Calculation::Calculation::Di if (exactOutput) { quiz_assert_print_if_failure(strcmp(lastCalculation->exactOutputText(), exactOutput) == 0, input); } - if (approximateOutput) { - quiz_assert_print_if_failure(strcmp(lastCalculation->approximateOutputText(), approximateOutput) == 0, input); + if (displayedApproximateOutput) { + quiz_assert_print_if_failure(strcmp(lastCalculation->approximateOutputText(::Calculation::Calculation::NumberOfSignificantDigits::UserDefined), displayedApproximateOutput) == 0, input); + } + if (storedApproximateOutput) { + quiz_assert_print_if_failure(strcmp(lastCalculation->approximateOutputText(::Calculation::Calculation::NumberOfSignificantDigits::Maximal), storedApproximateOutput) == 0, input); } store->deleteAll(); } +QUIZ_CASE(calculation_significant_digits) { + Shared::GlobalContext globalContext; + CalculationStore store; + + assertCalculationIs("123456789", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "123456789", "1.234568ᴇ8", "123456789", &globalContext, &store); + assertCalculationIs("1234567", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "1234567", "1234567", "1234567", &globalContext, &store); + +} + QUIZ_CASE(calculation_display_exact_approximate) { Shared::GlobalContext globalContext; CalculationStore store; - assertCalculationDisplay("1/2", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Equal, nullptr, nullptr, &globalContext, &store); - assertCalculationDisplay("1/3", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, nullptr, nullptr, &globalContext, &store); - assertCalculationDisplay("1/0", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); - assertCalculationDisplay("2x-x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); - assertCalculationDisplay("[[1,2,3]]", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); - assertCalculationDisplay("[[1,x,3]]", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "undef", &globalContext, &store); - assertCalculationDisplay("28^7", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); - assertCalculationDisplay("3+√(2)→a", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(2)+3", nullptr, &globalContext, &store); + assertCalculationIs("1/2", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Equal, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("1/3", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("1/0", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); + assertCalculationIs("2x-x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); + assertCalculationIs("[[1,2,3]]", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("[[1,x,3]]", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "undef", "undef", &globalContext, &store); + assertCalculationIs("28^7", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("3+√(2)→a", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(2)+3", nullptr, nullptr, &globalContext, &store); Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); - assertCalculationDisplay("3+2→a", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "5", "5", &globalContext, &store); + assertCalculationIs("3+2→a", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "5", "5", "5", &globalContext, &store); Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); - assertCalculationDisplay("3→a", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", "3", &globalContext, &store); + assertCalculationIs("3→a", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", "3", "3", &globalContext, &store); Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); - assertCalculationDisplay("3+x→f(x)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "x+3", nullptr, &globalContext, &store); + assertCalculationIs("3+x→f(x)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "x+3", nullptr, nullptr, &globalContext, &store); Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); - assertCalculationDisplay("1+1+random()", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); - assertCalculationDisplay("1+1+round(1.343,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3.34", &globalContext, &store); - assertCalculationDisplay("randint(2,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "5", "5", &globalContext, &store); - assertCalculationDisplay("confidence(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); - assertCalculationDisplay("prediction(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); - assertCalculationDisplay("prediction95(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("1+1+random()", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("1+1+round(1.343,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3.34", "3.34", &globalContext, &store); + assertCalculationIs("randint(2,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "5", "5", "5", &globalContext, &store); + assertCalculationIs("confidence(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("prediction(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("prediction95(0.5,2)+3", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); } @@ -103,14 +115,14 @@ QUIZ_CASE(calculation_symbolic_computation) { Shared::GlobalContext globalContext; CalculationStore store; - assertCalculationDisplay("x+x+1+3+√(π)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); - assertCalculationDisplay("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); - assertCalculationDisplay("1+x→f(x)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "x+1", nullptr, &globalContext, &store); - assertCalculationDisplay("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); - assertCalculationDisplay("f(2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", "3", &globalContext, &store); - assertCalculationDisplay("2→x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "2", nullptr, &globalContext, &store); - assertCalculationDisplay("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", nullptr, &globalContext, &store); - assertCalculationDisplay("x+x+1+3+√(π)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(π)+8", nullptr, &globalContext, &store); + assertCalculationIs("x+x+1+3+√(π)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); + assertCalculationIs("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); + assertCalculationIs("1+x→f(x)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "x+1", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", "undef", &globalContext, &store); + assertCalculationIs("f(2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", "3", "3", &globalContext, &store); + assertCalculationIs("2→x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "2", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("f(x)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Equal, "3", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("x+x+1+3+√(π)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(π)+8", nullptr, nullptr, &globalContext, &store); Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); Ion::Storage::sharedStorage()->recordNamed("x.exp").destroy(); @@ -120,16 +132,16 @@ QUIZ_CASE(calculation_symbolic_computation_and_parametered_expressions) { Shared::GlobalContext globalContext; CalculationStore store; - assertCalculationDisplay("int((ℯ^(-x))-x^(0.5), x, 0, 3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); // Tests a bug with symbolic computation - assertCalculationDisplay("int(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", &globalContext, &store); - assertCalculationDisplay("sum(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3", &globalContext, &store); - assertCalculationDisplay("product(x,x,1,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", &globalContext, &store); - assertCalculationDisplay("diff(x^2,x,3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "6", &globalContext, &store); - assertCalculationDisplay("2→x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); - assertCalculationDisplay("int(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", &globalContext, &store); - assertCalculationDisplay("sum(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3", &globalContext, &store); - assertCalculationDisplay("product(x,x,1,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", &globalContext, &store); - assertCalculationDisplay("diff(x^2,x,3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "6", &globalContext, &store); + assertCalculationIs("int((ℯ^(-x))-x^(0.5), x, 0, 3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); // Tests a bug with symbolic computation + assertCalculationIs("int(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store); + assertCalculationIs("sum(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3", "3", &globalContext, &store); + assertCalculationIs("product(x,x,1,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store); + assertCalculationIs("diff(x^2,x,3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "6", "6", &globalContext, &store); + assertCalculationIs("2→x", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, nullptr, &globalContext, &store); + assertCalculationIs("int(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store); + assertCalculationIs("sum(x,x,0,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "3", "3", &globalContext, &store); + assertCalculationIs("product(x,x,1,2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "2", "2", &globalContext, &store); + assertCalculationIs("diff(x^2,x,3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "6", "6", &globalContext, &store); Ion::Storage::sharedStorage()->recordNamed("x.exp").destroy(); } @@ -140,31 +152,31 @@ QUIZ_CASE(calculation_complex_format) { CalculationStore store; Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Real); - assertCalculationDisplay("1+𝐢", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "1+𝐢", &globalContext, &store); - assertCalculationDisplay("√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "unreal", nullptr, &globalContext, &store); - assertCalculationDisplay("ln(-2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", &globalContext, &store); - assertCalculationDisplay("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", &globalContext, &store); - assertCalculationDisplay("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "-2", &globalContext, &store); - assertCalculationDisplay("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "4", &globalContext, &store); - assertCalculationDisplay("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", &globalContext, &store); + assertCalculationIs("1+𝐢", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "1+𝐢", "1+𝐢", &globalContext, &store); + assertCalculationIs("√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, "unreal", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("ln(-2)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store); + assertCalculationIs("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store); + assertCalculationIs("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "-2", "-2", &globalContext, &store); + assertCalculationIs("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "4", "4", &globalContext, &store); + assertCalculationIs("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "unreal", "unreal", &globalContext, &store); Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Cartesian); - assertCalculationDisplay("1+𝐢", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "1+𝐢", &globalContext, &store); - assertCalculationDisplay("√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "𝐢", &globalContext, &store); - assertCalculationDisplay("ln(-2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ln(-2)", nullptr, &globalContext, &store); - assertCalculationDisplay("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "-1", &globalContext, &store); - assertCalculationDisplay("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "1+√(3)×𝐢", nullptr, &globalContext, &store); - assertCalculationDisplay("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "-2+2×√(3)×𝐢", nullptr, &globalContext, &store); - assertCalculationDisplay("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "root(8,4)/2+root(8,4)/2×𝐢", nullptr, &globalContext, &store); + assertCalculationIs("1+𝐢", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "1+𝐢", "1+𝐢", &globalContext, &store); + assertCalculationIs("√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "𝐢", "𝐢", &globalContext, &store); + assertCalculationIs("ln(-2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ln(-2)", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ApproximateOnly, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "-1", "-1", &globalContext, &store); + assertCalculationIs("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "1+√(3)×𝐢", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "-2+2×√(3)×𝐢", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "root(8,4)/2+root(8,4)/2×𝐢", nullptr, nullptr, &globalContext, &store); Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Polar); - assertCalculationDisplay("1+𝐢", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(2)×ℯ^\u0012π/4×𝐢\u0013", nullptr, &globalContext, &store); - assertCalculationDisplay("√(-1)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ℯ^\u0012π/2×𝐢\u0013", nullptr, &globalContext, &store); - assertCalculationDisplay("ln(-2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ln(-2)", nullptr, &globalContext, &store); - assertCalculationDisplay("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "ℯ^\u00123.1415926535898×𝐢\u0013", &globalContext, &store); - assertCalculationDisplay("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "2×ℯ^\u0012π/3×𝐢\u0013", nullptr, &globalContext, &store); - assertCalculationDisplay("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "4×ℯ^\u0012\u00122×π\u0013/3×𝐢\u0013", nullptr, &globalContext, &store); - assertCalculationDisplay("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "root(2,4)×ℯ^\u0012π/4×𝐢\u0013", nullptr, &globalContext, &store); + assertCalculationIs("1+𝐢", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "√(2)×ℯ^\u0012π/4×𝐢\u0013", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("√(-1)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ℯ^\u0012π/2×𝐢\u0013", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("ln(-2)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "ln(-2)", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("√(-1)×√(-1)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, "ℯ^\u00123.141593×𝐢\u0013", "ℯ^\u00123.1415926535898×𝐢\u0013", &globalContext, &store); + assertCalculationIs("(-8)^(1/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "2×ℯ^\u0012π/3×𝐢\u0013", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("(-8)^(2/3)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "4×ℯ^\u0012\u00122×π\u0013/3×𝐢\u0013", nullptr, nullptr, &globalContext, &store); + assertCalculationIs("(-2)^(1/4)", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Approximation, "root(2,4)×ℯ^\u0012π/4×𝐢\u0013", nullptr, nullptr, &globalContext, &store); Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Cartesian); } diff --git a/apps/code/app.cpp b/apps/code/app.cpp index deb081b0c..665a09ce4 100644 --- a/apps/code/app.cpp +++ b/apps/code/app.cpp @@ -109,6 +109,10 @@ bool App::handleEvent(Ion::Events::Event event) { return false; } +void App::willExitResponderChain(Responder * nextFirstResponder) { + m_menuController.willExitApp(); +} + Toolbox * App::toolboxForInputEventHandler(InputEventHandler * textInput) { return &m_toolbox; } diff --git a/apps/code/app.h b/apps/code/app.h index 4033a99cc..024e1e12f 100644 --- a/apps/code/app.h +++ b/apps/code/app.h @@ -53,6 +53,7 @@ public: /* Responder */ bool handleEvent(Ion::Events::Event event) override; + void willExitResponderChain(Responder * nextFirstResponder) override; /* InputEventHandlerDelegate */ Toolbox * toolboxForInputEventHandler(InputEventHandler * textInput) override; @@ -69,13 +70,15 @@ public: void deinitPython(); VariableBoxController * variableBoxController() { return &m_variableBoxController; } + + static constexpr int k_pythonHeapSize = 16384; + private: /* Python delegate: * MicroPython requires a heap. To avoid dynamic allocation, we keep a working * buffer here and we give to controllers that load Python environment. We * also memoize the last Python user to avoid re-initiating MicroPython when * unneeded. */ - static constexpr int k_pythonHeapSize = 32768; // Default value: 16384 char m_pythonHeap[k_pythonHeapSize]; const void * m_pythonUser; diff --git a/apps/code/catalog.de.i18n b/apps/code/catalog.de.i18n index d7425f15a..4fc15ea09 100644 --- a/apps/code/catalog.de.i18n +++ b/apps/code/catalog.de.i18n @@ -12,6 +12,7 @@ PythonSingleQuote = "Einfaches Anführungszeichen" PythonAbs = "Absolute/r Wert/Größe" PythonAcos = "Arkuskosinus" PythonAcosh = "Hyperbelkosinus" +PythonAppend = "Add x to the end of the list" PythonAsin = "Arkussinus" PythonAsinh = "Hyperbelsinus" PythonAtan = "Arkustangens" @@ -20,19 +21,21 @@ PythonAtanh = "Hyperbeltangens" PythonBin = "Ganzzahl nach binär konvertieren" PythonCeil = "Aufrundung" PythonChoice = "Zufallszahl aus der Liste" +PythonClear = "Empty the list" PythonCmathFunction = "cmath-Modul-Funktionspräfix" PythonColor = "Definiere eine RGB-Farbe" PythonComplex = "a+ib zurückgeben" PythonCopySign = "Return x with the sign of y" PythonCos = "Kosinus" 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)" PythonConstantE = "2.718281828459046" PythonErf = "Error function" PythonErfc = "Complementary error function" -PythonEval = "Returns the evaluated expression" +PythonEval = "Return the evaluated expression" PythonExp = "Exponential function" PythonExpm1 = "Compute exp(x)-1" PythonFabs = "Absolute value" @@ -46,21 +49,68 @@ PythonGetPixel = "Return pixel (x,y) color" PythonGetrandbits = "Integer with k random bits" PythonHex = "Convert integer to hexadecimal" PythonImportCmath = "Import cmath module" +PythonImportIon = "Import ion module" PythonImportKandinsky = "Import kandinsky module" PythonImportRandom = "Import random module" PythonImportMath = "Import math module" +PythonImportTime = "Import time module" PythonImportTurtle = "Import turtle module" -PythonImportFromCmath = "Import cmath module" -PythonImportFromKandinsky = "Import kandinsky module" -PythonImportFromRandom = "Import random module" -PythonImportFromMath = "Import math module" -PythonImportFromTurtle = "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" PythonIsNaN = "Check if x is a NaN" +PythonIsKeyDown = "Return True if the k key is down" 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" @@ -71,10 +121,12 @@ PythonMathFunction = "math module function 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" PythonConstantPi = "3.141592653589794" 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" @@ -85,16 +137,20 @@ PythonRandrange = "Random number in range(start, stop)" PythonRangeStartStop = "List from start to stop-1" PythonRangeStop = "List from 0 to stop-1" PythonRect = "z in cartesian coordinates" +PythonRemove = "Remove the first occurrence of x" +PythonReverse = "Reverse the elements of the list" PythonRound = "Round to n digits" PythonSeed = "Initialize random number generator" PythonSetPixel = "Color pixel (x,y)" PythonSin = "Sine" PythonSinh = "Hyperbolic sine" -PythonSorted = "Sort a list" +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" +PythonTimeFunction = "time module function prefix" PythonTrunc = "x truncated to an integer" PythonTurtleBackward = "Move backward by x pixels" PythonTurtleBlack = "Black color" diff --git a/apps/code/catalog.en.i18n b/apps/code/catalog.en.i18n index 150277610..88a0c6954 100644 --- a/apps/code/catalog.en.i18n +++ b/apps/code/catalog.en.i18n @@ -12,6 +12,7 @@ PythonSingleQuote = "Single quote" PythonAbs = "Absolute value/Magnitude" PythonAcos = "Arc cosine" PythonAcosh = "Arc hyperbolic cosine" +PythonAppend = "Add x to the end of the list" PythonAsin = "Arc sine" PythonAsinh = "Arc hyperbolic sine" PythonAtan = "Arc tangent" @@ -20,19 +21,21 @@ PythonAtanh = "Arc hyperbolic tangent" 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)" PythonConstantE = "2.718281828459046" PythonErf = "Error function" PythonErfc = "Complementary error function" -PythonEval = "Returns the evaluated expression" +PythonEval = "Return the evaluated expression" PythonExp = "Exponential function" PythonExpm1 = "Compute exp(x)-1" PythonFabs = "Absolute value" @@ -46,21 +49,68 @@ PythonGetPixel = "Return pixel (x,y) color" PythonGetrandbits = "Integer with k random bits" PythonHex = "Convert integer to hexadecimal" PythonImportCmath = "Import cmath module" +PythonImportIon = "Import ion module" PythonImportKandinsky = "Import kandinsky module" PythonImportRandom = "Import random module" PythonImportMath = "Import math module" +PythonImportTime = "Import time module" PythonImportTurtle = "Import turtle module" -PythonImportFromCmath = "Import cmath module" -PythonImportFromKandinsky = "Import kandinsky module" -PythonImportFromRandom = "Import random module" -PythonImportFromMath = "Import math module" -PythonImportFromTurtle = "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" @@ -71,10 +121,12 @@ PythonMathFunction = "math module function 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" PythonConstantPi = "3.141592653589794" 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" @@ -85,16 +137,20 @@ PythonRandrange = "Random number in range(start, stop)" PythonRangeStartStop = "List from start to stop-1" PythonRangeStop = "List from 0 to stop-1" PythonRect = "z in cartesian coordinates" +PythonRemove = "Remove the first occurrence of x" +PythonReverse = "Reverse the elements of the list" PythonRound = "Round to n digits" PythonSeed = "Initialize random number generator" PythonSetPixel = "Color pixel (x,y)" PythonSin = "Sine" PythonSinh = "Hyperbolic sine" -PythonSorted = "Sort a list" +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" +PythonTimeFunction = "time module function prefix" PythonTrunc = "x truncated to an integer" PythonTurtleBackward = "Move backward by x pixels" PythonTurtleBlack = "Black color" diff --git a/apps/code/catalog.es.i18n b/apps/code/catalog.es.i18n index fbd41a5ed..ae9bb0b9f 100644 --- a/apps/code/catalog.es.i18n +++ b/apps/code/catalog.es.i18n @@ -12,6 +12,7 @@ PythonSingleQuote = "Single quote" PythonAbs = "Absolute value/Magnitude" PythonAcos = "Arc cosine" PythonAcosh = "Arc hyperbolic cosine" +PythonAppend = "Add x to the end of the list" PythonAsin = "Arc sine" PythonAsinh = "Arc hyperbolic sine" PythonAtan = "Arc tangent" @@ -20,19 +21,21 @@ PythonAtanh = "Arc hyperbolic tangent" 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)" PythonConstantE = "2.718281828459046" PythonErf = "Error function" PythonErfc = "Complementary error function" -PythonEval = "Returns the evaluated expression" +PythonEval = "Return the evaluated expression" PythonExp = "Exponential function" PythonExpm1 = "Compute exp(x)-1" PythonFabs = "Absolute value" @@ -46,21 +49,68 @@ PythonGetPixel = "Return pixel (x,y) color" PythonGetrandbits = "Integer with k random bits" PythonHex = "Convert integer to hexadecimal" PythonImportCmath = "Import cmath module" +PythonImportIon = "Import ion module" PythonImportKandinsky = "Import kandinsky module" PythonImportRandom = "Import random module" PythonImportMath = "Import math module" +PythonImportTime = "Import time module" PythonImportTurtle = "Import turtle module" -PythonImportFromCmath = "Import cmath module" -PythonImportFromKandinsky = "Import kandinsky module" -PythonImportFromRandom = "Import random module" -PythonImportFromMath = "Import math module" -PythonImportFromTurtle = "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" @@ -71,10 +121,12 @@ PythonMathFunction = "math module function 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" PythonConstantPi = "3.141592653589794" 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" @@ -85,16 +137,20 @@ PythonRandrange = "Random number in range(start, stop)" PythonRangeStartStop = "List from start to stop-1" PythonRangeStop = "List from 0 to stop-1" PythonRect = "z in cartesian coordinates" +PythonRemove = "Remove the first occurrence of x" +PythonReverse = "Reverse the elements of the list" PythonRound = "Round to n digits" PythonSeed = "Initialize random number generator" PythonSetPixel = "Color pixel (x,y)" PythonSin = "Sine" PythonSinh = "Hyperbolic sine" -PythonSorted = "Sort a list" +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" +PythonTimeFunction = "time module function prefix" PythonTrunc = "x truncated to an integer" PythonTurtleBackward = "Move backward by x pixels" PythonTurtleBlack = "Black color" diff --git a/apps/code/catalog.fr.i18n b/apps/code/catalog.fr.i18n index 96f87b00e..4a98637cd 100644 --- a/apps/code/catalog.fr.i18n +++ b/apps/code/catalog.fr.i18n @@ -12,6 +12,7 @@ PythonSingleQuote = "Apostrophe" PythonAbs = "Valeur absolue/Module" PythonAcos = "Arc cosinus" PythonAcosh = "Arc cosinus hyperbolique" +PythonAppend = "Insère x à la fin de la liste" PythonAsin = "Arc sinus" PythonAsinh = "Arc sinus hyperbolique" PythonAtan = "Arc tangente" @@ -20,12 +21,14 @@ PythonAtanh = "Arc tangente hyperbolique" PythonBin = "Conversion d'un entier en binaire" PythonCeil = "Plafond" PythonChoice = "Nombre aléatoire dans la liste" +PythonClear = "Vide la liste" PythonCmathFunction = "Préfixe fonction du module cmath" PythonColor = "Définit une couleur rvb" PythonComplex = "Renvoie a+ib" PythonCopySign = "Renvoie x avec le signe de y" PythonCos = "Cosinus" PythonCosh = "Cosinus hyperbolique" +PythonCount = "Compte les occurrences de x" PythonDegrees = "Conversion de radians en degrés" PythonDivMod = "Quotient et reste" PythonDrawString = "Affiche un texte au pixel (x,y)" @@ -46,21 +49,68 @@ PythonGetPixel = "Renvoie la couleur du pixel (x,y)" PythonGetrandbits = "Nombre aléatoire sur k bits" PythonHex = "Conversion entier en hexadécimal" PythonImportCmath = "Importation du module cmath" +PythonImportIon = "Importation du module ion" PythonImportKandinsky = "Importation du module kandinsky" PythonImportRandom = "Importation du module random" PythonImportMath = "Importation du module math" PythonImportTurtle = "Importation du module turtle" -PythonImportFromCmath = "Importation du module cmath" -PythonImportFromKandinsky = "Importation du module kandinsky" -PythonImportFromRandom = "Importation du module random" -PythonImportFromMath = "Importation du module math" -PythonImportFromTurtle = "Importation du module turtle" +PythonImportTime = "Importation du module time" +PythonIndex = "Indice première occurrence de x" PythonInput = "Entrer une valeur" +PythonInsert = "Insère x en i-ème position" PythonInt = "Conversion en entier" +PythonIonFunction = "Préfixe fonction module ion" PythonIsFinite = "Teste si x est fini" PythonIsInfinite = "Teste si x est infini" +PythonIsKeyDown = "Renvoie True si touche k enfoncée" PythonIsNaN = "Teste si x est NaN" PythonKandinskyFunction = "Préfixe fonction module kandinsky" +PythonKeyLeft = "Touche FLECHE GAUCHE" +PythonKeyUp = "Touche FLECHE HAUT" +PythonKeyDown = "Touche FLECHE BAS" +PythonKeyRight = "Touche FLECHE DROITE" +PythonKeyOk = "Touche OK" +PythonKeyBack = "Touche RETOUR" +PythonKeyHome = "Touche HOME" +PythonKeyOnOff = "Touche ON/OFF" +PythonKeyShift = "Touche SHIFT" +PythonKeyAlpha = "Touche ALPHA" +PythonKeyXnt = "Touche X,N,T" +PythonKeyVar = "Touche VAR" +PythonKeyToolbox = "Touche BOITE A OUTILS" +PythonKeyBackspace = "Touche EFFACER" +PythonKeyExp = "Touche EXPONENTIELLE" +PythonKeyLn = "Touche LOGARITHME NEPERIEN" +PythonKeyLog = "Touche LOGARITHME DECIMAL" +PythonKeyImaginary = "Touche I IMAGINAIRE" +PythonKeyComma = "Touche VIRGULE" +PythonKeyPower = "Touche PUISSANCE" +PythonKeySine = "Touche SINUS" +PythonKeyCosine = "Touche COSINUS" +PythonKeyTangent = "Touche TANGENTE" +PythonKeyPi = "Touche PI" +PythonKeySqrt = "Touche RACINE CARREE" +PythonKeySquare = "Touche CARRE" +PythonKeySeven = "Touche 7" +PythonKeyEight = "Touche 8" +PythonKeyNine = "Touche 9" +PythonKeyLeftParenthesis = "Touche PARENTHESE GAUCHE" +PythonKeyRightParenthesis = "Touche PARENTHESE DROITE" +PythonKeyFour = "Touche 4" +PythonKeyFive = "Touche 5" +PythonKeySix = "Touche 6" +PythonKeyMultiplication = "Touche MULTIPLICATION" +PythonKeyDivision = "Touche DIVISION" +PythonKeyOne = "Touche 1" +PythonKeyTwo = "Touche 2" +PythonKeyThree = "Touche 3" +PythonKeyPlus = "Touche PLUS" +PythonKeyMinus = "Touche MOINS" +PythonKeyZero = "Touche 0" +PythonKeyDot = "Touche POINT" +PythonKeyEe = "Touche 10 PUISSANCE X" +PythonKeyAns = "Touche ANS" +PythonKeyExe = "Touche EXE" PythonLdexp = "Inverse de frexp : x*(2**i)" PythonLength = "Longueur d'un objet" PythonLgamma = "Logarithme de la fonction gamma" @@ -71,10 +121,12 @@ PythonMathFunction = "Préfixe fonction du module math" PythonMax = "Maximum" PythonMin = "Minimum" PythonModf = "Parties fractionnaire et entière" +PythonMonotonic = "Renvoie la valeur de l'horloge" PythonOct = "Conversion en octal" PythonPhase = "Argument de z" PythonConstantPi = "3.141592653589793" PythonPolar = "Conversion en polaire" +PythonPop = "Supprime le dernier élément" PythonPower = "x à la puissance y" PythonPrint = "Affiche l'objet" PythonRadians = "Conversion de degrés en radians" @@ -85,16 +137,20 @@ PythonRandrange = "Nombre dans range(start, stop)" PythonRangeStartStop = "Liste de start à stop-1" PythonRangeStop = "Liste de 0 à stop-1" PythonRect = "Conversion en algébrique" +PythonRemove = "Supprime le premier x de la liste" +PythonReverse = "Inverse les éléments de la liste" PythonRound = "Arrondi à n décimales" PythonSeed = "Initialiser générateur aléatoire" PythonSetPixel = "Colore le pixel (x,y)" PythonSin = "Sinus" PythonSinh = "Sinus hyperbolique" -PythonSorted = "Tri d'une liste" +PythonSleep = "Suspend l'exécution t secondes" +PythonSort = "Trie la liste" PythonSqrt = "Racine carrée" -PythonSum = "Somme des éléments d'une liste" +PythonSum = "Somme des éléments de la liste" PythonTan = "Tangente" PythonTanh = "Tangente hyperbolique" +PythonTimeFunction = "Préfixe fonction module time" PythonTrunc = "Troncature entière" PythonTurtleBackward = "Recule de x pixels" PythonTurtleBlack = "Couleur noire" @@ -116,7 +172,7 @@ PythonTurtlePendown = "Abaisse le crayon" PythonTurtlePensize = "Taille du tracé en pixels" PythonTurtlePenup = "Relève le crayon" PythonTurtlePink = "Couleur rose" -PythonTurtlePosition = "Renvoie la position (x,y) actuelle" +PythonTurtlePosition = "Renvoie la position (x,y)" PythonTurtlePurple = "Couleur violette" PythonTurtleRed = "Couleur rouge" PythonTurtleReset = "Réinitialise le dessin" diff --git a/apps/code/catalog.pt.i18n b/apps/code/catalog.pt.i18n index ad3f43d97..bc0c946ce 100644 --- a/apps/code/catalog.pt.i18n +++ b/apps/code/catalog.pt.i18n @@ -12,6 +12,7 @@ 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" PythonAsin = "Arc sine" PythonAsinh = "Arc hyperbolic sine" PythonAtan = "Arc tangent" @@ -20,19 +21,21 @@ PythonAtanh = "Arc hyperbolic tangent" 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)" PythonConstantE = "2.718281828459046" PythonErf = "Error function" PythonErfc = "Complementary error function" -PythonEval = "Returns the evaluated expression" +PythonEval = "Return the evaluated expression" PythonExp = "Exponential function" PythonExpm1 = "Compute exp(x)-1" PythonFabs = "Absolute value" @@ -46,21 +49,68 @@ PythonGetPixel = "Return pixel (x,y) color" PythonGetrandbits = "Integer with k random bits" PythonHex = "Convert integer to hexadecimal" PythonImportCmath = "Import cmath module" +PythonImportIon = "Import ion module" PythonImportKandinsky = "Import kandinsky module" PythonImportRandom = "Import random module" PythonImportMath = "Import math module" +PythonImportTime = "Import time module" PythonImportTurtle = "Import turtle module" -PythonImportFromCmath = "Import cmath module" -PythonImportFromKandinsky = "Import kandinsky module" -PythonImportFromRandom = "Import random module" -PythonImportFromMath = "Import math module" -PythonImportFromTurtle = "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" @@ -71,10 +121,12 @@ PythonMathFunction = "math module function 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" PythonConstantPi = "3.141592653589794" 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" @@ -85,16 +137,20 @@ PythonRandrange = "Random number in range(start, stop)" PythonRangeStartStop = "List from start to stop-1" PythonRangeStop = "List from 0 to stop-1" PythonRect = "z in cartesian coordinates" +PythonRemove = "Remove the first occurrence of x" +PythonReverse = "Reverse the elements of the list" PythonRound = "Round to n digits" PythonSeed = "Initialize random number generator" PythonSetPixel = "Color pixel (x,y)" PythonSin = "Sine" PythonSinh = "Hyperbolic sine" -PythonSorted = "Sort a list" +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" +PythonTimeFunction = "time module function prefix" PythonTrunc = "x truncated to an integer" PythonTurtleBackward = "Move backward by x pixels" PythonTurtleBlack = "Black color" diff --git a/apps/code/catalog.universal.i18n b/apps/code/catalog.universal.i18n index e331df515..1b73fc358 100644 --- a/apps/code/catalog.universal.i18n +++ b/apps/code/catalog.universal.i18n @@ -10,6 +10,8 @@ PythonCommand1J = "1j" PythonCommandAbs = "abs(x)" PythonCommandAcos = "acos(x)" PythonCommandAcosh = "acosh(x)" +PythonCommandAppend = "list.append(x)" +PythonCommandAppendWithoutArg = ".append(\x11)" PythonCommandAsin = "asin(x)" PythonCommandAsinh = "asinh(x)" PythonCommandAtan = "atan(x)" @@ -18,6 +20,8 @@ PythonCommandAtanh = "atanh(x)" PythonCommandBin = "bin(x)" PythonCommandCeil = "ceil(x)" PythonCommandChoice = "choice(list)" +PythonCommandClear = "list.clear()" +PythonCommandClearWithoutArg = ".clear()" PythonCommandCmathFunction = "cmath.function" PythonCommandCmathFunctionWithoutArg = "cmath.\x11" PythonCommandColor = "color(r,g,b)" @@ -27,6 +31,8 @@ PythonCommandCopySign = "copysign(x,y)" PythonCommandCos = "cos(x)" PythonCommandCosComplex = "cos(z)" PythonCommandCosh = "cosh(x)" +PythonCommandCount = "list.count(x)" +PythonCommandCountWithoutArg = ".count(\x11)" PythonCommandDegrees = "degrees(x)" PythonCommandDivMod = "divmod(a,b)" PythonCommandDrawString = "draw_string(\"text\",x,y)" @@ -48,24 +54,81 @@ PythonCommandGetPixel = "get_pixel(x,y)" PythonCommandGetrandbits = "getrandbits(k)" PythonCommandHex = "hex(x)" PythonCommandImag = "z.imag" -PythonCommandImagWithoutArg = "\x11.imag" +PythonCommandImagWithoutArg = ".imag" PythonCommandImportFromCmath = "from cmath import *" -PythonCommandImportFromMath = "from math import *" +PythonCommandImportFromIon = "from ion import *" PythonCommandImportFromKandinsky = "from kandinsky import *" +PythonCommandImportFromMath = "from math import *" PythonCommandImportFromRandom = "from random import *" +PythonCommandImportFromTime = "from time import *" PythonCommandImportFromTurtle = "from turtle import *" PythonCommandImportCmath = "import cmath" +PythonCommandImportIon = "import ion" PythonCommandImportKandinsky = "import kandinsky" -PythonCommandImportRandom = "import random" PythonCommandImportMath = "import math" +PythonCommandImportRandom = "import random" +PythonCommandImportTime = "import time" PythonCommandImportTurtle = "import turtle" +PythonCommandIndex = "list.index(x)" +PythonCommandIndexWithoutArg = ".index(\x11)" PythonCommandInput = "input(\"text\")" +PythonCommandInsert = "list.insert(i,x)" +PythonCommandInsertWithoutArg = ".insert(\x11,)" PythonCommandInt = "int(x)" +PythonCommandIonFunction = "ion.function" +PythonCommandIonFunctionWithoutArg = "ion.\x11" PythonCommandIsFinite = "isfinite(x)" PythonCommandIsInfinite = "isinf(x)" PythonCommandIsNaN = "isnan(x)" PythonCommandKandinskyFunction = "kandinsky.function" PythonCommandKandinskyFunctionWithoutArg = "kandinsky.\x11" +PythonCommandKeyLeft = "KEY_LEFT" +PythonCommandKeyUp = "KEY_UP" +PythonCommandKeyDown = "KEY_DOWN" +PythonCommandKeyRight = "KEY_RIGHT" +PythonCommandKeyOk = "KEY_OK" +PythonCommandKeyBack = "KEY_BACK" +PythonCommandKeyHome = "KEY_HOME" +PythonCommandKeyOnOff = "KEY_ONOFF" +PythonCommandKeyShift = "KEY_SHIFT" +PythonCommandKeyAlpha = "KEY_ALPHA" +PythonCommandKeyXnt = "KEY_XNT" +PythonCommandKeyVar = "KEY_VAR" +PythonCommandKeyToolbox = "KEY_TOOLBOX" +PythonCommandKeyBackspace = "KEY_BACKSPACE" +PythonCommandKeyExp = "KEY_EXP" +PythonCommandKeyLn = "KEY_LN" +PythonCommandKeyLog = "KEY_LOG" +PythonCommandKeyImaginary = "KEY_IMAGINARY" +PythonCommandKeyComma = "KEY_COMMA" +PythonCommandKeyPower = "KEY_POWER" +PythonCommandKeySine = "KEY_SINE" +PythonCommandKeyCosine = "KEY_COSINE" +PythonCommandKeyTangent = "KEY_TANGENT" +PythonCommandKeyPi = "KEY_PI" +PythonCommandKeySqrt = "KEY_SQRT" +PythonCommandKeySquare = "KEY_SQUARE" +PythonCommandKeySeven = "KEY_SEVEN" +PythonCommandKeyEight = "KEY_EIGHT" +PythonCommandKeyNine = "KEY_NINE" +PythonCommandKeyLeftParenthesis = "KEY_LEFTPARENTHESIS" +PythonCommandKeyRightParenthesis = "KEY_RIGHTPARENTHESIS" +PythonCommandKeyFour = "KEY_FOUR" +PythonCommandKeyFive = "KEY_FIVE" +PythonCommandKeySix = "KEY_SIX" +PythonCommandKeyMultiplication = "KEY_MULTIPLICATION" +PythonCommandKeyDivision = "KEY_DIVISION" +PythonCommandKeyOne = "KEY_ONE" +PythonCommandKeyTwo = "KEY_TWO" +PythonCommandKeyThree = "KEY_THREE" +PythonCommandKeyPlus = "KEY_PLUS" +PythonCommandKeyMinus = "KEY_MINUS" +PythonCommandKeyZero = "KEY_ZERO" +PythonCommandKeyDot = "KEY_DOT" +PythonCommandKeyEe = "KEY_EE" +PythonCommandKeyAns = "KEY_ANS" +PythonCommandKeyExe = "KEY_EXE" +PythonCommandIsKeyDown = "keydown(k)" PythonCommandLdexp = "ldexp(x,i)" PythonCommandLength = "len(object)" PythonCommandLgamma = "lgamma(x)" @@ -78,9 +141,12 @@ PythonCommandMathFunctionWithoutArg = "math.\x11" PythonCommandMax = "max(list)" PythonCommandMin = "min(list)" PythonCommandModf = "modf(x)" +PythonCommandMonotonic = "monotonic()" PythonCommandOct = "oct(x)" PythonCommandPhase = "phase(z)" PythonCommandPolar = "polar(z)" +PythonCommandPop = "list.pop()" +PythonCommandPopWithoutArg = ".pop()" PythonCommandPower = "pow(x,y)" PythonCommandPrint = "print(object)" PythonCommandRadians = "radians(x)" @@ -92,20 +158,29 @@ PythonCommandRandrange = "randrange(start, stop)" PythonCommandRangeStartStop = "range(start, stop)" PythonCommandRangeStop = "range(stop)" PythonCommandReal = "z.real" -PythonCommandRealWithoutArg = "\x11.real" +PythonCommandRealWithoutArg = ".real" PythonCommandRect = "rect(r, arg)" +PythonCommandRemove = "list.remove(x)" +PythonCommandRemoveWithoutArg = ".remove(\x11)" +PythonCommandReverse = "list.reverse()" +PythonCommandReverseWithoutArg = ".reverse()" PythonCommandRound = "round(x, n)" PythonCommandSeed = "seed(x)" PythonCommandSetPixel = "set_pixel(x,y,color)" PythonCommandSin = "sin(x)" PythonCommandSinComplex = "sin(z)" PythonCommandSinh = "sinh(x)" +PythonCommandSleep = "sleep(t)" +PythonCommandSort = "list.sort()" +PythonCommandSortWithoutArg = ".sort()" PythonCommandSorted = "sorted(list)" PythonCommandSqrt = "sqrt(x)" PythonCommandSqrtComplex = "sqrt(z)" PythonCommandSum = "sum(list)" PythonCommandTan = "tan(x)" PythonCommandTanh = "tanh(x)" +PythonCommandTimeFunction = "time.function" +PythonCommandTimeFunctionWithoutArg = "time.\x11" PythonCommandTrunc = "trunc(x)" PythonCommandTurtleFunction = "turtle.function" PythonCommandTurtleFunctionWithoutArg = "turtle.\x11" diff --git a/apps/code/console_controller.cpp b/apps/code/console_controller.cpp index 9cd6b1f86..200cc8e65 100644 --- a/apps/code/console_controller.cpp +++ b/apps/code/console_controller.cpp @@ -6,7 +6,9 @@ #include #include #include -#include "../apps_container.h" +#include +#include +#include extern "C" { #include @@ -28,13 +30,13 @@ ConsoleController::ConsoleController(Responder * parentResponder, App * pythonDe TextFieldDelegate(), MicroPython::ExecutionEnvironment(), m_pythonDelegate(pythonDelegate), - m_rowHeight(Poincare::Preferences::sharedPreferences()->KDPythonFont()->glyphSize().height()), m_importScriptsWhenViewAppears(false), m_selectableTableView(this, this, this, this), m_editCell(this, pythonDelegate, this), m_scriptStore(scriptStore), m_sandboxController(this, this), - m_inputRunLoopActive(false) + m_inputRunLoopActive(false), + m_preventEdition(false) #if EPSILON_GETOPT , m_locked(lockOnConsole) #endif @@ -76,10 +78,21 @@ void ConsoleController::autoImport() { } void ConsoleController::runAndPrintForCommand(const char * command) { - m_consoleStore.pushCommand(command, strlen(command)); + const char * storedCommand = m_consoleStore.pushCommand(command); assert(m_outputAccumulationBuffer[0] == '\0'); - runCode(command); + // Draw the console before running the code + m_preventEdition = true; + m_editCell.setText(""); + m_editCell.setPrompt(""); + refreshPrintOutput(); + + runCode(storedCommand); + + m_preventEdition = false; + m_editCell.setPrompt(sStandardPromptText); + m_editCell.setEditing(true); + flushOutputAccumulationBufferToStore(); m_consoleStore.deleteLastLineIfEmpty(); } @@ -119,6 +132,7 @@ const char * ConsoleController::inputText(const char * prompt) { } } + const char * previousPrompt = m_editCell.promptText(); m_editCell.setPrompt(promptText); m_editCell.setText(""); @@ -141,7 +155,9 @@ const char * ConsoleController::inputText(const char * prompt) { printText(text, strlen(text)); flushOutputAccumulationBufferToStore(); - m_editCell.setPrompt(sStandardPromptText); + m_editCell.setPrompt(previousPrompt); + m_editCell.setText(""); + refreshPrintOutput(); return text; } @@ -202,7 +218,7 @@ int ConsoleController::numberOfRows() const { } KDCoordinate ConsoleController::rowHeight(int j) { - return m_rowHeight; + return GlobalPreferences::sharedGlobalPreferences()->font()->glyphSize().height(); } KDCoordinate ConsoleController::cumulatedHeightFromIndex(int j) { @@ -359,6 +375,15 @@ void ConsoleController::resetSandbox() { m_sandboxController.reset(); } +void ConsoleController::refreshPrintOutput() { + m_selectableTableView.reloadData(); + m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines()); + if (m_preventEdition) { + m_editCell.setEditing(false); + } + AppsContainer::sharedAppsContainer()->redrawWindow(); +} + /* printText is called by the Python machine. * The text argument is not always null-terminated. */ void ConsoleController::printText(const char * text, size_t length) { @@ -381,6 +406,7 @@ void ConsoleController::printText(const char * text, size_t length) { assert(textCutIndex == length - 1); appendTextToOutputAccumulationBuffer(text, length-1); flushOutputAccumulationBufferToStore(); + micropython_port_vm_hook_refresh_print(); } void ConsoleController::autoImportScript(Script script, bool force) { @@ -424,7 +450,7 @@ void ConsoleController::autoImportScript(Script script, bool force) { } void ConsoleController::flushOutputAccumulationBufferToStore() { - m_consoleStore.pushResult(m_outputAccumulationBuffer, strlen(m_outputAccumulationBuffer)); + m_consoleStore.pushResult(m_outputAccumulationBuffer); emptyOutputAccumulationBuffer(); } diff --git a/apps/code/console_controller.h b/apps/code/console_controller.h index 3b9d33dd4..807568ae9 100644 --- a/apps/code/console_controller.h +++ b/apps/code/console_controller.h @@ -17,7 +17,6 @@ class App; class ConsoleController : public ViewController, public ListViewDataSource, public SelectableTableViewDataSource, public SelectableTableViewDelegate, public TextFieldDelegate, public MicroPython::ExecutionEnvironment { public: - ConsoleController(Responder * parentResponder, App * pythonDelegate, ScriptStore * scriptStore #if EPSILON_GETOPT , bool m_lockOnConsole @@ -64,6 +63,7 @@ public: void displaySandbox() override; void hideSandbox() override; void resetSandbox() override; + void refreshPrintOutput() override; void printText(const char * text, size_t length) override; const char * inputText(const char * prompt) override; @@ -78,7 +78,8 @@ private: static constexpr size_t k_maxImportCommandSize = 5 + 9 + TextField::maxBufferSize(); // strlen(k_importCommand1) + strlen(k_importCommand2) + TextField::maxBufferSize() static constexpr int LineCellType = 0; static constexpr int EditCellType = 1; - static constexpr int k_numberOfLineCells = 20; // May change depending on the screen height + static constexpr int k_numberOfLineCells = (Ion::Display::Height - Metric::TitleBarHeight) / 14 + 2; // 14 = KDFont::SmallFont->glyphSize().height() + // k_numberOfLineCells = (240 - 18)/14 ~ 15.9. The 0.1 cell can be above and below the 15 other cells so we add +2 cells. static constexpr int k_outputAccumulationBufferSize = 100; void flushOutputAccumulationBufferToStore(); void appendTextToOutputAccumulationBuffer(const char * text, size_t length); @@ -86,7 +87,6 @@ private: size_t firstNewLineCharIndex(const char * text, size_t length); StackViewController * stackViewController(); App * m_pythonDelegate; - int m_rowHeight; bool m_importScriptsWhenViewAppears; ConsoleStore m_consoleStore; SelectableTableView m_selectableTableView; @@ -102,6 +102,7 @@ private: SandboxController m_sandboxController; bool m_inputRunLoopActive; bool m_autoImportScripts; + bool m_preventEdition; #if EPSILON_GETOPT bool m_locked; #endif diff --git a/apps/code/console_edit_cell.cpp b/apps/code/console_edit_cell.cpp index 9eb431206..e5a4a2708 100644 --- a/apps/code/console_edit_cell.cpp +++ b/apps/code/console_edit_cell.cpp @@ -2,6 +2,7 @@ #include "console_controller.h" #include #include +#include #include namespace Code { @@ -9,8 +10,8 @@ namespace Code { ConsoleEditCell::ConsoleEditCell(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, TextFieldDelegate * delegate) : HighlightCell(), Responder(parentResponder), - m_promptView(Poincare::Preferences::sharedPreferences()->KDPythonFont(), nullptr, 0, 0.5), - m_textField(this, nullptr, TextField::maxBufferSize(), TextField::maxBufferSize(), inputEventHandlerDelegate, delegate, Poincare::Preferences::sharedPreferences()->KDPythonFont()) + m_promptView(GlobalPreferences::sharedGlobalPreferences()->font(), nullptr, 0, 0.5), + m_textField(this, nullptr, TextField::maxBufferSize(), TextField::maxBufferSize(), inputEventHandlerDelegate, delegate, GlobalPreferences::sharedGlobalPreferences()->font()) { } @@ -27,10 +28,10 @@ View * ConsoleEditCell::subviewAtIndex(int index) { } } -void ConsoleEditCell::layoutSubviews() { +void ConsoleEditCell::layoutSubviews(bool force) { KDSize promptSize = m_promptView.minimalSizeForOptimalDisplay(); - m_promptView.setFrame(KDRect(KDPointZero, promptSize.width(), bounds().height())); - m_textField.setFrame(KDRect(KDPoint(promptSize.width(), KDCoordinate(0)), bounds().width() - promptSize.width(), bounds().height())); + m_promptView.setFrame(KDRect(KDPointZero, promptSize.width(), bounds().height()), force); + m_textField.setFrame(KDRect(KDPoint(promptSize.width(), KDCoordinate(0)), bounds().width() - promptSize.width(), bounds().height()), force); } void ConsoleEditCell::didBecomeFirstResponder() { diff --git a/apps/code/console_edit_cell.h b/apps/code/console_edit_cell.h index 378beb8c4..fb781565e 100644 --- a/apps/code/console_edit_cell.h +++ b/apps/code/console_edit_cell.h @@ -17,7 +17,7 @@ public: // View int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; // Responder void didBecomeFirstResponder() override; @@ -33,6 +33,7 @@ public: void setText(const char * text); bool insertText(const char * text); void setPrompt(const char * prompt); + const char * promptText() const { return m_promptView.text(); } private: PointerTextView m_promptView; TextField m_textField; diff --git a/apps/code/console_line_cell.cpp b/apps/code/console_line_cell.cpp index e24a550c9..458c8eb42 100644 --- a/apps/code/console_line_cell.cpp +++ b/apps/code/console_line_cell.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include namespace Code { @@ -19,11 +19,11 @@ void ConsoleLineCell::ScrollableConsoleLineView::ConsoleLineView::setLine(Consol void ConsoleLineCell::ScrollableConsoleLineView::ConsoleLineView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(bounds(), Palette::CodeBackground); - ctx->drawString(m_line->text(), KDPointZero, Poincare::Preferences::sharedPreferences()->KDPythonFont(), textColor(m_line), isHighlighted()? Palette::CodeBackgroundSelected : Palette::CodeBackground); + ctx->drawString(m_line->text(), KDPointZero, GlobalPreferences::sharedGlobalPreferences()->font(), textColor(m_line), isHighlighted()? Palette::Select : KDColorWhite); } KDSize ConsoleLineCell::ScrollableConsoleLineView::ConsoleLineView::minimalSizeForOptimalDisplay() const { - return Poincare::Preferences::sharedPreferences()->KDPythonFont()->stringSize(m_line->text()); + return GlobalPreferences::sharedGlobalPreferences()->font()->stringSize(m_line->text()); } ConsoleLineCell::ScrollableConsoleLineView::ScrollableConsoleLineView(Responder * parentResponder) : @@ -35,7 +35,7 @@ ConsoleLineCell::ScrollableConsoleLineView::ScrollableConsoleLineView(Responder ConsoleLineCell::ConsoleLineCell(Responder * parentResponder) : HighlightCell(), Responder(parentResponder), - m_promptView(Poincare::Preferences::sharedPreferences()->KDPythonFont(), I18n::Message::ConsolePrompt, 0, 0.5), + m_promptView(GlobalPreferences::sharedGlobalPreferences()->font(), I18n::Message::ConsolePrompt, 0, 0.5), m_scrollableView(this), m_line() { @@ -78,16 +78,16 @@ View * ConsoleLineCell::subviewAtIndex(int index) { return &m_scrollableView; } -void ConsoleLineCell::layoutSubviews() { +void ConsoleLineCell::layoutSubviews(bool force) { if (m_line.isCommand()) { - KDSize promptSize = Poincare::Preferences::sharedPreferences()->KDPythonFont()->stringSize(I18n::translate(I18n::Message::ConsolePrompt)); - m_promptView.setFrame(KDRect(KDPointZero, promptSize.width(), bounds().height())); - m_scrollableView.setFrame(KDRect(KDPoint(promptSize.width(), 0), bounds().width() - promptSize.width(), bounds().height())); + KDSize promptSize = GlobalPreferences::sharedGlobalPreferences()->font()->stringSize(I18n::translate(I18n::Message::ConsolePrompt)); + m_promptView.setFrame(KDRect(KDPointZero, promptSize.width(), bounds().height()), force); + m_scrollableView.setFrame(KDRect(KDPoint(promptSize.width(), 0), bounds().width() - promptSize.width(), bounds().height()), force); return; } assert(m_line.isResult()); - m_promptView.setFrame(KDRectZero); - m_scrollableView.setFrame(bounds()); + m_promptView.setFrame(KDRectZero, force); + m_scrollableView.setFrame(bounds(), force); } void ConsoleLineCell::didBecomeFirstResponder() { diff --git a/apps/code/console_line_cell.h b/apps/code/console_line_cell.h index 73b879d0e..b6c32d6d3 100644 --- a/apps/code/console_line_cell.h +++ b/apps/code/console_line_cell.h @@ -30,7 +30,7 @@ public: /* View */ int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; /* Responder */ void didBecomeFirstResponder() override; diff --git a/apps/code/console_store.cpp b/apps/code/console_store.cpp index cd3d0445d..390ba860e 100644 --- a/apps/code/console_store.cpp +++ b/apps/code/console_store.cpp @@ -55,12 +55,12 @@ int ConsoleStore::numberOfLines() const { return 0; } -void ConsoleStore::pushCommand(const char * text, size_t length) { - push(CurrentSessionCommandMarker, text, length); +const char * ConsoleStore::pushCommand(const char * text) { + return push(CurrentSessionCommandMarker, text); } -void ConsoleStore::pushResult(const char * text, size_t length) { - push(CurrentSessionResultMarker, text, length); +void ConsoleStore::pushResult(const char * text) { + push(CurrentSessionResultMarker, text); } void ConsoleStore::deleteLastLineIfEmpty() { @@ -91,9 +91,9 @@ int ConsoleStore::deleteCommandAndResultsAtIndex(int index) { return indexOfLineToDelete; } -void ConsoleStore::push(const char marker, const char * text, size_t length) { - size_t textLength = length; - if (ConsoleLine::sizeOfConsoleLine(length) > k_historySize - 1) { +const char * ConsoleStore::push(const char marker, const char * text) { + size_t textLength = strlen(text); + if (ConsoleLine::sizeOfConsoleLine(textLength) > k_historySize - 1) { textLength = k_historySize - 1 - 1 - 1; // Marker, null termination and null marker. } int i = indexOfNullMarker(); @@ -105,6 +105,7 @@ void ConsoleStore::push(const char marker, const char * text, size_t length) { m_history[i] = marker; strlcpy(&m_history[i+1], text, minInt(k_historySize-(i+1),textLength+1)); m_history[i+1+textLength+1] = 0; + return &m_history[i+1]; } ConsoleLine::Type ConsoleStore::lineTypeForMarker(char marker) const { diff --git a/apps/code/console_store.h b/apps/code/console_store.h index f5ae9c2b7..bc0e2fc75 100644 --- a/apps/code/console_store.h +++ b/apps/code/console_store.h @@ -14,8 +14,8 @@ public: void startNewSession(); ConsoleLine lineAtIndex(int i) const; int numberOfLines() const; - void pushCommand(const char * text, size_t length); - void pushResult(const char * text, size_t length); + const char * pushCommand(const char * text); + void pushResult(const char * text); void deleteLastLineIfEmpty(); int deleteCommandAndResultsAtIndex(int index); private: @@ -30,7 +30,7 @@ private: } return marker; } - void push(const char marker, const char * text, size_t length); + const char * push(const char marker, const char * text); ConsoleLine::Type lineTypeForMarker(char marker) const; int indexOfNullMarker() const; void deleteLineAtIndex(int index); diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index aba355959..b4fd1ff2a 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -20,20 +20,36 @@ EditorController::EditorController(MenuController * menuController, App * python void EditorController::setScript(Script script) { m_script = script; - Script::Data scriptData = m_script.value(); - size_t availableScriptSize = scriptData.size + Ion::Storage::sharedStorage()->availableSize(); - assert(sizeof(m_areaBuffer) >= availableScriptSize); - // We cannot use strlcpy as the first char reprensenting the importation status can be 0. - memcpy(m_areaBuffer, (const char *)scriptData.buffer, scriptData.size); - m_editorView.setText(m_areaBuffer+1, availableScriptSize-1); // 1 char is taken by the importation status flag + + /* 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 + * its size. + * + * |****|****|m_script|****|**********|¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨| + * available space + * is transformed to: + * + * |****|****|m_script|¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨|****|**********| + * available space + * + * */ + + size_t newScriptSize = Ion::Storage::sharedStorage()->putAvailableSpaceAtEndOfRecord(m_script); + m_editorView.setText(const_cast(m_script.scriptContent()), newScriptSize - Script::k_importationStatusSize); +} + +void EditorController::willExitApp() { + cleanStorageEmptySpace(); } // TODO: this should be done in textAreaDidFinishEditing maybe?? bool EditorController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::OK || event == Ion::Events::Back || event == Ion::Events::Home) { - saveScript(); + if (event == Ion::Events::OK || event == Ion::Events::Back || event == Ion::Events::Home || event == Ion::Events::USBEnumeration) { + /* Exit the edition on USB enumeration, because the storage needs to be in a + * "clean" state (with all records packed at the beginning of the storage) */ + cleanStorageEmptySpace(); stackController()->pop(); - return event != Ion::Events::Home; + return event != Ion::Events::Home && event != Ion::Events::USBEnumeration; } return false; } @@ -48,16 +64,11 @@ void EditorController::viewWillAppear() { } void EditorController::viewDidDisappear() { + m_editorView.resetSelection(); m_menuController->scriptContentEditionDidFinish(); } bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) { - if (event == Ion::Events::Var) { - /* We save the script before displaying the Variable box to add new - * functions or variables. */ - saveScript(); - return false; - } if (App::app()->textInputDidReceiveEvent(textArea, event)) { return true; } @@ -66,7 +77,7 @@ bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events: return true; } - if (event == Ion::Events::Backspace) { + if (event == Ion::Events::Backspace && textArea->selectionIsEmpty()) { /* If the cursor is on the left of the text of a line, backspace one * indentation space at a time. */ const char * text = textArea->text(); @@ -118,11 +129,15 @@ StackViewController * EditorController::stackController() { return static_cast(parentResponder()); } -void EditorController::saveScript() { - size_t sizeOfValue = strlen(m_areaBuffer+1)+1+1; // size of scriptContent + size of importation status - Script::ErrorStatus err = m_script.setValue({.buffer=m_areaBuffer, .size=sizeOfValue}); - assert(err != Script::ErrorStatus::NotEnoughSpaceAvailable && err != Script::ErrorStatus::RecordDoesNotExist); // This should not happen as we set the text area according to the available space in the Kallax - (void) err; +void EditorController::cleanStorageEmptySpace() { + if (m_script.isNull() || !Ion::Storage::sharedStorage()->hasRecord(m_script)) { + return; + } + 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 } + } diff --git a/apps/code/editor_controller.h b/apps/code/editor_controller.h index 284bf1af3..9a45a8f40 100644 --- a/apps/code/editor_controller.h +++ b/apps/code/editor_controller.h @@ -17,6 +17,7 @@ class EditorController : public ViewController, public TextAreaDelegate, public public: EditorController(MenuController * menuController, App * pythonDelegate); void setScript(Script script); + void willExitApp(); /* ViewController */ View * view() override { return &m_editorView; } @@ -33,13 +34,9 @@ public: VariableBoxController * variableBoxForInputEventHandler(InputEventHandler * textInput) override; private: + void cleanStorageEmptySpace(); StackViewController * stackController(); - void saveScript(); EditorView m_editorView; - /* m_areaBuffer first character is dedicated to the importation status. - * Thereby, we avoid wasteful copy while adding the Script to the storage - * (in order to add the importation status char before the areaBuffer). */ - char m_areaBuffer[Ion::Storage::k_storageSize]; // this could be slightly optimize Script m_script; MenuController * m_menuController; }; diff --git a/apps/code/editor_view.cpp b/apps/code/editor_view.cpp index 199a66845..569ff8067 100644 --- a/apps/code/editor_view.cpp +++ b/apps/code/editor_view.cpp @@ -1,4 +1,5 @@ #include "editor_view.h" +#include #include #include #include @@ -10,50 +11,47 @@ namespace Code { EditorView::EditorView(Responder * parentResponder, App * pythonDelegate) : Responder(parentResponder), View(), - m_textArea(parentResponder, pythonDelegate, Poincare::Preferences::sharedPreferences()->KDPythonFont()), - m_gutterView(Poincare::Preferences::sharedPreferences()->KDPythonFont()) + m_textArea(parentResponder, pythonDelegate, GlobalPreferences::sharedGlobalPreferences()->font()), + m_gutterView(GlobalPreferences::sharedGlobalPreferences()->font()) { m_textArea.setScrollViewDelegate(this); } +void EditorView::resetSelection() { + m_textArea.resetSelection(); +} + void EditorView::scrollViewDidChangeOffset(ScrollViewDataSource * scrollViewDataSource) { m_gutterView.setOffset(scrollViewDataSource->offset().y()); } -int EditorView::numberOfSubviews() const { - return 2; -} - View * EditorView::subviewAtIndex(int index) { - View * subviews[] = {&m_textArea, &m_gutterView}; - return subviews[index]; + if (index == 0) { + return &m_textArea; + } + assert(index == 1); + return &m_gutterView; } void EditorView::didBecomeFirstResponder() { Container::activeApp()->setFirstResponder(&m_textArea); } -void EditorView::layoutSubviews() { +void EditorView::layoutSubviews(bool force) { m_gutterView.setOffset(0); KDCoordinate gutterWidth = m_gutterView.minimalSizeForOptimalDisplay().width(); - m_gutterView.setFrame(KDRect(0, 0, gutterWidth, bounds().height())); + m_gutterView.setFrame(KDRect(0, 0, gutterWidth, bounds().height()), force); m_textArea.setFrame(KDRect( - gutterWidth, - 0, - bounds().width()-gutterWidth, - bounds().height() - )); + gutterWidth, + 0, + bounds().width()-gutterWidth, + bounds().height()), + force); } /* EditorView::GutterView */ -EditorView::GutterView::GutterView(const KDFont * font) : - View(), - m_offset(0) -{ -} - void EditorView::GutterView::drawRect(KDContext * ctx, KDRect rect) const { KDColor textColor = KDColor::RGB24(0x919EA4); KDColor backgroundColor = KDColor::RGB24(0xE4E6E7); diff --git a/apps/code/editor_view.h b/apps/code/editor_view.h index 9fb759fca..d963522df 100644 --- a/apps/code/editor_view.h +++ b/apps/code/editor_view.h @@ -9,6 +9,7 @@ namespace Code { class EditorView : public Responder, public View, public ScrollViewDelegate { public: EditorView(Responder * parentResponder, App * pythonDelegate); + void resetSelection(); void setTextAreaDelegates(InputEventHandlerDelegate * inputEventHandlerDelegate, TextAreaDelegate * delegate) { m_textArea.setDelegates(inputEventHandlerDelegate, delegate); } @@ -24,13 +25,13 @@ public: void scrollViewDidChangeOffset(ScrollViewDataSource * scrollViewDataSource) override; void didBecomeFirstResponder() override; private: - int numberOfSubviews() const override; + int numberOfSubviews() const override { return 2; } View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; class GutterView : public View { public: - GutterView(const KDFont * font); + GutterView(const KDFont * font) : View(), m_font(font), m_offset(0) {} void drawRect(KDContext * ctx, KDRect rect) const override; void setOffset(KDCoordinate offset); KDSize minimalSizeForOptimalDisplay() const override; diff --git a/apps/code/menu_controller.cpp b/apps/code/menu_controller.cpp index b53e02c7c..ced77beac 100644 --- a/apps/code/menu_controller.cpp +++ b/apps/code/menu_controller.cpp @@ -169,6 +169,10 @@ void MenuController::scriptContentEditionDidFinish() { reloadConsole(); } +void MenuController::willExitApp() { + m_editorController.willExitApp(); +} + int MenuController::numberOfRows() const { return m_scriptStore->numberOfScripts() + m_shouldDisplayAddScriptRow; } @@ -299,24 +303,6 @@ bool MenuController::textFieldShouldFinishEditing(TextField * textField, Ion::Ev || event == Ion::Events::Down || event == Ion::Events::Up; } -bool MenuController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) { - if (event == Ion::Events::Right - && textField->isEditing() - && textField->cursorLocation() == textField->text() + textField->draftTextLength()) { - return true; - } - if (event == Ion::Events::Clear && textField->isEditing()) { - constexpr size_t k_bufferSize = 4; - char buffer[k_bufferSize] = {'.', 0, 0, 0}; - assert(k_bufferSize >= 1 + strlen(ScriptStore::k_scriptExtension) + 1); - strlcpy(&buffer[1], ScriptStore::k_scriptExtension, strlen(ScriptStore::k_scriptExtension) + 1); - textField->setText(buffer); - textField->setCursorLocation(textField->text()); - return true; - } - return false; -} - bool MenuController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { const char * newName; static constexpr int bufferSize = Script::k_defaultScriptNameMaxSize + 1 + ScriptStore::k_scriptExtensionLength; //"script99" + "." + "py" diff --git a/apps/code/menu_controller.h b/apps/code/menu_controller.h index caa78d2e1..4d57b2e0e 100644 --- a/apps/code/menu_controller.h +++ b/apps/code/menu_controller.h @@ -24,6 +24,7 @@ public: void reloadConsole(); void openConsoleWithScript(Script script); void scriptContentEditionDidFinish(); + void willExitApp(); /* ViewController */ View * view() override { return &m_selectableTableView; } @@ -51,7 +52,7 @@ public: /* TextFieldDelegate */ bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; - bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; + bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override { return false; } bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; bool textFieldDidAbortEditing(TextField * textField) override { return privateTextFieldDidAbortEditing(textField, true); @@ -67,7 +68,7 @@ public: private: static constexpr int k_maxNumberOfDisplayableScriptCells = 5; // = 240/50 - static constexpr int k_parametersColumnWidth = 37; + static constexpr int k_parametersColumnWidth = Metric::EllipsisCellWidth; static constexpr int AddScriptCellType = 0; static constexpr int ScriptCellType = 1; static constexpr int ScriptParameterCellType = 2; diff --git a/apps/code/python_text_area.cpp b/apps/code/python_text_area.cpp index 023106dbd..a2bce364f 100644 --- a/apps/code/python_text_area.cpp +++ b/apps/code/python_text_area.cpp @@ -1,11 +1,13 @@ #include "python_text_area.h" #include "app.h" +#include +#include +#include extern "C" { #include "py/nlr.h" #include "py/lexer.h" } -#include #include namespace Code { @@ -17,6 +19,7 @@ constexpr KDColor KeywordColor = Palette::CodeKeyword; constexpr KDColor OperatorColor = Palette::CodeOperator; constexpr KDColor StringColor = Palette::CodeString; constexpr KDColor BackgroundColor = Palette::CodeBackground; +constexpr KDColor HighlightColor = Palette::CodeBackgroundSelected; static inline const char * minPointer(const char * x, const char * y) { return x < y ? x : y; } @@ -39,40 +42,15 @@ static inline KDColor TokenColor(mp_token_kind_t tokenKind) { return Palette::CodeText; } -static inline size_t TokenLength(mp_lexer_t * lex) { - if (lex->tok_kind == MP_TOKEN_STRING) { - return lex->vstr.len + 2; - } - if (lex->vstr.len > 0) { - return lex->vstr.len; - } - switch (lex->tok_kind) { - case MP_TOKEN_OP_DBL_STAR: - case MP_TOKEN_OP_DBL_SLASH: - case MP_TOKEN_OP_DBL_LESS: - case MP_TOKEN_OP_DBL_MORE: - case MP_TOKEN_OP_LESS_EQUAL: - case MP_TOKEN_OP_MORE_EQUAL: - case MP_TOKEN_OP_DBL_EQUAL: - case MP_TOKEN_OP_NOT_EQUAL: - case MP_TOKEN_DEL_PLUS_EQUAL: - case MP_TOKEN_DEL_MINUS_EQUAL: - case MP_TOKEN_DEL_STAR_EQUAL: - case MP_TOKEN_DEL_SLASH_EQUAL: - case MP_TOKEN_DEL_PERCENT_EQUAL: - case MP_TOKEN_DEL_AMPERSAND_EQUAL: - case MP_TOKEN_DEL_PIPE_EQUAL: - case MP_TOKEN_DEL_CARET_EQUAL: - case MP_TOKEN_DEL_MINUS_MORE: - return 2; - case MP_TOKEN_DEL_DBL_SLASH_EQUAL: - case MP_TOKEN_DEL_DBL_MORE_EQUAL: - case MP_TOKEN_DEL_DBL_LESS_EQUAL: - case MP_TOKEN_DEL_DBL_STAR_EQUAL: - return 3; - default: - return 1; +static inline size_t TokenLength(mp_lexer_t * lex, const char * tokenPosition) { + /* The lexer stores the beginning of the current token and of the next token, + * so we just use that. */ + if (lex->line > 1) { + /* The next token is on the next line, so we cannot just make the difference + * of the columns. */ + return UTF8Helper::CodePointSearch(tokenPosition, '\n') - tokenPosition; } + return lex->column - lex->tok_column; } void PythonTextArea::ContentView::loadSyntaxHighlighter() { @@ -95,7 +73,7 @@ void PythonTextArea::ContentView::clearRect(KDContext * ctx, KDRect rect) const #define LOG_DRAW(...) #endif -void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char * text, size_t byteLength, int fromColumn, int toColumn) 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)) { @@ -108,46 +86,81 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char lineStart, minPointer(text + byteLength, lineEnd) - lineStart, StringColor, - BackgroundColor + BackgroundColor, + selectionStart, + selectionEnd, + HighlightColor ); return; } + /* 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 + * starting with a whitespace. So we're discarding leading whitespaces + * beforehand. */ + const char * firstNonSpace = UTF8Helper::NotCodePointSearch(text, ' '); + if (firstNonSpace != text) { + // Color the discarded leading whitespaces + const char * spacesStart = UTF8Helper::CodePointAtGlyphOffset(text, fromColumn); + drawStringAt( + ctx, + line, + fromColumn, + spacesStart, + minPointer(text + byteLength, firstNonSpace) - spacesStart, + StringColor, + BackgroundColor, + selectionStart, + selectionEnd, + HighlightColor); + } + if (UTF8Helper::CodePointIs(firstNonSpace, UCodePointNull)) { + return; + } + nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { - /* 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 - * starting with a whitespace. So we're discarding leading whitespaces - * beforehand. */ - const char * firstNonSpace = UTF8Helper::NotCodePointSearch(text, ' '); - if (UTF8Helper::CodePointIs(firstNonSpace, UCodePointNull)) { - nlr_pop(); - return; - } - mp_lexer_t * lex = mp_lexer_new_from_str_len(0, firstNonSpace, byteLength - (firstNonSpace - text), 0); LOG_DRAW("Pop token %d\n", lex->tok_kind); const char * tokenFrom = firstNonSpace; size_t tokenLength = 0; + const char * tokenEnd = firstNonSpace; while (lex->tok_kind != MP_TOKEN_NEWLINE && lex->tok_kind != MP_TOKEN_END) { - tokenFrom = firstNonSpace + lex->tok_column - 1; - tokenLength = TokenLength(lex); + if (tokenFrom != tokenEnd) { + // We passed over white spaces, we need to color them + drawStringAt( + ctx, + line, + UTF8Helper::GlyphOffsetAtCodePoint(text, tokenEnd), + tokenEnd, + minPointer(text + byteLength, tokenFrom) - tokenEnd, + StringColor, + BackgroundColor, + selectionStart, + selectionEnd, + HighlightColor); + } + tokenLength = TokenLength(lex, tokenFrom); + tokenEnd = tokenFrom + tokenLength; 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), - BackgroundColor - ); + BackgroundColor, + selectionStart, + selectionEnd, + HighlightColor); mp_lexer_to_next(lex); LOG_DRAW("Pop token %d\n", lex->tok_kind); } tokenFrom += tokenLength; + if (tokenFrom < text + byteLength) { LOG_DRAW("Draw comment \"%.*s\" from %d\n", byteLength - (tokenFrom - text), firstNonSpace, tokenFrom); drawStringAt(ctx, line, @@ -155,7 +168,10 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char tokenFrom, text + byteLength - tokenFrom, CommentColor, - BackgroundColor); + BackgroundColor, + selectionStart, + selectionEnd, + HighlightColor); } mp_lexer_free(lex); @@ -163,13 +179,13 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char } } -KDRect PythonTextArea::ContentView::dirtyRectFromPosition(const char * position, bool lineBreak) const { +KDRect PythonTextArea::ContentView::dirtyRectFromPosition(const char * position, bool includeFollowingLines) const { /* Mark the whole line as dirty. * TextArea has a very conservative approach and only dirties the surroundings * of the current character. That works for plain text, but when doing syntax * highlighting, you may want to redraw the surroundings as well. For example, * if editing "def foo" into "df foo", you'll want to redraw "df". */ - KDRect baseDirtyRect = TextArea::ContentView::dirtyRectFromPosition(position, lineBreak); + KDRect baseDirtyRect = TextArea::ContentView::dirtyRectFromPosition(position, includeFollowingLines); return KDRect( bounds().x(), baseDirtyRect.y(), diff --git a/apps/code/python_text_area.h b/apps/code/python_text_area.h index a16b5006b..149d6bfd0 100644 --- a/apps/code/python_text_area.h +++ b/apps/code/python_text_area.h @@ -27,8 +27,8 @@ protected: void loadSyntaxHighlighter(); void unloadSyntaxHighlighter(); void clearRect(KDContext * ctx, KDRect rect) const override; - void drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn) const override; - KDRect dirtyRectFromPosition(const char * position, bool lineBreak) const override; + void drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn, const char * selectionStart, const char * selectionEnd) const override; + KDRect dirtyRectFromPosition(const char * position, bool includeFollowingLines) const override; private: App * m_pythonDelegate; }; diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 2b82bfec9..d4b1eaa71 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -48,7 +48,7 @@ const ToolboxMessageTree loopsAndTestsChildren[] = { const ToolboxMessageTree MathModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportMath, I18n::Message::PythonImportMath, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromMath, I18n::Message::PythonImportFromMath, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromMath, I18n::Message::PythonImportMath, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandMathFunction, I18n::Message::PythonMathFunction, false, I18n::Message::PythonCommandMathFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandConstantE, I18n::Message::PythonConstantE, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandConstantPi, I18n::Message::PythonConstantPi, false), @@ -94,7 +94,7 @@ const ToolboxMessageTree MathModuleChildren[] = { const ToolboxMessageTree KandinskyModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportKandinsky, I18n::Message::PythonImportKandinsky, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromKandinsky, I18n::Message::PythonImportFromKandinsky, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromKandinsky, I18n::Message::PythonImportKandinsky, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKandinskyFunction, I18n::Message::PythonKandinskyFunction, false, I18n::Message::PythonCommandKandinskyFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPixel, I18n::Message::PythonGetPixel), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSetPixel, I18n::Message::PythonSetPixel), @@ -105,7 +105,7 @@ const ToolboxMessageTree KandinskyModuleChildren[] = { const ToolboxMessageTree RandomModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportRandom, I18n::Message::PythonImportRandom, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromRandom, I18n::Message::PythonImportFromRandom, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromRandom, I18n::Message::PythonImportRandom, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRandomFunction, I18n::Message::PythonRandomFunction, false, I18n::Message::PythonCommandRandomFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetrandbits, I18n::Message::PythonGetrandbits), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSeed, I18n::Message::PythonSeed), @@ -118,7 +118,7 @@ const ToolboxMessageTree RandomModuleChildren[] = { const ToolboxMessageTree CMathModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportCmath, I18n::Message::PythonImportCmath, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromCmath, I18n::Message::PythonImportFromCmath, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromCmath, I18n::Message::PythonImportCmath, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCmathFunction, I18n::Message::PythonCmathFunction, false, I18n::Message::PythonCommandCmathFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandConstantE, I18n::Message::PythonConstantE, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandConstantPi, I18n::Message::PythonConstantPi, false), @@ -134,7 +134,7 @@ const ToolboxMessageTree CMathModuleChildren[] = { const ToolboxMessageTree TurtleModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportTurtle, I18n::Message::PythonImportTurtle, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromTurtle, I18n::Message::PythonImportFromTurtle, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromTurtle, I18n::Message::PythonImportTurtle, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandTurtleFunction, I18n::Message::PythonTurtleFunction, false, I18n::Message::PythonCommandTurtleFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandForward, I18n::Message::PythonTurtleForward), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandBackward, I18n::Message::PythonTurtleBackward), @@ -175,13 +175,76 @@ const ToolboxMessageTree timeModuleChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonTimeCommandMonotonic, I18n::Message::PythonTimeMonotonic, false) }; +const ToolboxMessageTree IonModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportIon, I18n::Message::PythonImportIon, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromIon, I18n::Message::PythonImportIon, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIonFunction, I18n::Message::PythonIonFunction, false, I18n::Message::PythonCommandIonFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIsKeyDown, I18n::Message::PythonIsKeyDown), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyLeft, I18n::Message::PythonKeyLeft, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyUp, I18n::Message::PythonKeyUp, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyDown, I18n::Message::PythonKeyDown, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyRight, I18n::Message::PythonKeyRight, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyOk, I18n::Message::PythonKeyOk, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyBack, I18n::Message::PythonKeyBack, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyHome, I18n::Message::PythonKeyHome, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyOnOff, I18n::Message::PythonKeyOnOff, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyShift, I18n::Message::PythonKeyShift, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyAlpha, I18n::Message::PythonKeyAlpha, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyXnt, I18n::Message::PythonKeyXnt, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyVar, I18n::Message::PythonKeyVar, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyToolbox, I18n::Message::PythonKeyToolbox, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyBackspace, I18n::Message::PythonKeyBackspace, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyExp, I18n::Message::PythonKeyExp, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyLn, I18n::Message::PythonKeyLn, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyLog, I18n::Message::PythonKeyLog, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyImaginary, I18n::Message::PythonKeyImaginary, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyComma, I18n::Message::PythonKeyComma, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyPower, I18n::Message::PythonKeyPower, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeySine, I18n::Message::PythonKeySine, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyCosine, I18n::Message::PythonKeyCosine, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyTangent, I18n::Message::PythonKeyTangent, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyPi, I18n::Message::PythonKeyPi, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeySqrt, I18n::Message::PythonKeySqrt, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeySquare, I18n::Message::PythonKeySquare, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeySeven, I18n::Message::PythonKeySeven, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyEight, I18n::Message::PythonKeyEight, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyNine, I18n::Message::PythonKeyNine, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyLeftParenthesis, I18n::Message::PythonKeyLeftParenthesis, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyRightParenthesis, I18n::Message::PythonKeyRightParenthesis, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyFour, I18n::Message::PythonKeyFour, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyFive, I18n::Message::PythonKeyFive, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeySix, I18n::Message::PythonKeySix, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyMultiplication, I18n::Message::PythonKeyMultiplication, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyDivision, I18n::Message::PythonKeyDivision, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyOne, I18n::Message::PythonKeyOne, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyTwo, I18n::Message::PythonKeyTwo, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyThree, I18n::Message::PythonKeyThree, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyPlus, I18n::Message::PythonKeyPlus, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyMinus, I18n::Message::PythonKeyMinus, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyZero, I18n::Message::PythonKeyZero, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyDot, I18n::Message::PythonKeyDot, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyEe, I18n::Message::PythonKeyEe, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyAns, I18n::Message::PythonKeyAns, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKeyExe, I18n::Message::PythonKeyExe, false) +}; + +const ToolboxMessageTree TimeModuleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportTime, I18n::Message::PythonImportTime, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromTime, I18n::Message::PythonImportTime, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandTimeFunction, I18n::Message::PythonTimeFunction, false, I18n::Message::PythonCommandTimeFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandMonotonic, I18n::Message::PythonMonotonic, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSleep, I18n::Message::PythonSleep) +}; + const ToolboxMessageTree modulesChildren[] = { ToolboxMessageTree::Node(I18n::Message::MathModule, MathModuleChildren), ToolboxMessageTree::Node(I18n::Message::CmathModule, CMathModuleChildren), ToolboxMessageTree::Node(I18n::Message::RandomModule, RandomModuleChildren), ToolboxMessageTree::Node(I18n::Message::TurtleModule, TurtleModuleChildren), ToolboxMessageTree::Node(I18n::Message::KandinskyModule, KandinskyModuleChildren), - ToolboxMessageTree::Node(I18n::Message::PythonTimeModule, timeModuleChildren) + ToolboxMessageTree::Node(I18n::Message::PythonTimeModule, timeModuleChildren), + ToolboxMessageTree::Node(I18n::Message::IonModule, IonModuleChildren), + ToolboxMessageTree::Node(I18n::Message::TimeModule, TimeModuleChildren) }; const ToolboxMessageTree catalogChildren[] = { @@ -197,6 +260,7 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAbs, I18n::Message::PythonAbs), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAcos, I18n::Message::PythonAcos), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAcosh, I18n::Message::PythonAcosh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAppend, I18n::Message::PythonAppend, false, I18n::Message::PythonCommandAppendWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAsin, I18n::Message::PythonAsin), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAsinh, I18n::Message::PythonAsinh), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandAtan, I18n::Message::PythonAtan), @@ -210,12 +274,14 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCeil, I18n::Message::PythonCeil), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandChoice, I18n::Message::PythonChoice), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandCircle, I18n::Message::PythonTurtleCircle), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandClear, I18n::Message::PythonClear, false, I18n::Message::PythonCommandClearWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCmathFunction, I18n::Message::PythonCmathFunction, false, I18n::Message::PythonCommandCmathFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColor, I18n::Message::PythonColor), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandComplex, I18n::Message::PythonComplex), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCopySign, I18n::Message::PythonCopySign), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCos, I18n::Message::PythonCos), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCosh, I18n::Message::PythonCosh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandCount, I18n::Message::PythonCount, false, I18n::Message::PythonCommandCountWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDegrees, I18n::Message::PythonDegrees), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDivMod, I18n::Message::PythonDivMod), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawString, I18n::Message::PythonDrawString), @@ -232,11 +298,13 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandForward, I18n::Message::PythonTurtleForward), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFmod, I18n::Message::PythonFmod), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFrExp, I18n::Message::PythonFrExp), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromCmath, I18n::Message::PythonImportFromCmath, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromKandinsky, I18n::Message::PythonImportFromKandinsky, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromMath, I18n::Message::PythonImportFromMath, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromRandom, I18n::Message::PythonImportFromRandom, false), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromTurtle, I18n::Message::PythonImportFromTurtle, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromCmath, I18n::Message::PythonImportCmath, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromIon, I18n::Message::PythonImportIon, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromKandinsky, I18n::Message::PythonImportKandinsky, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromMath, I18n::Message::PythonImportMath, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromRandom, I18n::Message::PythonImportRandom, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromTurtle, I18n::Message::PythonImportTurtle, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportFromTime, I18n::Message::PythonImportTime, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGamma, I18n::Message::PythonGamma), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPixel, I18n::Message::PythonGetPixel), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetrandbits, I18n::Message::PythonGetrandbits), @@ -247,17 +315,23 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandHex, I18n::Message::PythonHex), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandHideturtle, I18n::Message::PythonTurtleHideturtle, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportCmath, I18n::Message::PythonImportCmath, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportIon, I18n::Message::PythonImportIon, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportKandinsky, I18n::Message::PythonImportKandinsky, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportMath, I18n::Message::PythonImportMath, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportRandom, I18n::Message::PythonImportRandom, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportTurtle, I18n::Message::PythonImportTurtle, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandImportTime, I18n::Message::PythonImportTime, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIndex, I18n::Message::PythonIndex, false, I18n::Message::PythonCommandIndexWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandInput, I18n::Message::PythonInput), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandInsert, I18n::Message::PythonInsert, false, I18n::Message::PythonCommandInsertWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandInt, I18n::Message::PythonInt), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIonFunction, I18n::Message::PythonIonFunction, false, I18n::Message::PythonCommandIonFunctionWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandIsdown, I18n::Message::PythonTurtleIsdown, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIsFinite, I18n::Message::PythonIsFinite), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIsInfinite, I18n::Message::PythonIsInfinite), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIsNaN, I18n::Message::PythonIsNaN), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandKandinskyFunction, I18n::Message::PythonKandinskyFunction, false, I18n::Message::PythonCommandKandinskyFunctionWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandIsKeyDown, I18n::Message::PythonIsKeyDown), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandLdexp, I18n::Message::PythonLdexp), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandLeft, I18n::Message::PythonTurtleLeft), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandLength, I18n::Message::PythonLength), @@ -269,6 +343,7 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandMax, I18n::Message::PythonMax), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandMin, I18n::Message::PythonMin), 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::PythonTurtleCommandPendown, I18n::Message::PythonTurtlePendown, false), @@ -278,6 +353,7 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonCommandConstantPi, I18n::Message::PythonConstantPi, false), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandPink, I18n::Message::PythonTurtlePink, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPolar, I18n::Message::PythonPolar), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPop, I18n::Message::PythonPop, false, I18n::Message::PythonCommandPopWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandPosition, I18n::Message::PythonTurtlePosition, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPower, I18n::Message::PythonPower), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandPrint, I18n::Message::PythonPrint), @@ -291,7 +367,9 @@ const ToolboxMessageTree catalogChildren[] = { 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::PythonCommandRemove, I18n::Message::PythonRemove, false, I18n::Message::PythonCommandRemoveWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandReset, I18n::Message::PythonTurtleReset, false), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandReverse, I18n::Message::PythonReverse, false, I18n::Message::PythonCommandReverseWithoutArg), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandRight, I18n::Message::PythonTurtleRight), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandRound, I18n::Message::PythonRound), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandSetheading, I18n::Message::PythonTurtleSetheading), @@ -300,12 +378,15 @@ const ToolboxMessageTree catalogChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandShowturtle, I18n::Message::PythonTurtleShowturtle, false), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSin, I18n::Message::PythonSin), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSinh, I18n::Message::PythonSinh), - ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSorted, I18n::Message::PythonSorted), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSleep, I18n::Message::PythonSleep), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSort, I18n::Message::PythonSort, false, I18n::Message::PythonCommandSortWithoutArg), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSorted, I18n::Message::PythonSort), ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandSpeed, I18n::Message::PythonTurtleSpeed), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSqrt, I18n::Message::PythonSqrt), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSum, I18n::Message::PythonSum), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandTan, I18n::Message::PythonTan), ToolboxMessageTree::Leaf(I18n::Message::PythonCommandTanh, I18n::Message::PythonTanh), + ToolboxMessageTree::Leaf(I18n::Message::PythonCommandTimeFunction, I18n::Message::PythonTimeFunction, false, I18n::Message::PythonCommandTimeFunctionWithoutArg), 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), @@ -341,7 +422,7 @@ bool PythonToolbox::handleEvent(Ion::Events::Event event) { } if (event.hasText() && strlen(event.text()) == 1 ) { char c = event.text()[0]; - if (UTF8Helper::CodePointIsLetter(c)) { + if (CodePoint(c).isLatinLetter()) { scrollToLetter(c); return true; } @@ -361,7 +442,7 @@ KDCoordinate PythonToolbox::rowHeight(int j) { * children of the toolbox, which is the only menu that has special height * rows. */ const ToolboxMessageTree * messageTree = static_cast(m_messageTreeModel->children(j)); - return k_font->stringSize(I18n::translate(messageTree->label())).height() + 2*Metric::TableCellLabelTopMargin + (messageTree->text() == I18n::Message::Default ? 0 : Toolbox::rowHeight(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); } @@ -402,7 +483,7 @@ int PythonToolbox::maxNumberOfDisplayedRows() { } void PythonToolbox::scrollToLetter(char letter) { - assert(UTF8Helper::CodePointIsLetter(letter)); + assert(CodePoint(letter).isLatinLetter()); /* We look for a child MessageTree that starts with the wanted letter. If we * do not find one, we scroll to the first child MessageTree that starts with * a letter higher than the wanted letter. */ @@ -414,7 +495,7 @@ void PythonToolbox::scrollToLetter(char letter) { index = i; break; } - if (index < 0 && l >= lowerLetter && UTF8Helper::CodePointIsLowerCaseLetter(l)) { + if (index < 0 && l >= lowerLetter && CodePoint(l).isLatinSmallLetter()) { index = i; } } diff --git a/apps/code/script.cpp b/apps/code/script.cpp index 12a88c976..1b33fd0e7 100644 --- a/apps/code/script.cpp +++ b/apps/code/script.cpp @@ -48,7 +48,7 @@ bool Script::nameCompliant(const char * name) { * problems with case sensitivity. */ UTF8Decoder decoder(name); CodePoint c = decoder.nextCodePoint(); - if (c == UCodePointNull || !(UTF8Helper::CodePointIsLowerCaseLetter(c) || c == '_' || c == '.')) { + if (c == UCodePointNull || !(c.isLatinSmallLetter() || c == '_' || c == '.')) { /* The name cannot be empty. Its first letter must be in [a-z_] or the * extension dot. */ return false; @@ -57,7 +57,7 @@ bool Script::nameCompliant(const char * name) { if (c == '.' && strcmp(decoder.stringPosition(), ScriptStore::k_scriptExtension) == 0) { return true; } - if (!(UTF8Helper::CodePointIsLowerCaseLetter(c) || c == '_' || UTF8Helper::CodePointIsNumber(c))) { + if (!(c.isLatinSmallLetter() || c == '_' || c.isDecimalDigit())) { return false; } c = decoder.nextCodePoint(); @@ -77,7 +77,7 @@ void Script::toggleImportationStatus() { setValue(d); } -const char * Script::readContent() const { +const char * Script::scriptContent() const { assert(!isNull()); Data d = value(); return (const char *)d.buffer + k_importationStatusSize; diff --git a/apps/code/script.h b/apps/code/script.h index 4ab639c13..fc10352f3 100644 --- a/apps/code/script.h +++ b/apps/code/script.h @@ -26,7 +26,7 @@ public: Script(Ion::Storage::Record r) : Record(r) {} bool importationStatus() const; void toggleImportationStatus(); - const char * readContent() const; + const char * scriptContent() const; }; } diff --git a/apps/code/script_name_cell.cpp b/apps/code/script_name_cell.cpp index 26792e7f2..d451df130 100644 --- a/apps/code/script_name_cell.cpp +++ b/apps/code/script_name_cell.cpp @@ -29,12 +29,13 @@ void ScriptNameCell::didBecomeFirstResponder() { Container::activeApp()->setFirstResponder(&m_textField); } -void ScriptNameCell::layoutSubviews() { +void ScriptNameCell::layoutSubviews(bool force) { KDRect cellBounds = bounds(); m_textField.setFrame(KDRect(cellBounds.x() + k_leftMargin, cellBounds.y(), cellBounds.width() - k_leftMargin, - cellBounds.height())); + cellBounds.height()), + force); } } diff --git a/apps/code/script_name_cell.h b/apps/code/script_name_cell.h index ec9831bd8..dfa2c1d36 100644 --- a/apps/code/script_name_cell.h +++ b/apps/code/script_name_cell.h @@ -48,7 +48,7 @@ private: assert(index == 0); return &m_textField; } - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; Shared::TextFieldWithExtension m_textField; char m_textBody[TextField::maxBufferSize()]; diff --git a/apps/code/script_node_cell.cpp b/apps/code/script_node_cell.cpp index 68fc8b83a..a3e265bf2 100644 --- a/apps/code/script_node_cell.cpp +++ b/apps/code/script_node_cell.cpp @@ -23,12 +23,12 @@ void ScriptNodeCell::ScriptNodeView::setScriptStore(ScriptStore * scriptStore) { } void ScriptNodeCell::ScriptNodeView::drawRect(KDContext * ctx, KDRect rect) const { - ctx->drawString(m_scriptNode->name(), KDPoint(0, Metric::TableCellLabelTopMargin), k_font, Palette::CodeText, isHighlighted()? Palette::CodeBackgroundSelected : Palette::CodeBackground); + 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::TableCellLabelTopMargin), k_font, Palette::CodeText, isHighlighted()? Palette::CodeBackgroundSelected : Palette::CodeBackground); + ctx->drawString(ScriptNodeCell::k_parentheses, KDPoint(nameSize.width(), Metric::TableCellVerticalMargin), k_font, Palette::CodeText, isHighlighted()? Palette::CodeBackgroundSelected : Palette::CodeBackground); } - ctx->drawString(m_scriptStore->scriptAtIndex(m_scriptNode->scriptIndex()).fullName(), KDPoint(0, Metric::TableCellLabelTopMargin + nameSize.height() + k_verticalMargin), k_font, Palette::SecondaryText, isHighlighted()? Palette::CodeBackgroundSelected : Palette::CodeBackground); + 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 { @@ -41,7 +41,7 @@ KDSize ScriptNodeCell::ScriptNodeView::minimalSizeForOptimalDisplay() const { 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::TableCellLabelTopMargin + size1.width() + k_verticalMargin + size2.width()); + return KDSize(size1.width() + size3.width() > size2.width() ? size1.width() + size3.width() : size2.width(), Metric::TableCellVerticalMargin + size1.width() + k_verticalMargin + size2.width()); } ScriptNodeCell::ScriptNodeCell() : diff --git a/apps/code/script_store.cpp b/apps/code/script_store.cpp index 9ef37ce03..705ba3506 100644 --- a/apps/code/script_store.cpp +++ b/apps/code/script_store.cpp @@ -38,7 +38,7 @@ void ScriptStore::scanScriptsForFunctionsAndVariables(void * context, ScanCallba // Handle lexer or parser errors with nlr. nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { - const char * scriptContent = scriptAtIndex(scriptIndex).readContent(); + const char * scriptContent = scriptAtIndex(scriptIndex).scriptContent(); if (scriptContent == nullptr) { continue; } @@ -120,7 +120,7 @@ const char * ScriptStore::contentOfScript(const char * name) { if (script.isNull()) { return nullptr; } - return script.readContent(); + return script.scriptContent(); } Script::ErrorStatus ScriptStore::addScriptFromTemplate(const ScriptTemplate * scriptTemplate) { diff --git a/apps/code/toolbox.universal.i18n b/apps/code/toolbox.universal.i18n index 4fa5710d3..d5afd929c 100644 --- a/apps/code/toolbox.universal.i18n +++ b/apps/code/toolbox.universal.i18n @@ -1,6 +1,8 @@ -MathModule = "math" CmathModule = "cmath" +IonModule = "ion" KandinskyModule = "kandinsky" +MathModule = "math" +TimeModule = "time" TurtleModule = "turtle" ForLoopMenu = "For" IfStatementMenu = "If" diff --git a/apps/exam_mode_configuration.h b/apps/exam_mode_configuration.h new file mode 100644 index 000000000..4f2879030 --- /dev/null +++ b/apps/exam_mode_configuration.h @@ -0,0 +1,27 @@ +#ifndef APPS_EXAM_MODE_CONFIGURATION_H +#define APPS_EXAM_MODE_CONFIGURATION_H + +#include "global_preferences.h" +#include "settings/settings_message_tree.h" +#include + +namespace ExamModeConfiguration { + +// Settings menu +extern const Settings::SettingsMessageTree s_modelExamChildren[2]; +int numberOfAvailableExamMode(); +GlobalPreferences::ExamMode examModeAtIndex(int index); +I18n::Message examModeActivationMessage(int index); + +// Settings pop-up +I18n::Message examModeActivationWarningMessage(GlobalPreferences::ExamMode mode, int line); + +// Exam mode behaviour +KDColor examModeColor(GlobalPreferences::ExamMode mode); +bool appIsForbiddenInExamMode(I18n::Message appName, GlobalPreferences::ExamMode mode); +bool exactExpressionsAreForbidden(GlobalPreferences::ExamMode mode); + +} + +#endif + diff --git a/apps/exam_mode_configuration_non_official.cpp b/apps/exam_mode_configuration_non_official.cpp new file mode 100644 index 000000000..2f94ceef7 --- /dev/null +++ b/apps/exam_mode_configuration_non_official.cpp @@ -0,0 +1,38 @@ +#include "exam_mode_configuration.h" + +constexpr Settings::SettingsMessageTree ExamModeConfiguration::s_modelExamChildren[] = {Settings::SettingsMessageTree(I18n::Message::ActivateExamMode), Settings::SettingsMessageTree(I18n::Message::Default)}; + +int ExamModeConfiguration::numberOfAvailableExamMode() { + return 1; +} + +GlobalPreferences::ExamMode ExamModeConfiguration::examModeAtIndex(int index) { + return GlobalPreferences::ExamMode::Standard; +} + +I18n::Message ExamModeConfiguration::examModeActivationMessage(int index) { + return I18n::Message::ActivateExamMode; +} + +I18n::Message ExamModeConfiguration::examModeActivationWarningMessage(GlobalPreferences::ExamMode mode, int line) { + if (mode == GlobalPreferences::ExamMode::Off) { + I18n::Message warnings[] = {I18n::Message::ExitExamMode1, I18n::Message::ExitExamMode2, I18n::Message::Default}; + return warnings[line]; + } + assert(mode == GlobalPreferences::ExamMode::Standard); + I18n::Message warnings[] = {I18n::Message::ActiveExamModeMessage1, I18n::Message::ActiveExamModeMessage2, I18n::Message::ActiveExamModeMessage3}; + return warnings[line]; +} + +KDColor ExamModeConfiguration::examModeColor(GlobalPreferences::ExamMode mode) { + assert(mode == GlobalPreferences::ExamMode::Standard); + return KDColorRed; +} + +bool ExamModeConfiguration::appIsForbiddenInExamMode(I18n::Message appName, GlobalPreferences::ExamMode mode) { + return false; +} + +bool ExamModeConfiguration::exactExpressionsAreForbidden(GlobalPreferences::ExamMode mode) { + return false; +} diff --git a/apps/exam_mode_configuration_official.cpp b/apps/exam_mode_configuration_official.cpp new file mode 100644 index 000000000..3338dbf78 --- /dev/null +++ b/apps/exam_mode_configuration_official.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 +// Caution: Dutch exam mode is subject to a compliance certification by a government agency. Distribution of a non-certified Dutch exam mode is illegal. + +#include "exam_mode_configuration.h" + +constexpr Settings::SettingsMessageTree ExamModeConfiguration::s_modelExamChildren[2] = {Settings::SettingsMessageTree(I18n::Message::ActivateExamMode), Settings::SettingsMessageTree(I18n::Message::ActivateDutchExamMode)}; + +int ExamModeConfiguration::numberOfAvailableExamMode() { + if (GlobalPreferences::sharedGlobalPreferences()->language() != I18n::Language::EN || GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { + return 1; + } + return 2; +} + +GlobalPreferences::ExamMode ExamModeConfiguration::examModeAtIndex(int index) { + return index == 0 ? GlobalPreferences::ExamMode::Standard : GlobalPreferences::ExamMode::Dutch; +} + +I18n::Message ExamModeConfiguration::examModeActivationMessage(int index) { + return index == 0 ? I18n::Message::ActivateExamMode : I18n::Message::ActivateDutchExamMode; +} + +I18n::Message ExamModeConfiguration::examModeActivationWarningMessage(GlobalPreferences::ExamMode mode, int line) { + if (mode == GlobalPreferences::ExamMode::Off) { + I18n::Message warnings[] = {I18n::Message::ExitExamMode1, I18n::Message::ExitExamMode2, I18n::Message::Default}; + return warnings[line]; + } else if (mode == GlobalPreferences::ExamMode::Standard || mode == GlobalPreferences::ExamMode::NoSym) { + I18n::Message warnings[] = {I18n::Message::ActiveExamModeMessage1, I18n::Message::ActiveExamModeMessage2, I18n::Message::ActiveExamModeMessage3}; + return warnings[line]; + } + assert(mode == GlobalPreferences::ExamMode::Dutch); + I18n::Message warnings[] = {I18n::Message::ActiveDutchExamModeMessage1, I18n::Message::ActiveDutchExamModeMessage2, I18n::Message::ActiveDutchExamModeMessage3}; + return warnings[line]; +} + +KDColor ExamModeConfiguration::examModeColor(GlobalPreferences::ExamMode mode) { + /* The Dutch exam mode LED is supposed to be orange but we can only make + * blink "pure" colors: with RGB leds on or off (as the PWM is used for + * blinking). The closest "pure" color is Yellow. Moreover, Orange LED is + * already used when the battery is charging. Using yellow, we can assert + * that the yellow LED only means that Dutch exam mode is on and avoid + * confusing states when the battery is charging and states when the Dutch + * exam mode is on. */ + return mode == GlobalPreferences::ExamMode::Dutch ? KDColorYellow : KDColorRed; +} + +bool ExamModeConfiguration::appIsForbiddenInExamMode(I18n::Message appName, GlobalPreferences::ExamMode mode) { + return appName == I18n::Message::CodeApp && mode == GlobalPreferences::ExamMode::Dutch; +} + +bool ExamModeConfiguration::exactExpressionsAreForbidden(GlobalPreferences::ExamMode mode) { + return mode == GlobalPreferences::ExamMode::Dutch; +} diff --git a/apps/exam_pop_up_controller.cpp b/apps/exam_pop_up_controller.cpp index 27451be80..82111dbd6 100644 --- a/apps/exam_pop_up_controller.cpp +++ b/apps/exam_pop_up_controller.cpp @@ -1,5 +1,6 @@ #include "exam_pop_up_controller.h" #include "apps_container.h" +#include "exam_mode_configuration.h" #include #include "global_preferences.h" #include @@ -68,10 +69,14 @@ ExamPopUpController::ContentView::ContentView(Responder * parentResponder) : return true; }, parentResponder), KDFont::SmallFont), m_warningTextView(KDFont::SmallFont, I18n::Message::Warning, 0.5, 0.5, KDColorWhite, KDColorBlack), - m_messageTextView1(KDFont::SmallFont, I18n::Message::Default, 0.5, 0.5, KDColorWhite, KDColorBlack), - m_messageTextView2(KDFont::SmallFont, I18n::Message::Default, 0.5, 0.5, KDColorWhite, KDColorBlack), - m_messageTextView3(KDFont::SmallFont, I18n::Message::Default, 0.5, 0.5, KDColorWhite, KDColorBlack) + m_messageTextViews{} { + for (int i = 0; i < k_maxNumberOfLines; i++) { + m_messageTextViews[i].setFont(KDFont::SmallFont); + m_messageTextViews[i].setAlignment(0.5f, 0.5f); + m_messageTextViews[i].setBackgroundColor(KDColorBlack); + m_messageTextViews[i].setTextColor(KDColorWhite); + } } void ExamPopUpController::ContentView::drawRect(KDContext * ctx, KDRect rect) const { @@ -92,19 +97,8 @@ int ExamPopUpController::ContentView::selectedButton() { } void ExamPopUpController::ContentView::setMessagesForExamMode(GlobalPreferences::ExamMode mode) { - if (mode == GlobalPreferences::ExamMode::Off) { - m_messageTextView1.setMessage(I18n::Message::ExitExamMode1); - m_messageTextView2.setMessage(I18n::Message::ExitExamMode2); - m_messageTextView3.setMessage(I18n::Message::Default); - } else if (mode == GlobalPreferences::ExamMode::Standard || mode == GlobalPreferences::ExamMode::NoSym) { - m_messageTextView1.setMessage(I18n::Message::ActiveExamModeMessage1); - m_messageTextView2.setMessage(I18n::Message::ActiveExamModeMessage2); - m_messageTextView3.setMessage(I18n::Message::ActiveExamModeMessage3); - } else { - assert(mode == GlobalPreferences::ExamMode::Dutch); - m_messageTextView1.setMessage(I18n::Message::ActiveDutchExamModeMessage1); - m_messageTextView2.setMessage(I18n::Message::ActiveDutchExamModeMessage2); - m_messageTextView3.setMessage(I18n::Message::ActiveDutchExamModeMessage3); + for (int i = 0; i < k_maxNumberOfLines; i++) { + m_messageTextViews[i].setMessage(ExamModeConfiguration::examModeActivationWarningMessage(mode, i)); } } @@ -116,30 +110,23 @@ View * ExamPopUpController::ContentView::subviewAtIndex(int index) { switch (index) { case 0: return &m_warningTextView; - case 1: - return &m_messageTextView1; - case 2: - return &m_messageTextView2; - case 3: - return &m_messageTextView3; case 4: return &m_cancelButton; case 5: return &m_okButton; default: - assert(false); - return nullptr; + return &m_messageTextViews[index-1]; } } -void ExamPopUpController::ContentView::layoutSubviews() { +void ExamPopUpController::ContentView::layoutSubviews(bool force) { KDCoordinate height = bounds().height(); KDCoordinate width = bounds().width(); KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height(); - m_warningTextView.setFrame(KDRect(0, k_topMargin, width, textHeight)); - m_messageTextView1.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+textHeight, width, textHeight)); - m_messageTextView2.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+2*textHeight, width, textHeight)); - m_messageTextView3.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+3*textHeight, width, textHeight)); - m_cancelButton.setFrame(KDRect(k_buttonMargin, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight)); - m_okButton.setFrame(KDRect(2*k_buttonMargin+(width-3*k_buttonMargin)/2, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight)); + m_warningTextView.setFrame(KDRect(0, k_topMargin, width, textHeight), force); + for (int i = 0; i < k_maxNumberOfLines; i++) { + m_messageTextViews[i].setFrame(KDRect(0, k_topMargin+k_paragraphHeight+(i+1)*textHeight, width, textHeight), force); + } + m_cancelButton.setFrame(KDRect(k_buttonMargin, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight), force); + m_okButton.setFrame(KDRect(2*k_buttonMargin+(width-3*k_buttonMargin)/2, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight), force); } diff --git a/apps/exam_pop_up_controller.h b/apps/exam_pop_up_controller.h index 79ca4d6c9..3ef7285eb 100644 --- a/apps/exam_pop_up_controller.h +++ b/apps/exam_pop_up_controller.h @@ -37,13 +37,12 @@ private: constexpr static KDCoordinate k_paragraphHeight = 20; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; HighContrastButton m_cancelButton; HighContrastButton m_okButton; MessageTextView m_warningTextView; - MessageTextView m_messageTextView1; - MessageTextView m_messageTextView2; - MessageTextView m_messageTextView3; + constexpr static int k_maxNumberOfLines = 3; + MessageTextView m_messageTextViews[k_maxNumberOfLines]; }; ContentView m_contentView; GlobalPreferences::ExamMode m_targetExamMode; diff --git a/apps/global_preferences.h b/apps/global_preferences.h index adf5e27fd..b54ea0b2a 100644 --- a/apps/global_preferences.h +++ b/apps/global_preferences.h @@ -25,6 +25,8 @@ public: void setShowPopUp(bool showPopUp) { m_showPopUp = showPopUp; } int brightnessLevel() const { return m_brightnessLevel; } void setBrightnessLevel(int brightnessLevel); + const KDFont * font() const { return m_font; } + void setFont(const KDFont * font) { m_font = font; } constexpr static int NumberOfBrightnessStates = 15; private: GlobalPreferences() : @@ -32,7 +34,8 @@ private: m_examMode(ExamMode::Unknown), m_tempExamMode(ExamMode::Standard), m_showPopUp(true), - m_brightnessLevel(Ion::Backlight::MaxBrightness) {} + m_brightnessLevel(Ion::Backlight::MaxBrightness), + m_font(KDFont::LargeFont) {} I18n::Language m_language; static_assert((int8_t)GlobalPreferences::ExamMode::Off == 0, "GlobalPreferences::isInExamMode() is not right"); static_assert((int8_t)GlobalPreferences::ExamMode::Unknown < 0, "GlobalPreferences::isInExamMode() is not right"); @@ -40,6 +43,7 @@ private: mutable ExamMode m_tempExamMode; bool m_showPopUp; int m_brightnessLevel; + const KDFont * m_font; }; #endif diff --git a/apps/graph/base.pt.i18n b/apps/graph/base.pt.i18n index 9d56d4716..49ec2be81 100644 --- a/apps/graph/base.pt.i18n +++ b/apps/graph/base.pt.i18n @@ -13,7 +13,7 @@ IntervalX = "Intervalo x" FunctionDomain = "Intervalo do gráfico" FunctionColor = "Cor da função" NoFunction = "Nenhuma função" -NoActivatedFunction = "Sem função ativada" +NoActivatedFunction = "Sem função activada" PlotOptions = "Opções da curva" Compute = "Calcular" Zeros = "Raízes" diff --git a/apps/graph/graph/calculation_graph_controller.cpp b/apps/graph/graph/calculation_graph_controller.cpp index 292de3dac..0adcfc2d3 100644 --- a/apps/graph/graph/calculation_graph_controller.cpp +++ b/apps/graph/graph/calculation_graph_controller.cpp @@ -57,20 +57,16 @@ ContinuousFunctionStore * CalculationGraphController::functionStore() const { return App::app()->functionStore(); } -bool CalculationGraphController::handleLeftRightEvent(Ion::Events::Event event) { - if (!m_isActive) { - return false; - } - return SimpleInteractiveCurveViewController::handleLeftRightEvent(event); -} - bool CalculationGraphController::handleEnter() { StackViewController * stack = static_cast(parentResponder()); stack->pop(); return true; } -bool CalculationGraphController::moveCursorHorizontally(int direction) { +bool CalculationGraphController::moveCursorHorizontally(int direction, bool fast) { + if (!m_isActive) { + return false; + } Coordinate2D newPointOfInterest = computeNewPointOfInterestFromAbscissa(m_cursor->x(), direction); if (std::isnan(newPointOfInterest.x1())) { return false; diff --git a/apps/graph/graph/calculation_graph_controller.h b/apps/graph/graph/calculation_graph_controller.h index a400f56ab..46f861ddf 100644 --- a/apps/graph/graph/calculation_graph_controller.h +++ b/apps/graph/graph/calculation_graph_controller.h @@ -31,9 +31,8 @@ protected: bool m_isActive; private: bool handleZoom(Ion::Events::Event event) override { return false; } - bool handleLeftRightEvent(Ion::Events::Event event) override; bool handleEnter() override; - bool moveCursorHorizontally(int direction) override; + bool moveCursorHorizontally(int direction, bool fast = false) override; Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; } Shared::CurveView * curveView() override { return m_graphView; } }; diff --git a/apps/graph/graph/graph_controller.cpp b/apps/graph/graph/graph_controller.cpp index 22b754337..9f45f3eff 100644 --- a/apps/graph/graph/graph_controller.cpp +++ b/apps/graph/graph/graph_controller.cpp @@ -93,7 +93,6 @@ void GraphController::interestingRanges(float * xm, float * xM, float * ym, floa /* In practice, a step smaller than a pixel's width is needed for sampling * the values of a function. Otherwise some relevant extremal values may be * missed. */ - const float step = const_cast(this)->curveView()->pixelWidth() / 2; for (int i = 0; i < functionsCount; i++) { ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); if (f->plotType() != ContinuousFunction::PlotType::Cartesian) { @@ -103,8 +102,9 @@ void GraphController::interestingRanges(float * xm, float * xM, float * ym, floa * y-range for even functions (y = 1/x). */ assert(!std::isnan(f->tMin())); assert(!std::isnan(f->tMax())); - double tMin = maxFloat(f->tMin(), resultxMin); - double tMax = minFloat(f->tMax(), resultxMax); + const double tMin = maxFloat(f->tMin(), resultxMin); + const double tMax = minFloat(f->tMax(), resultxMax); + const double step = (tMax - tMin) / (2.0 * (m_view.bounds().width() - 1.0)); interestingFunctionRange(f, tMin, tMax, step, &resultxMin, &resultxMax, &resultyMin, &resultyMax); } if (resultyMin > resultyMax) { @@ -169,9 +169,9 @@ void GraphController::reloadBannerView() { reloadDerivativeInBannerViewForCursorOnFunction(m_cursor, record); } -bool GraphController::moveCursorHorizontally(int direction) { +bool GraphController::moveCursorHorizontally(int direction, bool fast) { Ion::Storage::Record record = functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor()); - return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, record); + return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, record, fast); } int GraphController::nextCurveIndexVertically(bool goingUp, int currentSelectedCurve, Poincare::Context * context) const { diff --git a/apps/graph/graph/graph_controller.h b/apps/graph/graph/graph_controller.h index 74f2f1105..8860dd245 100644 --- a/apps/graph/graph/graph_controller.h +++ b/apps/graph/graph/graph_controller.h @@ -27,7 +27,7 @@ private: void selectFunctionWithCursor(int functionIndex) override; BannerView * bannerView() override { return &m_bannerView; } void reloadBannerView() override; - bool moveCursorHorizontally(int direction) override; + bool moveCursorHorizontally(int direction, bool fast = false) override; int nextCurveIndexVertically(bool goingUp, int currentSelectedCurve, Poincare::Context * context) const override; double defaultCursorT(Ion::Storage::Record record) override; Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; } diff --git a/apps/graph/graph/graph_controller_helper.cpp b/apps/graph/graph/graph_controller_helper.cpp index 39e0b35d3..0c21be79c 100644 --- a/apps/graph/graph/graph_controller_helper.cpp +++ b/apps/graph/graph/graph_controller_helper.cpp @@ -12,7 +12,7 @@ namespace Graph { static inline double minDouble(double x, double y) { return x < y ? x : y; } static inline double maxDouble(double x, double y) { return x > y ? x : y; } -bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record) { +bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, bool fast) { ExpiringPointer function = App::app()->functionStore()->modelForRecord(record); double tCursorPosition = cursor->t(); double t = tCursorPosition; @@ -28,13 +28,12 @@ bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCurso } function = App::app()->functionStore()->modelForRecord(record); // Reload the expiring pointer double dir = (direction > 0 ? 1.0 : -1.0); - ContinuousFunction::PlotType type = function->plotType(); - if (type == ContinuousFunction::PlotType::Cartesian) { - t += dir * range->xGridUnit()/numberOfStepsInGradUnit; - } else { - assert(type == ContinuousFunction::PlotType::Polar || type == ContinuousFunction::PlotType::Parametric); - t += dir * (tMax-tMin)/k_definitionDomainDivisor; + double step = function->plotType() == ContinuousFunction::PlotType::Cartesian ? range->xGridUnit()/numberOfStepsInGradUnit : (tMax-tMin)/k_definitionDomainDivisor; + if (fast) { + // TODO Navigate quicker the longer the user presses? (slow start) + step *= 5.0; } + t += dir * step; t = maxDouble(tMin, minDouble(tMax, t)); Coordinate2D xy = function->evaluateXYAtParameter(t, App::app()->localContext()); cursor->moveTo(t, xy.x1(), xy.x2()); diff --git a/apps/graph/graph/graph_controller_helper.h b/apps/graph/graph/graph_controller_helper.h index 8f8e2a7b7..dcc6daf02 100644 --- a/apps/graph/graph/graph_controller_helper.h +++ b/apps/graph/graph/graph_controller_helper.h @@ -11,7 +11,7 @@ class App; class GraphControllerHelper { protected: - bool privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record); + bool privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, bool fast = false); void reloadDerivativeInBannerViewForCursorOnFunction(Shared::CurveViewCursor * cursor, Ion::Storage::Record record); virtual BannerView * bannerView() = 0; private: diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index 47d1e0c2e..7c966635a 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -29,6 +29,13 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { Ion::Storage::Record record = functionStore->activeRecordAtIndex(i); ExpiringPointer f = functionStore->modelForRecord(record);; Shared::ContinuousFunction::PlotType type = f->plotType(); + Poincare::Expression e = f->expressionReduced(context()); + if (e.isUndefined() || ( + type == Shared::ContinuousFunction::PlotType::Parametric && + e.childAtIndex(0).isUndefined() && + e.childAtIndex(1).isUndefined())) { + continue; + } float tmin = f->tMin(); float tmax = f->tMax(); /* The step is a fraction of tmax-tmin. We will evaluate the function at @@ -50,7 +57,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { ContinuousFunction * f = (ContinuousFunction *)model; Poincare::Context * c = (Poincare::Context *)context; return f->evaluateXYAtParameter(t, c); - }, f.operator->(), context(), f->color(), record == m_selectedRecord, m_highlightedStart, m_highlightedEnd); + }, f.operator->(), context(), f->color(), true, record == m_selectedRecord, m_highlightedStart, m_highlightedEnd); /* Draw tangent */ if (m_tangent && record == m_selectedRecord) { float tangentParameter[2]; diff --git a/apps/graph/graph/tangent_graph_controller.cpp b/apps/graph/graph/tangent_graph_controller.cpp index de88c4fe3..e6bb93a4d 100644 --- a/apps/graph/graph/tangent_graph_controller.cpp +++ b/apps/graph/graph/tangent_graph_controller.cpp @@ -89,7 +89,7 @@ void TangentGraphController::reloadBannerView() { m_bannerView->reload(); } -bool TangentGraphController::moveCursorHorizontally(int direction) { +bool TangentGraphController::moveCursorHorizontally(int direction, bool fast) { return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, m_record); } diff --git a/apps/graph/graph/tangent_graph_controller.h b/apps/graph/graph/tangent_graph_controller.h index a1652e0de..7e3103203 100644 --- a/apps/graph/graph/tangent_graph_controller.h +++ b/apps/graph/graph/tangent_graph_controller.h @@ -23,7 +23,7 @@ private: Shared::CurveView * curveView() override { return m_graphView; } BannerView * bannerView() override { return m_bannerView; }; void reloadBannerView() override; - bool moveCursorHorizontally(int direction) override; + bool moveCursorHorizontally(int direction, bool fast = false) override; bool handleEnter() override; GraphView * m_graphView; BannerView * m_bannerView; diff --git a/apps/graph/list/list_controller.cpp b/apps/graph/list/list_controller.cpp index 99d1906c1..78261ded5 100644 --- a/apps/graph/list/list_controller.cpp +++ b/apps/graph/list/list_controller.cpp @@ -67,8 +67,8 @@ bool ListController::textFieldDidFinishEditing(TextField * textField, const char GlobalContext::DestroyRecordsBaseNamedWithoutExtension(baseName, Ion::Storage::funcExtension); // Set the name - Function::NameNotCompliantError nameError = Function::NameNotCompliantError::None; - Ion::Storage::Record::ErrorStatus error = Function::BaseNameCompliant(baseName, &nameError) ? modelStore()->recordAtIndex(m_selectableTableView.selectedRow()).setBaseNameWithExtension(baseName, Ion::Storage::funcExtension) : Ion::Storage::Record::ErrorStatus::NonCompliantName; + Function::NameNotCompliantError nameError = Function::BaseNameCompliant(baseName); + Ion::Storage::Record::ErrorStatus error = nameError == Function::NameNotCompliantError::None ? modelStore()->recordAtIndex(m_selectableTableView.selectedRow()).setBaseNameWithExtension(baseName, Ion::Storage::funcExtension) : Ion::Storage::Record::ErrorStatus::NonCompliantName; // Handle any error if (error == Ion::Storage::Record::ErrorStatus::None) { diff --git a/apps/graph/list/list_parameter_controller.cpp b/apps/graph/list/list_parameter_controller.cpp index 331808e24..29b7bbdef 100644 --- a/apps/graph/list/list_parameter_controller.cpp +++ b/apps/graph/list/list_parameter_controller.cpp @@ -89,7 +89,7 @@ void ListParameterController::willDisplayCellForIndex(HighlightCell * cell, int constexpr int bufferSize = BufferTextView::k_maxNumberOfChar; char buffer[bufferSize]; int glyphLength = writeInterval(buffer, bufferSize, min, max, Preferences::VeryShortNumberOfSignificantDigits, Preferences::sharedPreferences()->displayMode()); - int numberOfAvailableGlyphs = (m_functionDomain.bounds().width() - m_functionDomain.labelView()->bounds().width() - m_functionDomain.accessoryView()->bounds().width() - TableCell::k_labelMargin - TableCell::k_accessoryMargin)/KDFont::SmallFont->glyphSize().width(); + int numberOfAvailableGlyphs = (m_functionDomain.bounds().width() - m_functionDomain.labelView()->bounds().width() - m_functionDomain.accessoryView()->bounds().width() - 2*Metric::TableCellHorizontalMargin)/KDFont::SmallFont->glyphSize().width(); if (glyphLength > numberOfAvailableGlyphs) { writeInterval(buffer, bufferSize, min, max, Preferences::VeryShortNumberOfSignificantDigits-1, Preferences::PrintFloatMode::Scientific); } diff --git a/apps/graph/list/text_field_function_title_cell.cpp b/apps/graph/list/text_field_function_title_cell.cpp index ecd7a7567..a1d138e24 100644 --- a/apps/graph/list/text_field_function_title_cell.cpp +++ b/apps/graph/list/text_field_function_title_cell.cpp @@ -52,9 +52,9 @@ void TextFieldFunctionTitleCell::setHorizontalAlignment(float alignment) { m_textField.setAlignment(alignment, verticalAlignment()); } -void TextFieldFunctionTitleCell::layoutSubviews() { +void TextFieldFunctionTitleCell::layoutSubviews(bool force) { KDRect frame = subviewFrame(); - m_textField.setFrame(frame); + m_textField.setFrame(frame, force); KDCoordinate maxTextFieldX = frame.width() - m_textField.minimalSizeForOptimalDisplay().width(); float horizontalAlignment = maxFloat( 0.0f, diff --git a/apps/graph/list/text_field_function_title_cell.h b/apps/graph/list/text_field_function_title_cell.h index 0d2b95d9b..dd76523e5 100644 --- a/apps/graph/list/text_field_function_title_cell.h +++ b/apps/graph/list/text_field_function_title_cell.h @@ -34,7 +34,7 @@ public: assert(index == 0); return &m_textField; } - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; // Responder void didBecomeFirstResponder() override; diff --git a/apps/graph/list/type_helper.cpp b/apps/graph/list/type_helper.cpp index ee1a1d871..130e90160 100644 --- a/apps/graph/list/type_helper.cpp +++ b/apps/graph/list/type_helper.cpp @@ -28,7 +28,7 @@ Poincare::Layout Layout(int index) { if (index < 2) { return Poincare::LayoutHelper::String(text, strlen(text)); } - Poincare::Expression parametric = Poincare::Expression::Parse(text); + Poincare::Expression parametric = Poincare::Expression::Parse(text, nullptr); // No need for context return parametric.createLayout(Poincare::Preferences::PrintFloatMode::Decimal, 1); } diff --git a/apps/graph/list/type_parameter_controller.cpp b/apps/graph/list/type_parameter_controller.cpp index 93f4e6143..13a4ecb2e 100644 --- a/apps/graph/list/type_parameter_controller.cpp +++ b/apps/graph/list/type_parameter_controller.cpp @@ -24,7 +24,7 @@ bool TypeParameterController::handleEvent(Ion::Events::Event event) { App * myApp = App::app(); assert(!m_record.isNull()); Shared::ExpiringPointer function = myApp->functionStore()->modelForRecord(m_record); - function->setPlotType(plotType, Poincare::Preferences::sharedPreferences()->angleUnit()); + function->setPlotType(plotType, Poincare::Preferences::sharedPreferences()->angleUnit(), myApp->localContext()); StackViewController * stack = stackController(); stack->pop(); stack->pop(); diff --git a/apps/graph/values/abscissa_title_cell.cpp b/apps/graph/values/abscissa_title_cell.cpp index bd174fccd..59ed57aa8 100644 --- a/apps/graph/values/abscissa_title_cell.cpp +++ b/apps/graph/values/abscissa_title_cell.cpp @@ -12,8 +12,8 @@ void AbscissaTitleCell::drawRect(KDContext * ctx, KDRect rect) const { } } -void AbscissaTitleCell::layoutSubviews() { - m_messageTextView.setFrame(rectWithoutSeparator(bounds())); +void AbscissaTitleCell::layoutSubviews(bool force) { + m_messageTextView.setFrame(rectWithoutSeparator(bounds()), force); } void AbscissaTitleCell::didSetSeparator() { diff --git a/apps/graph/values/abscissa_title_cell.h b/apps/graph/values/abscissa_title_cell.h index 6c786c638..fd1ca6f1a 100644 --- a/apps/graph/values/abscissa_title_cell.h +++ b/apps/graph/values/abscissa_title_cell.h @@ -10,7 +10,7 @@ class AbscissaTitleCell : public EvenOddMessageTextCell, public Shared::Separabl public: AbscissaTitleCell() : EvenOddMessageTextCell(), Separable() {} void drawRect(KDContext * ctx, KDRect rect) const override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; private: void didSetSeparator() override; }; diff --git a/apps/graph/values/interval_parameter_selector_controller.cpp b/apps/graph/values/interval_parameter_selector_controller.cpp index 67c2f57a1..80cd68f74 100644 --- a/apps/graph/values/interval_parameter_selector_controller.cpp +++ b/apps/graph/values/interval_parameter_selector_controller.cpp @@ -20,7 +20,7 @@ void IntervalParameterSelectorController::viewDidDisappear() { /* Deselect the table properly because it needs to be relayouted the next time * it appears: the number of rows might change according to the plot type. */ m_selectableTableView.deselectTable(false); - m_selectableTableView.setFrame(KDRectZero); + m_selectableTableView.setFrame(KDRectZero, false); } void IntervalParameterSelectorController::didBecomeFirstResponder() { diff --git a/apps/hardware_test/battery_test_controller.cpp b/apps/hardware_test/battery_test_controller.cpp index 997a99ea1..37dbd6549 100644 --- a/apps/hardware_test/battery_test_controller.cpp +++ b/apps/hardware_test/battery_test_controller.cpp @@ -94,11 +94,11 @@ void BatteryTestController::ContentView::setColor(KDColor color) { m_batteryChargingView.setBackgroundColor(color); } -void BatteryTestController::ContentView::layoutSubviews() { - m_batteryStateView.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height/2)); +void BatteryTestController::ContentView::layoutSubviews(bool force) { + m_batteryStateView.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height/2), force); KDSize textSize = KDFont::SmallFont->glyphSize(); - m_batteryLevelView.setFrame(KDRect(0, Ion::Display::Height-2*textSize.height(), Ion::Display::Width, textSize.height())); - m_batteryChargingView.setFrame(KDRect(0, Ion::Display::Height-textSize.height(), Ion::Display::Width, textSize.height())); + m_batteryLevelView.setFrame(KDRect(0, Ion::Display::Height-2*textSize.height(), Ion::Display::Width, textSize.height()), force); + m_batteryChargingView.setFrame(KDRect(0, Ion::Display::Height-textSize.height(), Ion::Display::Width, textSize.height()), force); } int BatteryTestController::ContentView::numberOfSubviews() const { diff --git a/apps/hardware_test/battery_test_controller.h b/apps/hardware_test/battery_test_controller.h index 723155abb..2d30757ab 100644 --- a/apps/hardware_test/battery_test_controller.h +++ b/apps/hardware_test/battery_test_controller.h @@ -21,7 +21,7 @@ private: constexpr static int k_maxNumberOfCharacters = 20; void setColor(KDColor color) override; private: - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; constexpr static int k_margin = 4; diff --git a/apps/hardware_test/code_128b_view.cpp b/apps/hardware_test/code_128b_view.cpp index 75d011ab6..9d1a30636 100644 --- a/apps/hardware_test/code_128b_view.cpp +++ b/apps/hardware_test/code_128b_view.cpp @@ -20,7 +20,7 @@ void Code128BView::setData(const char * data) { markRectAsDirty(bounds()); } -void Code128BView::layoutSubviews() { +void Code128BView::layoutSubviews(bool force) { updateModuleWidth(); } diff --git a/apps/hardware_test/code_128b_view.h b/apps/hardware_test/code_128b_view.h index c437e8e27..a6894a946 100644 --- a/apps/hardware_test/code_128b_view.h +++ b/apps/hardware_test/code_128b_view.h @@ -10,7 +10,7 @@ public: Code128BView(); void drawRect(KDContext * ctx, KDRect rect) const override; void setData(const char * data); - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; private: static constexpr KDCoordinate k_outlineThickness = 1; static constexpr KDCoordinate k_charPatternWidth = 11; diff --git a/apps/hardware_test/colors_lcd_test_controller.cpp b/apps/hardware_test/colors_lcd_test_controller.cpp index 25a14f535..cc2ce7f43 100644 --- a/apps/hardware_test/colors_lcd_test_controller.cpp +++ b/apps/hardware_test/colors_lcd_test_controller.cpp @@ -33,8 +33,8 @@ void ColorsLCDTestController::ContentView::setColor(KDColor color) { m_colorsLCDStateView.setBackgroundColor(color); } -void ColorsLCDTestController::ContentView::layoutSubviews() { - m_colorsLCDStateView.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height)); +void ColorsLCDTestController::ContentView::layoutSubviews(bool force) { + m_colorsLCDStateView.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height), force); } } diff --git a/apps/hardware_test/colors_lcd_test_controller.h b/apps/hardware_test/colors_lcd_test_controller.h index 91cd15e73..005b7a8d8 100644 --- a/apps/hardware_test/colors_lcd_test_controller.h +++ b/apps/hardware_test/colors_lcd_test_controller.h @@ -22,7 +22,7 @@ private: BufferTextView * colorsLCDStateTextView() { return &m_colorsLCDStateView; } void setColor(KDColor color) override; private: - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; int numberOfSubviews() const override { return 1; } View * subviewAtIndex(int index) override { assert(index == 0); diff --git a/apps/hardware_test/lcd_data_test_controller.cpp b/apps/hardware_test/lcd_data_test_controller.cpp index fbc2cb364..06bbd81bf 100644 --- a/apps/hardware_test/lcd_data_test_controller.cpp +++ b/apps/hardware_test/lcd_data_test_controller.cpp @@ -46,9 +46,9 @@ void LCDDataTestController::ContentView::setStatus(bool success, int numberOfErr m_lcdNumberPixelFailuresView.setText(buffer); } -void LCDDataTestController::ContentView::layoutSubviews() { - m_lcdDataStateView.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height)); - m_lcdNumberPixelFailuresView.setFrame(KDRect(10, 10, Ion::Display::Width, 20)); +void LCDDataTestController::ContentView::layoutSubviews(bool force) { + m_lcdDataStateView.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height), force); + m_lcdNumberPixelFailuresView.setFrame(KDRect(10, 10, Ion::Display::Width, 20), force); } } diff --git a/apps/hardware_test/lcd_data_test_controller.h b/apps/hardware_test/lcd_data_test_controller.h index 2d261b3b6..507edaa8a 100644 --- a/apps/hardware_test/lcd_data_test_controller.h +++ b/apps/hardware_test/lcd_data_test_controller.h @@ -24,7 +24,7 @@ private: private: constexpr static const char * k_lcdDataPassTest = "LCD DATA: OK"; constexpr static const char * k_lcdDataFailTest = "LCD DATA: FAIL"; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; int numberOfSubviews() const override { return 2; } View * subviewAtIndex(int index) override { assert(index >= 0 && index < 2); diff --git a/apps/hardware_test/lcd_timing_test_controller.cpp b/apps/hardware_test/lcd_timing_test_controller.cpp index 61492a729..fbdb193cf 100644 --- a/apps/hardware_test/lcd_timing_test_controller.cpp +++ b/apps/hardware_test/lcd_timing_test_controller.cpp @@ -47,9 +47,9 @@ void LCDTimingTestController::ContentView::setStatus(bool success, int numberOfE m_lcdNumberGlyphFailuresView.setText(buffer); } -void LCDTimingTestController::ContentView::layoutSubviews() { - m_lcdTimingStateView.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height)); - m_lcdNumberGlyphFailuresView.setFrame(KDRect(10, 10, Ion::Display::Width, 20)); +void LCDTimingTestController::ContentView::layoutSubviews(bool force) { + m_lcdTimingStateView.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height), force); + m_lcdNumberGlyphFailuresView.setFrame(KDRect(10, 10, Ion::Display::Width, 20), force); } } diff --git a/apps/hardware_test/lcd_timing_test_controller.h b/apps/hardware_test/lcd_timing_test_controller.h index 17cfbf386..75bf7fccc 100644 --- a/apps/hardware_test/lcd_timing_test_controller.h +++ b/apps/hardware_test/lcd_timing_test_controller.h @@ -25,7 +25,7 @@ private: private: constexpr static const char * k_lcdTimingPassTest = "LCD TIMING: OK"; constexpr static const char * k_lcdTimingFailTest = "LCD TIMING: FAIL"; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; int numberOfSubviews() const override { return 2; } View * subviewAtIndex(int index) override { assert(index >= 0 && index < 2); diff --git a/apps/hardware_test/led_test_controller.cpp b/apps/hardware_test/led_test_controller.cpp index 07b3917b1..54c3ec549 100644 --- a/apps/hardware_test/led_test_controller.cpp +++ b/apps/hardware_test/led_test_controller.cpp @@ -58,12 +58,12 @@ SolidColorView * LEDTestController::ContentView::LEDColorIndicatorView() { return &m_ledColorIndicatorView; } -void LEDTestController::ContentView::layoutSubviews() { +void LEDTestController::ContentView::layoutSubviews(bool force) { KDSize ledSize = m_ledView.minimalSizeForOptimalDisplay(); - m_ledView.setFrame(KDRect((Ion::Display::Width-ledSize.width()-k_indicatorSize-k_indicatorMargin)/2, k_arrowLength+2*k_arrowMargin, ledSize.width(), ledSize.height())); - m_ledColorIndicatorView.setFrame(KDRect((Ion::Display::Width-k_indicatorSize)/2+k_indicatorMargin/2+ledSize.width()/2, k_arrowLength+2*k_arrowMargin, k_indicatorSize, k_indicatorSize)); - m_ledColorOutlineView.setFrame(KDRect((Ion::Display::Width-k_indicatorSize)/2+k_indicatorMargin/2+ledSize.width()/2-1, k_arrowLength+2*k_arrowMargin-1, k_indicatorSize+2, k_indicatorSize+2)); - m_arrowView.setFrame(KDRect(0, k_arrowMargin, bounds().width(), k_arrowLength)); + m_ledView.setFrame(KDRect((Ion::Display::Width-ledSize.width()-k_indicatorSize-k_indicatorMargin)/2, k_arrowLength+2*k_arrowMargin, ledSize.width(), ledSize.height()), force); + m_ledColorIndicatorView.setFrame(KDRect((Ion::Display::Width-k_indicatorSize)/2+k_indicatorMargin/2+ledSize.width()/2, k_arrowLength+2*k_arrowMargin, k_indicatorSize, k_indicatorSize), force); + m_ledColorOutlineView.setFrame(KDRect((Ion::Display::Width-k_indicatorSize)/2+k_indicatorMargin/2+ledSize.width()/2-1, k_arrowLength+2*k_arrowMargin-1, k_indicatorSize+2, k_indicatorSize+2), force); + m_arrowView.setFrame(KDRect(0, k_arrowMargin, bounds().width(), k_arrowLength), force); } int LEDTestController::ContentView::numberOfSubviews() const { diff --git a/apps/hardware_test/led_test_controller.h b/apps/hardware_test/led_test_controller.h index 92d74ed0b..6c339f206 100644 --- a/apps/hardware_test/led_test_controller.h +++ b/apps/hardware_test/led_test_controller.h @@ -18,7 +18,7 @@ private: ContentView(); SolidColorView * LEDColorIndicatorView(); private: - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; SolidColorView m_ledColorIndicatorView; diff --git a/apps/hardware_test/pop_up_controller.cpp b/apps/hardware_test/pop_up_controller.cpp index 24a0592b5..76afaa19b 100644 --- a/apps/hardware_test/pop_up_controller.cpp +++ b/apps/hardware_test/pop_up_controller.cpp @@ -100,17 +100,17 @@ View * PopUpController::ContentView::subviewAtIndex(int index) { } } -void PopUpController::ContentView::layoutSubviews() { +void PopUpController::ContentView::layoutSubviews(bool force) { KDCoordinate height = bounds().height(); KDCoordinate width = bounds().width(); KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height(); - m_warningTextView.setFrame(KDRect(0, k_topMargin, width, textHeight)); - m_messageTextView1.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+textHeight, width, textHeight)); - m_messageTextView2.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+2*textHeight, width, textHeight)); - m_messageTextView3.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+3*textHeight, width, textHeight)); - m_messageTextView4.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+4*textHeight, width, textHeight)); - m_cancelButton.setFrame(KDRect(k_buttonMargin, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight)); - m_okButton.setFrame(KDRect(2*k_buttonMargin+(width-3*k_buttonMargin)/2, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight)); + m_warningTextView.setFrame(KDRect(0, k_topMargin, width, textHeight), force); + m_messageTextView1.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+textHeight, width, textHeight), force); + m_messageTextView2.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+2*textHeight, width, textHeight), force); + m_messageTextView3.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+3*textHeight, width, textHeight), force); + m_messageTextView4.setFrame(KDRect(0, k_topMargin+k_paragraphHeight+4*textHeight, width, textHeight), force); + m_cancelButton.setFrame(KDRect(k_buttonMargin, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight), force); + m_okButton.setFrame(KDRect(2*k_buttonMargin+(width-3*k_buttonMargin)/2, height-k_buttonMargin-k_buttonHeight, (width-3*k_buttonMargin)/2, k_buttonHeight), force); } } diff --git a/apps/hardware_test/pop_up_controller.h b/apps/hardware_test/pop_up_controller.h index bdaa8f1ee..b04724e95 100644 --- a/apps/hardware_test/pop_up_controller.h +++ b/apps/hardware_test/pop_up_controller.h @@ -25,7 +25,7 @@ private: constexpr static KDCoordinate k_paragraphHeight = 20; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; Button m_cancelButton; Button m_okButton; MessageTextView m_warningTextView; diff --git a/apps/hardware_test/vblank_test_controller.cpp b/apps/hardware_test/vblank_test_controller.cpp index 9736c921c..57845dfa7 100644 --- a/apps/hardware_test/vblank_test_controller.cpp +++ b/apps/hardware_test/vblank_test_controller.cpp @@ -33,8 +33,8 @@ void VBlankTestController::ContentView::setColor(KDColor color) { m_vBlankStateView.setBackgroundColor(color); } -void VBlankTestController::ContentView::layoutSubviews() { - m_vBlankStateView.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height)); +void VBlankTestController::ContentView::layoutSubviews(bool force) { + m_vBlankStateView.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height), force); } } diff --git a/apps/hardware_test/vblank_test_controller.h b/apps/hardware_test/vblank_test_controller.h index 951394586..f13bae303 100644 --- a/apps/hardware_test/vblank_test_controller.h +++ b/apps/hardware_test/vblank_test_controller.h @@ -22,7 +22,7 @@ private: BufferTextView * vBlankStateTextView() { return &m_vBlankStateView; } void setColor(KDColor color) override; private: - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; int numberOfSubviews() const override { return 1; } View * subviewAtIndex(int index) override { assert(index == 0); diff --git a/apps/home/app_cell.cpp b/apps/home/app_cell.cpp index f456c0438..27600e379 100644 --- a/apps/home/app_cell.cpp +++ b/apps/home/app_cell.cpp @@ -25,10 +25,10 @@ View * AppCell::subviewAtIndex(int index) { return views[index]; } -void AppCell::layoutSubviews() { - m_iconView.setFrame(KDRect((bounds().width()-k_iconWidth)/2, k_iconMargin, k_iconWidth,k_iconHeight)); +void AppCell::layoutSubviews(bool force) { + m_iconView.setFrame(KDRect((bounds().width()-k_iconWidth)/2, k_iconMargin, k_iconWidth,k_iconHeight), force); KDSize nameSize = m_nameView.minimalSizeForOptimalDisplay(); - m_nameView.setFrame(KDRect((bounds().width()-nameSize.width())/2-k_nameWidthMargin, bounds().height()-nameSize.height() - 2*k_nameHeightMargin, nameSize.width()+2*k_nameWidthMargin, nameSize.height()+2*k_nameHeightMargin)); + m_nameView.setFrame(KDRect((bounds().width()-nameSize.width())/2-k_nameWidthMargin, bounds().height()-nameSize.height() - 2*k_nameHeightMargin, nameSize.width()+2*k_nameWidthMargin, nameSize.height()+2*k_nameHeightMargin), force); } void AppCell::setAppDescriptor(::App::Descriptor * descriptor) { diff --git a/apps/home/app_cell.h b/apps/home/app_cell.h index 19dabf6fe..73cb7e0f9 100644 --- a/apps/home/app_cell.h +++ b/apps/home/app_cell.h @@ -12,7 +12,7 @@ public: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; void setVisible(bool visible); void reloadCell() override; diff --git a/apps/home/controller.cpp b/apps/home/controller.cpp index 485a75765..4f2f14249 100644 --- a/apps/home/controller.cpp +++ b/apps/home/controller.cpp @@ -45,8 +45,8 @@ View * Controller::ContentView::subviewAtIndex(int index) { return &m_selectableTableView; } -void Controller::ContentView::layoutSubviews() { - m_selectableTableView.setFrame(bounds()); +void Controller::ContentView::layoutSubviews(bool force) { + m_selectableTableView.setFrame(bounds(), force); } Controller::Controller(Responder * parentResponder, SelectableTableViewDataSource * selectionDataSource) : diff --git a/apps/home/controller.h b/apps/home/controller.h index b30bf89d1..428de3ccb 100644 --- a/apps/home/controller.h +++ b/apps/home/controller.h @@ -37,7 +37,7 @@ private: private: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; SelectableTableView m_selectableTableView; }; static constexpr KDCoordinate k_sideMargin = 4; diff --git a/apps/i18n.py b/apps/i18n.py index 8aa0bf450..4f4b44d49 100644 --- a/apps/i18n.py +++ b/apps/i18n.py @@ -33,6 +33,10 @@ def source_definition(i18n_string): result = result + u"\"" return result.encode("utf-8") +def is_commented_line(line): + match_comment = re.match(r"^#(.*)$", line) + return match_comment + def split_line(line): match = re.match(r"^(\w+)\s*=\s*\"(.*)\"$", line) if not match: @@ -53,6 +57,8 @@ def parse_files(files): data[locale] = {} with io.open(path, "r", encoding='utf-8') as file: for line in file: + if is_commented_line(line): + continue name,definition = split_line(line) if locale == "universal": if name in messages: diff --git a/apps/math_toolbox.cpp b/apps/math_toolbox.cpp index a01af1c42..cbc9871a3 100644 --- a/apps/math_toolbox.cpp +++ b/apps/math_toolbox.cpp @@ -5,6 +5,16 @@ using namespace Poincare; +/* TODO + * Unit submenu should be created from the Poincare::Unit::Representative table. + * This would avoid to store duplicates const char *. + * This would requires classes as: + * - PointerTree parent of MessageTree and BufferTree + * - PointerTableCell instead of MessageTableCell + * + * We should add in the model tree the possibility to indicate a Leaf that has + * to be selected when the menu appears. */ + /* We create one model tree: each node keeps the label of the row it refers to * and the text which would be edited by clicking on the row. When the node is a * subtree, the edited text is set at I18n::Message::Default. */ @@ -74,6 +84,175 @@ const ToolboxMessageTree listsChildren[] = { }; #endif +const ToolboxMessageTree unitTimeSecondChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitTimeSecondSymbol, I18n::Message::UnitTimeSecond), + ToolboxMessageTree::Leaf(I18n::Message::UnitTimeSecondMilliSymbol, I18n::Message::UnitTimeSecondMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitTimeSecondMicroSymbol, I18n::Message::UnitTimeSecondMicro), + ToolboxMessageTree::Leaf(I18n::Message::UnitTimeSecondNanoSymbol, I18n::Message::UnitTimeSecondNano), +}; + +const ToolboxMessageTree unitTimeChildren[] = { + ToolboxMessageTree::Node(I18n::Message::UnitTimeSecondMenu, unitTimeSecondChildren), + ToolboxMessageTree::Leaf(I18n::Message::UnitTimeMinuteSymbol, I18n::Message::UnitTimeMinute), + ToolboxMessageTree::Leaf(I18n::Message::UnitTimeHourSymbol, I18n::Message::UnitTimeHour), + ToolboxMessageTree::Leaf(I18n::Message::UnitTimeDaySymbol, I18n::Message::UnitTimeDay), + ToolboxMessageTree::Leaf(I18n::Message::UnitTimeWeekSymbol, I18n::Message::UnitTimeWeek), + ToolboxMessageTree::Leaf(I18n::Message::UnitTimeMonthSymbol, I18n::Message::UnitTimeMonth), + ToolboxMessageTree::Leaf(I18n::Message::UnitTimeYearSymbol, I18n::Message::UnitTimeYear)}; + +const ToolboxMessageTree unitDistanceMeterChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterKiloSymbol, I18n::Message::UnitDistanceMeterKilo), + ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterSymbol, I18n::Message::UnitDistanceMeter), + ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterMilliSymbol, I18n::Message::UnitDistanceMeterMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterMicroSymbol, I18n::Message::UnitDistanceMeterMicro), + ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterNanoSymbol, I18n::Message::UnitDistanceMeterNano), + ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceMeterPicoSymbol, I18n::Message::UnitDistanceMeterPico), +}; + +const ToolboxMessageTree unitDistanceChildren[] = { + ToolboxMessageTree::Node(I18n::Message::UnitDistanceMeterMenu, unitDistanceMeterChildren), + ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceAstronomicalUnitSymbol, I18n::Message::UnitDistanceAstronomicalUnit), + ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceLightYearSymbol, I18n::Message::UnitDistanceLightYear), + ToolboxMessageTree::Leaf(I18n::Message::UnitDistanceParsecSymbol, I18n::Message::UnitDistanceParsec)}; + +const ToolboxMessageTree unitMassChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitMassTonneSymbol, I18n::Message::UnitMassTonne), + ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramKiloSymbol, I18n::Message::UnitMassGramKilo), + ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramSymbol, I18n::Message::UnitMassGram), + ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramMilliSymbol, I18n::Message::UnitMassGramMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramMicroSymbol, I18n::Message::UnitMassGramMicro), + ToolboxMessageTree::Leaf(I18n::Message::UnitMassGramNanoSymbol, I18n::Message::UnitMassGramNano), +}; + +const ToolboxMessageTree unitCurrentAmpereChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitCurrentAmpereSymbol, I18n::Message::UnitCurrentAmpere), + ToolboxMessageTree::Leaf(I18n::Message::UnitCurrentAmpereMilliSymbol, I18n::Message::UnitCurrentAmpereMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitCurrentAmpereMicroSymbol, I18n::Message::UnitCurrentAmpereMicro), +}; + +const ToolboxMessageTree unitTemperatureChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitTemperatureKelvinSymbol, I18n::Message::UnitTemperatureKelvin)}; + +const ToolboxMessageTree unitAmountMoleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitAmountMoleSymbol, I18n::Message::UnitAmountMole), + ToolboxMessageTree::Leaf(I18n::Message::UnitAmountMoleMilliSymbol, I18n::Message::UnitAmountMoleMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitAmountMoleMicroSymbol, I18n::Message::UnitAmountMoleMicro), +}; + +const ToolboxMessageTree unitLuminousIntensityChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitLuminousIntensityCandelaSymbol, I18n::Message::UnitLuminousIntensityCandela)}; + +const ToolboxMessageTree unitFrequencyHertzChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitFrequencyHertzGigaSymbol, I18n::Message::UnitFrequencyHertzGiga), + ToolboxMessageTree::Leaf(I18n::Message::UnitFrequencyHertzMegaSymbol, I18n::Message::UnitFrequencyHertzMega), + ToolboxMessageTree::Leaf(I18n::Message::UnitFrequencyHertzKiloSymbol, I18n::Message::UnitFrequencyHertzKilo), +ToolboxMessageTree::Leaf(I18n::Message::UnitFrequencyHertzSymbol, I18n::Message::UnitFrequencyHertz)}; + +const ToolboxMessageTree unitForceNewtonChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitForceNewtonKiloSymbol, I18n::Message::UnitForceNewtonKilo), + ToolboxMessageTree::Leaf(I18n::Message::UnitForceNewtonSymbol, I18n::Message::UnitForceNewton), + ToolboxMessageTree::Leaf(I18n::Message::UnitForceNewtonMilliSymbol, I18n::Message::UnitForceNewtonMilli), +}; + +const ToolboxMessageTree unitPressureChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitPressurePascalSymbol, I18n::Message::UnitPressurePascal), + ToolboxMessageTree::Leaf(I18n::Message::UnitPressurePascalHectoSymbol, I18n::Message::UnitPressurePascalHecto), + ToolboxMessageTree::Leaf(I18n::Message::UnitPressureBarSymbol, I18n::Message::UnitPressureBar), + ToolboxMessageTree::Leaf(I18n::Message::UnitPressureAtmSymbol, I18n::Message::UnitPressureAtm)}; + +const ToolboxMessageTree unitEnergyJouleChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyJouleKiloSymbol, I18n::Message::UnitEnergyJouleKilo), + ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyJouleSymbol, I18n::Message::UnitEnergyJoule), + ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyJouleMilliSymbol, I18n::Message::UnitEnergyJouleMilli), +}; + +const ToolboxMessageTree unitEnergyElectronVoltChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyElectronVoltMegaSymbol, I18n::Message::UnitEnergyElectronVoltMega), + ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyElectronVoltKiloSymbol, I18n::Message::UnitEnergyElectronVoltKilo), + ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyElectronVoltSymbol, I18n::Message::UnitEnergyElectronVolt), + ToolboxMessageTree::Leaf(I18n::Message::UnitEnergyElectronVoltMilliSymbol, I18n::Message::UnitEnergyElectronVoltMilli), +}; + +const ToolboxMessageTree unitEnergyChildren[] = { + ToolboxMessageTree::Node(I18n::Message::UnitEnergyJouleMenu, unitEnergyJouleChildren), + ToolboxMessageTree::Node(I18n::Message::UnitEnergyEletronVoltMenu, unitEnergyElectronVoltChildren)}; + +const ToolboxMessageTree unitPowerWattChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattGigaSymbol, I18n::Message::UnitPowerWattGiga), + ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattMegaSymbol, I18n::Message::UnitPowerWattMega), + ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattKiloSymbol, I18n::Message::UnitPowerWattKilo), + ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattSymbol, I18n::Message::UnitPowerWatt), + ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattMilliSymbol, I18n::Message::UnitPowerWattMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitPowerWattMicroSymbol, I18n::Message::UnitPowerWattMicro), +}; + +const ToolboxMessageTree unitElectricChargeCoulombChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitChargeCoulombSymbol, I18n::Message::UnitChargeCoulomb), +}; + +const ToolboxMessageTree unitPotentialVoltChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitPotentialVoltKiloSymbol, I18n::Message::UnitPotentialVoltKilo), + ToolboxMessageTree::Leaf(I18n::Message::UnitPotentialVoltSymbol, I18n::Message::UnitPotentialVolt), + ToolboxMessageTree::Leaf(I18n::Message::UnitPotentialVoltMilliSymbol, I18n::Message::UnitPotentialVoltMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitPotentialVoltMicroSymbol, I18n::Message::UnitPotentialVoltMicro), +}; + +const ToolboxMessageTree unitCapacitanceFaradChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitCapacitanceFaradSymbol, I18n::Message::UnitCapacitanceFarad), + ToolboxMessageTree::Leaf(I18n::Message::UnitCapacitanceFaradMilliSymbol, I18n::Message::UnitCapacitanceFaradMilli), + ToolboxMessageTree::Leaf(I18n::Message::UnitCapacitanceFaradMicroSymbol, I18n::Message::UnitCapacitanceFaradMicro), +}; + +const ToolboxMessageTree unitResistanceOhmChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitResistanceOhmKiloSymbol, I18n::Message::UnitResistanceOhmKilo), + ToolboxMessageTree::Leaf(I18n::Message::UnitResistanceOhmSymbol, I18n::Message::UnitResistanceOhm), +}; + +const ToolboxMessageTree unitConductanceSiemensChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitConductanceSiemensSymbol, I18n::Message::UnitConductanceSiemens), + ToolboxMessageTree::Leaf(I18n::Message::UnitConductanceSiemensMilliSymbol, I18n::Message::UnitConductanceSiemensMilli), +}; + +const ToolboxMessageTree unitMagneticFieldChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitMagneticFieldTeslaSymbol, I18n::Message::UnitMagneticFieldTesla)}; + +const ToolboxMessageTree unitInductanceChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitInductanceHenrySymbol, I18n::Message::UnitInductanceHenry)}; + +const ToolboxMessageTree unitSurfaceChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitSurfaceHectarSymbol, I18n::Message::UnitSurfaceHectar)}; + +const ToolboxMessageTree unitVolumeLiterChildren[] = { + ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeLiterSymbol, I18n::Message::UnitVolumeLiter), + ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeLiterDeciSymbol, I18n::Message::UnitVolumeLiterDeci), + ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeLiterCentiSymbol, I18n::Message::UnitVolumeLiterCenti), + ToolboxMessageTree::Leaf(I18n::Message::UnitVolumeLiterMilliSymbol, I18n::Message::UnitVolumeLiterMilli), +}; + +const ToolboxMessageTree unitChildren[] = { + ToolboxMessageTree::Node(I18n::Message::UnitTimeMenu, unitTimeChildren), + ToolboxMessageTree::Node(I18n::Message::UnitDistanceMenu, unitDistanceChildren), + ToolboxMessageTree::Node(I18n::Message::UnitMassMenu, unitMassChildren), + ToolboxMessageTree::Node(I18n::Message::UnitCurrentMenu, unitCurrentAmpereChildren), + ToolboxMessageTree::Node(I18n::Message::UnitTemperatureMenu, unitTemperatureChildren), + ToolboxMessageTree::Node(I18n::Message::UnitAmountMenu, unitAmountMoleChildren), + ToolboxMessageTree::Node(I18n::Message::UnitLuminousIntensityMenu, unitLuminousIntensityChildren), + ToolboxMessageTree::Node(I18n::Message::UnitFrequencyMenu, unitFrequencyHertzChildren), + ToolboxMessageTree::Node(I18n::Message::UnitForceMenu, unitForceNewtonChildren), + ToolboxMessageTree::Node(I18n::Message::UnitPressureMenu, unitPressureChildren), + ToolboxMessageTree::Node(I18n::Message::UnitEnergyMenu, unitEnergyChildren), + ToolboxMessageTree::Node(I18n::Message::UnitPowerMenu, unitPowerWattChildren), + ToolboxMessageTree::Node(I18n::Message::UnitElectricChargeMenu, unitElectricChargeCoulombChildren), + ToolboxMessageTree::Node(I18n::Message::UnitPotentialMenu, unitPotentialVoltChildren), + ToolboxMessageTree::Node(I18n::Message::UnitCapacitanceMenu, unitCapacitanceFaradChildren), + ToolboxMessageTree::Node(I18n::Message::UnitResistanceMenu, unitResistanceOhmChildren), + ToolboxMessageTree::Node(I18n::Message::UnitConductanceMenu, unitConductanceSiemensChildren), + ToolboxMessageTree::Node(I18n::Message::UnitMagneticFieldMenu, unitMagneticFieldChildren), + ToolboxMessageTree::Node(I18n::Message::InductanceMenu, unitInductanceChildren), + ToolboxMessageTree::Node(I18n::Message::UnitSurfaceMenu, unitSurfaceChildren), + ToolboxMessageTree::Node(I18n::Message::UnitVolumeMenu, unitVolumeLiterChildren), +}; + const ToolboxMessageTree randomAndApproximationChildren[] = { ToolboxMessageTree::Leaf(I18n::Message::RandomCommandWithArg, I18n::Message::RandomFloat), ToolboxMessageTree::Leaf(I18n::Message::RandintCommandWithArg, I18n::Message::RandomInteger), @@ -393,6 +572,7 @@ const ToolboxMessageTree menu[] = { #if LIST_ARE_DEFINED ToolboxMessageTree::Node(I18n::Message::Lists,listsChildren), #endif + ToolboxMessageTree::Node(I18n::Message::Unit, unitChildren), ToolboxMessageTree::Node(I18n::Message::RandomAndApproximation, randomAndApproximationChildren), ToolboxMessageTree::Node(I18n::Message::HyperbolicTrigonometry, trigonometryChildren), ToolboxMessageTree::Node(I18n::Message::Fluctuation, predictionChildren), diff --git a/apps/on_boarding/logo_view.cpp b/apps/on_boarding/logo_view.cpp index 84b142f7c..2bc4867b9 100644 --- a/apps/on_boarding/logo_view.cpp +++ b/apps/on_boarding/logo_view.cpp @@ -23,8 +23,8 @@ View * LogoView::subviewAtIndex(int index) { return &m_logoView; } -void LogoView::layoutSubviews() { - m_logoView.setFrame(KDRect((Ion::Display::Width - ImageStore::LogoIcon->width())/2, (Ion::Display::Height - ImageStore::LogoIcon->height())/2, ImageStore::LogoIcon->width(), ImageStore::LogoIcon->height())); +void LogoView::layoutSubviews(bool force) { + m_logoView.setFrame(KDRect((Ion::Display::Width - ImageStore::LogoIcon->width())/2, (Ion::Display::Height - ImageStore::LogoIcon->height())/2, ImageStore::LogoIcon->width(), ImageStore::LogoIcon->height()), force); } } diff --git a/apps/on_boarding/logo_view.h b/apps/on_boarding/logo_view.h index b1d1650d5..7cbfc62a6 100644 --- a/apps/on_boarding/logo_view.h +++ b/apps/on_boarding/logo_view.h @@ -12,7 +12,7 @@ public: private: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; ImageView m_logoView; }; diff --git a/apps/on_boarding/pop_up_controller.cpp b/apps/on_boarding/pop_up_controller.cpp index 4a057a2b4..c63328b9e 100644 --- a/apps/on_boarding/pop_up_controller.cpp +++ b/apps/on_boarding/pop_up_controller.cpp @@ -30,7 +30,7 @@ View * PopUpController::MessageViewWithSkip::subviewAtIndex(int index) { return nullptr; } -void PopUpController::MessageViewWithSkip::layoutSubviews() { +void PopUpController::MessageViewWithSkip::layoutSubviews(bool force) { // Layout the main message MessageView::layoutSubviews(); // Layout the "skip (OK)" @@ -38,8 +38,8 @@ void PopUpController::MessageViewWithSkip::layoutSubviews() { KDCoordinate width = bounds().width(); KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height(); KDSize okSize = m_okView.minimalSizeForOptimalDisplay(); - m_skipView.setFrame(KDRect(0, height-k_bottomMargin-textHeight, width-okSize.width()-k_okMargin-k_skipMargin, textHeight)); - m_okView.setFrame(KDRect(width - okSize.width()-k_okMargin, height-okSize.height()-k_okMargin, okSize)); + m_skipView.setFrame(KDRect(0, height-k_bottomMargin-textHeight, width-okSize.width()-k_okMargin-k_skipMargin, textHeight), force); + m_okView.setFrame(KDRect(width - okSize.width()-k_okMargin, height-okSize.height()-k_okMargin, okSize), force); } PopUpController::PopUpController(I18n::Message * messages, KDColor * colors, uint8_t numberOfMessages) : diff --git a/apps/on_boarding/pop_up_controller.h b/apps/on_boarding/pop_up_controller.h index 9f1b3d78b..f092e8853 100644 --- a/apps/on_boarding/pop_up_controller.h +++ b/apps/on_boarding/pop_up_controller.h @@ -20,7 +20,7 @@ private: protected: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; private: constexpr static KDCoordinate k_bottomMargin = 13; constexpr static KDCoordinate k_okMargin = 10; diff --git a/apps/probability/Makefile b/apps/probability/Makefile index 653f1a8b2..cc8e26580 100644 --- a/apps/probability/Makefile +++ b/apps/probability/Makefile @@ -4,8 +4,10 @@ app_headers += apps/probability/app.h app_probability_test_src = $(addprefix apps/probability/,\ distribution/binomial_distribution.cpp \ distribution/chi_squared_distribution.cpp \ + distribution/fisher_distribution.cpp \ distribution/geometric_distribution.cpp \ distribution/helper.cpp \ + distribution/hypergeometric_function.cpp\ distribution/distribution.cpp \ distribution/regularized_gamma.cpp \ distribution/student_distribution.cpp \ @@ -48,9 +50,6 @@ i18n_files += $(addprefix apps/probability/,\ base.universal.i18n\ ) -tests_src += $(addprefix apps/probability/distribution/,\ - hypergeometric_function.cpp\ -) tests_src += $(addprefix apps/probability/test/,\ hypergeometric_function.cpp\ distributions.cpp\ @@ -65,9 +64,11 @@ $(eval $(call depends_on_image,apps/probability/distribution_controller.cpp,$(ad binomial_icon.png \ chi_squared_icon.png \ exponential_icon.png \ + fisher_icon.png \ focused_binomial_icon.png \ focused_chi_squared_icon.png \ focused_exponential_icon.png \ + focused_fisher_icon.png \ focused_geometric_icon.png \ focused_normal_icon.png \ focused_poisson_icon.png \ diff --git a/apps/probability/base.de.i18n b/apps/probability/base.de.i18n index caad4c2e7..45d52dd65 100644 --- a/apps/probability/base.de.i18n +++ b/apps/probability/base.de.i18n @@ -5,15 +5,14 @@ Binomial = "Binomial" Geometric = "Geometrische" Uniforme = "Uniform" Normal = "Normal" -Poisson = "Poisson" ChiSquared = "Chi-Quadrat" -Student = "Student" UniformDistribution = "Uniformverteilung" ExponentialDistribution = "Exponentialverteilung" GeometricDistribution = "Geometrischeverteilung" PoissonDistribution = "Poisson-Verteilung" ChiSquaredDistribution = "Chi-Quadrat-Verteilung" StudentDistribution = "Student-Verteilung" +FisherDistribution = "F-Verteilung" ChooseParameters = "Parameter auswählen" RepetitionNumber = "n: Anzahl der Versuche" SuccessProbability = "p: Erfolgswahrscheinlichkeit" @@ -23,4 +22,6 @@ MeanDefinition = "μ: Erwartungswert" DeviationDefinition = "σ: Standardabweichung" LambdaPoissonDefinition = "λ: Parameter" DegreesOfFreedomDefinition = "k: Anzahl der Freiheitsgrade" +D1FisherDefinition = "d1: Freiheitsgrade des Zählers" +D2FisherDefinition = "d2: Freiheitsgrade des Nenners" ComputeProbability = "Wahrscheinlichkeit berechnen" diff --git a/apps/probability/base.en.i18n b/apps/probability/base.en.i18n index 275bfef05..c05cf05bb 100644 --- a/apps/probability/base.en.i18n +++ b/apps/probability/base.en.i18n @@ -5,15 +5,14 @@ Binomial = "Binomial" Geometric = "Geometric" Uniforme = "Uniform" Normal = "Normal" -Poisson = "Poisson" ChiSquared = "Chi-squared" -Student = "Student" UniformDistribution = "Uniform distribution" ExponentialDistribution = "Exponential distribution" GeometricDistribution = "Geometric distribution" PoissonDistribution = "Poisson distribution" ChiSquaredDistribution = "Chi-squared distribution" StudentDistribution = "Student's distribution" +FisherDistribution = "F distribution" ChooseParameters = "Choose parameters" RepetitionNumber = "n: Number of trials" SuccessProbability = "p: Success probability" @@ -23,4 +22,6 @@ MeanDefinition = "μ: Mean" DeviationDefinition = "σ: Standard deviation" LambdaPoissonDefinition = "λ: Parameter" DegreesOfFreedomDefinition = "k: Degrees of freedom" +D1FisherDefinition = "d1: Degrees of freedom of the numerator" +D2FisherDefinition = "d2: Degrees of freedom of the denominator" ComputeProbability = "Calculate probabilities" diff --git a/apps/probability/base.es.i18n b/apps/probability/base.es.i18n index 608daf6f5..65c70e20e 100644 --- a/apps/probability/base.es.i18n +++ b/apps/probability/base.es.i18n @@ -5,15 +5,14 @@ Binomial = "Binomial" Geometric = "Geométrica" Uniforme = "Uniforme" Normal = "Normal" -Poisson = "Poisson" ChiSquared = "Chi-cuadrado" -Student = "Student" UniformDistribution = "Distribución uniforme" ExponentialDistribution = "Distribución exponencial" GeometricDistribution = "Distribución geométrica" PoissonDistribution = "Distribución de Poisson" ChiSquaredDistribution = "Distribución chi-cuadrado" StudentDistribution = "Distribución de Student" +FisherDistribution = "Distribución F" ChooseParameters = "Seleccionar parámetros" RepetitionNumber = "n : Número de ensayos " SuccessProbability = "p : Probabilidad de éxito " @@ -23,4 +22,6 @@ MeanDefinition = "μ : Media" DeviationDefinition = "σ : Desviación típica" LambdaPoissonDefinition = "λ : Parámetro" DegreesOfFreedomDefinition = "k : Grados de libertad" +D1FisherDefinition = "d1 : Grados de libertad del numerador" +D2FisherDefinition = "d2 : Grados de libertad del denominador" ComputeProbability = "Calcular las probabilidades" diff --git a/apps/probability/base.fr.i18n b/apps/probability/base.fr.i18n index 0e14134eb..056fa00f6 100644 --- a/apps/probability/base.fr.i18n +++ b/apps/probability/base.fr.i18n @@ -5,15 +5,14 @@ Binomial = "Binomiale" Geometric = "Géométrique" Uniforme = "Uniforme" Normal = "Normale" -Poisson = "Poisson" ChiSquared = "Chi2" -Student = "Student" UniformDistribution = "Loi uniforme" ExponentialDistribution = "Loi exponentielle" GeometricDistribution = "Loi géométrique" PoissonDistribution = "Loi de Poisson" ChiSquaredDistribution = "Loi du chi2" StudentDistribution = "Loi de Student" +FisherDistribution = "Loi de Fisher" ChooseParameters = "Choisir les paramètres" RepetitionNumber = "n : Nombre de répétitions" SuccessProbability = "p : Probabilité de succès" @@ -23,4 +22,6 @@ MeanDefinition = "μ : Espérance ou moyenne" DeviationDefinition = "σ : Écart type" LambdaPoissonDefinition = "λ : Paramètre" DegreesOfFreedomDefinition = "k : Degrés de liberté" +D1FisherDefinition = "d1 : Degrés de liberté du numérateur" +D2FisherDefinition = "d2 : Degrés de liberté du dénominateur" ComputeProbability = "Calculer les probabilités" diff --git a/apps/probability/base.pt.i18n b/apps/probability/base.pt.i18n index 35ced259c..6e9071d74 100644 --- a/apps/probability/base.pt.i18n +++ b/apps/probability/base.pt.i18n @@ -5,15 +5,14 @@ Binomial = "Binomial" Geometric = "Geométrica" Uniforme = "Uniforme" Normal = "Normal" -Poisson = "Poisson" ChiSquared = "Qui-quadrado" -Student = "Student" UniformDistribution = "Distribuição uniforme" ExponentialDistribution = "Distribuição exponencial" GeometricDistribution = "Distribuição geométrica" PoissonDistribution = "Distribuição de Poisson" ChiSquaredDistribution = "Distribuição qui-quadrado" StudentDistribution = "Distribuição de Student" +FisherDistribution = "Distribuição F" ChooseParameters = "Selecionar os parâmetros" RepetitionNumber = "n : Número de ensaios" SuccessProbability = "p : Probabilidade de sucesso" @@ -23,4 +22,6 @@ MeanDefinition = "μ : Média" DeviationDefinition = "σ : Desvio padrão" LambdaPoissonDefinition = "λ : Parâmetro" DegreesOfFreedomDefinition = "k : Graus de liberdade" +D1FisherDefinition = "d1 : Graus de liberdade do numerador" +D2FisherDefinition = "d2 : Graus de liberdade do denominador" ComputeProbability = "Calcular probabilidades" diff --git a/apps/probability/base.universal.i18n b/apps/probability/base.universal.i18n index ac423313b..6121e51d1 100644 --- a/apps/probability/base.universal.i18n +++ b/apps/probability/base.universal.i18n @@ -1,7 +1,12 @@ +D1 = "d1" +D2 = "d2" DiscreteLegend = "P(X=" FiniteIntegralFirstLegend = "P(" FiniteIntegralLegend = "≤X≤" +Fisher = "Fisher" LeftIntegralFirstLegend = "P(X≤" LeftIntegralSecondLegend = ")=" +Poisson = "Poisson" RightIntegralFirstLegend = "P(X≥" RightIntegralSecondLegend = ")=" +Student = "Student" diff --git a/apps/probability/calculation_cell.cpp b/apps/probability/calculation_cell.cpp index 5154bb7db..1a3963bd9 100644 --- a/apps/probability/calculation_cell.cpp +++ b/apps/probability/calculation_cell.cpp @@ -67,10 +67,10 @@ View * CalculationCell::subviewAtIndex(int index) { return &m_calculation; } -void CalculationCell::layoutSubviews() { +void CalculationCell::layoutSubviews(bool force) { KDSize textSize = m_text.minimalSizeForOptimalDisplay(); - m_text.setFrame(KDRect(k_margin, 0, textSize.width(), bounds().height())); - m_calculation.setFrame(KDRect(2*k_margin+textSize.width()+ResponderImageCell::k_outline, ResponderImageCell::k_outline, calculationCellWidth(), ImageCell::k_height)); + m_text.setFrame(KDRect(k_margin, 0, textSize.width(), bounds().height()), force); + m_calculation.setFrame(KDRect(2*k_margin+textSize.width()+ResponderImageCell::k_outline, ResponderImageCell::k_outline, calculationCellWidth(), ImageCell::k_height), force); } KDCoordinate CalculationCell::calculationCellWidth() const { diff --git a/apps/probability/calculation_cell.h b/apps/probability/calculation_cell.h index f9432d895..7b78f46ea 100644 --- a/apps/probability/calculation_cell.h +++ b/apps/probability/calculation_cell.h @@ -22,7 +22,7 @@ private: constexpr static KDCoordinate k_margin = 5; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; KDCoordinate calculationCellWidth() const; MessageTextView m_text; EditableTextCell m_calculation; diff --git a/apps/probability/calculation_controller.cpp b/apps/probability/calculation_controller.cpp index 965099bf4..704ed387f 100644 --- a/apps/probability/calculation_controller.cpp +++ b/apps/probability/calculation_controller.cpp @@ -46,12 +46,12 @@ View * CalculationController::ContentView::subviewAtIndex(int index) { return &m_distributionCurveView; } -void CalculationController::ContentView::layoutSubviews() { +void CalculationController::ContentView::layoutSubviews(bool force) { KDCoordinate titleHeight = KDFont::SmallFont->glyphSize().height()+k_titleHeightMargin; - m_titleView.setFrame(KDRect(0, 0, bounds().width(), titleHeight)); + m_titleView.setFrame(KDRect(0, 0, bounds().width(), titleHeight), force); KDCoordinate calculationHeight = ResponderImageCell::k_oneCellHeight+2*k_tableMargin; - m_selectableTableView->setFrame(KDRect(0, titleHeight, bounds().width(), calculationHeight)); - m_distributionCurveView.setFrame(KDRect(0, titleHeight+calculationHeight, bounds().width(), bounds().height() - calculationHeight - titleHeight)); + m_selectableTableView->setFrame(KDRect(0, titleHeight, bounds().width(), calculationHeight), force); + m_distributionCurveView.setFrame(KDRect(0, titleHeight+calculationHeight, bounds().width(), bounds().height() - calculationHeight - titleHeight), force); } CalculationController::CalculationController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, Distribution * distribution, Calculation * calculation) : diff --git a/apps/probability/calculation_controller.h b/apps/probability/calculation_controller.h index 4c49dd9a0..77484a364 100644 --- a/apps/probability/calculation_controller.h +++ b/apps/probability/calculation_controller.h @@ -58,7 +58,7 @@ private: constexpr static KDCoordinate k_titleHeightMargin = 5; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; MessageTextView m_titleView; SelectableTableView * m_selectableTableView; DistributionCurveView m_distributionCurveView; diff --git a/apps/probability/cell.cpp b/apps/probability/cell.cpp index a0e6a565c..1c36ae1a2 100644 --- a/apps/probability/cell.cpp +++ b/apps/probability/cell.cpp @@ -26,12 +26,12 @@ View * Cell::subviewAtIndex(int index) { return &m_chevronView; } -void Cell::layoutSubviews() { +void Cell::layoutSubviews(bool force) { KDCoordinate width = bounds().width(); KDCoordinate height = bounds().height(); - m_labelView.setFrame(KDRect(1+k_iconWidth+2*k_iconMargin, 1, width-2-k_iconWidth-2*k_iconMargin - k_chevronWidth, height-2)); - m_iconView.setFrame(KDRect(1+k_iconMargin, (height - k_iconHeight)/2, k_iconWidth, k_iconHeight)); - m_chevronView.setFrame(KDRect(width-1-k_chevronWidth-k_chevronMargin, 1, k_chevronWidth, height - 2)); + m_labelView.setFrame(KDRect(1+k_iconWidth+2*k_iconMargin, 1, width-2-k_iconWidth-2*k_iconMargin - k_chevronWidth, height-2), force); + m_iconView.setFrame(KDRect(1+k_iconMargin, (height - k_iconHeight)/2, k_iconWidth, k_iconHeight), force); + m_chevronView.setFrame(KDRect(width-1-k_chevronWidth-k_chevronMargin, 1, k_chevronWidth, height - 2), force); } void Cell::reloadCell() { diff --git a/apps/probability/cell.h b/apps/probability/cell.h index ef692b573..ef980f38d 100644 --- a/apps/probability/cell.h +++ b/apps/probability/cell.h @@ -20,7 +20,7 @@ private: constexpr static KDCoordinate k_chevronMargin = 10; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; MessageTextView m_labelView; ImageView m_iconView; /* TODO: One day, we would rather store a mask (8bits/pixel) instead of two diff --git a/apps/probability/distribution/distribution.h b/apps/probability/distribution/distribution.h index ab5927d22..95575ed43 100644 --- a/apps/probability/distribution/distribution.h +++ b/apps/probability/distribution/distribution.h @@ -18,7 +18,8 @@ public: ChiSquared, Student, Geometric, - Poisson + Poisson, + Fisher }; virtual ~Distribution() = default; virtual I18n::Message title() = 0; diff --git a/apps/probability/distribution/fisher_distribution.cpp b/apps/probability/distribution/fisher_distribution.cpp new file mode 100644 index 000000000..97e145599 --- /dev/null +++ b/apps/probability/distribution/fisher_distribution.cpp @@ -0,0 +1,89 @@ +#include "fisher_distribution.h" +#include +#include +#include +#include + +namespace Probability { + +static inline double maxDouble(double x, double y) { return x > y ? x : y; } + +float FisherDistribution::xMin() const { + return -k_displayLeftMarginRatio * xMax(); +} + +float FisherDistribution::xMax() const { + return 5.0f; // The mode is always < 1 +} + +float FisherDistribution::yMax() const { + const float m = mode(); + float max = std::isnan(m) ? k_defaultMax : evaluateAtAbscissa(m); + max = std::isnan(max) ? k_defaultMax : max; + return max * (1.0f + k_displayTopMarginRatio); +} + +I18n::Message FisherDistribution::parameterNameAtIndex(int index) { + if (index == 0) { + return I18n::Message::D1; + } + assert(index == 1); + return I18n::Message::D2; +} + +I18n::Message FisherDistribution::parameterDefinitionAtIndex(int index) { + if (index == 0) { + return I18n::Message::D1FisherDefinition; + } + assert(index == 1); + return I18n::Message::D2FisherDefinition; +} + +float FisherDistribution::evaluateAtAbscissa(float x) const { + if (x < 0.0f) { + return NAN; + } + const float d1 = m_parameter1; + const float d2 = m_parameter2; + const float f = d1*x/(d1*x+d2); + const float numerator = std::pow(f, d1/2.0f) * std::pow(1.0f - f, d2/2.0f); + const float denominator = x * Poincare::BetaFunction(d1/2.0f, d2/2.0f); + return numerator / denominator; +} + +bool FisherDistribution::authorizedValueAtIndex(float x, int index) const { + assert(index == 0 || index == 1); + return x > FLT_MIN && x <= k_maxParameter; +} + +void FisherDistribution::setParameterAtIndex(float f, int index) { + TwoParameterDistribution::setParameterAtIndex(f, index); +} + +double FisherDistribution::cumulativeDistributiveFunctionAtAbscissa(double x) const { + const double d1 = m_parameter1; + const double d2 = m_parameter2; + return Poincare::RegularizedIncompleteBetaFunction(d1/2.0, d2/2.0, d1*x/(d1*x+d2)); +} + +double FisherDistribution::cumulativeDistributiveInverseForProbability(double * probability) { + /* We have to compute the values of the interval in which to look for x. + * We cannot put xMin because xMin is < 0 for display purposes, and negative + * values are not accepted. + * The maximum of the interval: we want */ + if (*probability < DBL_EPSILON) { + return 0.0; + } + return cumulativeDistributiveInverseForProbabilityUsingIncreasingFunctionRoot(probability, DBL_EPSILON, maxDouble(xMax(), 100.0)); // Ad-hoc value; +} + +float FisherDistribution::mode() const { + const float d1 = m_parameter1; + if (d1 > 2.0f) { + const float d2 = m_parameter2; + return (d1 - 2.0f)/d1 * d2/(d2 + 2.0f); + } + return NAN; +} + +} diff --git a/apps/probability/distribution/fisher_distribution.h b/apps/probability/distribution/fisher_distribution.h new file mode 100644 index 000000000..e61471f4b --- /dev/null +++ b/apps/probability/distribution/fisher_distribution.h @@ -0,0 +1,32 @@ +#ifndef PROBABILITE_FISHER_DISTRIBUTION_H +#define PROBABILITE_FISHER_DISTRIBUTION_H + +#include "two_parameter_distribution.h" + +namespace Probability { + +class FisherDistribution final : public TwoParameterDistribution { +public: + FisherDistribution() : TwoParameterDistribution(1.0f, 1.0f) {} + I18n::Message title() override { return I18n::Message::FisherDistribution; } + Type type() const override { return Type::Fisher; } + bool isContinuous() const override { return true; } + float xMin() const override; + float xMax() const override; + float yMax() const override; + I18n::Message parameterNameAtIndex(int index) override; + I18n::Message parameterDefinitionAtIndex(int index) override; + float evaluateAtAbscissa(float x) const override; + bool authorizedValueAtIndex(float x, int index) const override; + void setParameterAtIndex(float f, int index) override; + double cumulativeDistributiveFunctionAtAbscissa(double x) const override; + double cumulativeDistributiveInverseForProbability(double * probability) override; +private: + constexpr static float k_maxParameter = 144.0f; // The display works badly for d1 = d2 > 144. + constexpr static float k_defaultMax = 3.0f; + float mode() const; +}; + +} + +#endif diff --git a/apps/probability/distribution_controller.cpp b/apps/probability/distribution_controller.cpp index 87b3ec6e5..c09488707 100644 --- a/apps/probability/distribution_controller.cpp +++ b/apps/probability/distribution_controller.cpp @@ -5,6 +5,7 @@ #include "distribution/binomial_distribution.h" #include "distribution/chi_squared_distribution.h" #include "distribution/exponential_distribution.h" +#include "distribution/fisher_distribution.h" #include "distribution/geometric_distribution.h" #include "distribution/normal_distribution.h" #include "distribution/poisson_distribution.h" @@ -13,6 +14,7 @@ #include "images/binomial_icon.h" #include "images/chi_squared_icon.h" #include "images/exponential_icon.h" +#include "images/fisher_icon.h" #include "images/geometric_icon.h" #include "images/normal_icon.h" #include "images/poisson_icon.h" @@ -21,6 +23,7 @@ #include "images/focused_binomial_icon.h" #include "images/focused_chi_squared_icon.h" #include "images/focused_exponential_icon.h" +#include "images/focused_fisher_icon.h" #include "images/focused_geometric_icon.h" #include "images/focused_normal_icon.h" #include "images/focused_poisson_icon.h" @@ -29,28 +32,19 @@ namespace Probability { -DistributionController::ContentView::ContentView(SelectableTableView * selectableTableView) : - m_titleView(KDFont::SmallFont, I18n::Message::ChooseDistribution, 0.5f, 0.5f, Palette::SecondaryText, Palette::BackgroundApps), - m_selectableTableView(selectableTableView) -{ -} - -int DistributionController::ContentView::numberOfSubviews() const { - return 2; -} - View * DistributionController::ContentView::subviewAtIndex(int index) { - assert(index >= 0 && index < 2); + assert(index >= 0 && index < numberOfSubviews()); if (index == 0) { return &m_titleView; } return m_selectableTableView; } -void DistributionController::ContentView::layoutSubviews() { +void DistributionController::ContentView::layoutSubviews(bool force) { + assert(KDFont::SmallFont->glyphSize().height() == 14); // otherwise, k_numberOfCells badly computed KDCoordinate titleHeight = KDFont::SmallFont->glyphSize().height()+k_titleMargin; - m_titleView.setFrame(KDRect(0, 0, bounds().width(), titleHeight)); - m_selectableTableView->setFrame(KDRect(0, titleHeight, bounds().width(), bounds().height()-titleHeight)); + m_titleView.setFrame(KDRect(0, 0, bounds().width(), titleHeight), force); + m_selectableTableView->setFrame(KDRect(0, titleHeight, bounds().width(), bounds().height()-titleHeight), force); } static I18n::Message sMessages[] = { @@ -61,7 +55,8 @@ static I18n::Message sMessages[] = { I18n::Message::ChiSquared, I18n::Message::Student, I18n::Message::Geometric, - I18n::Message::Poisson + I18n::Message::Poisson, + I18n::Message::Fisher }; DistributionController::DistributionController(Responder * parentResponder, Distribution * distribution, ParametersController * parametersController) : @@ -76,10 +71,6 @@ DistributionController::DistributionController(Responder * parentResponder, Dist m_selectableTableView.setTopMargin(Metric::CommonTopMargin-ContentView::k_titleMargin); } -View * DistributionController::view() { - return &m_contentView; -} - void Probability::DistributionController::viewWillAppear() { selectRow((int)m_distribution->type()); } @@ -104,20 +95,12 @@ bool Probability::DistributionController::handleEvent(Ion::Events::Event event) return false; } -int Probability::DistributionController::numberOfRows() const { - return k_totalNumberOfModels; -}; - HighlightCell * Probability::DistributionController::reusableCell(int index) { assert(index >= 0); - assert(index < k_totalNumberOfModels); + assert(index < k_numberOfCells); return &m_cells[index]; } -int Probability::DistributionController::reusableCellCount() const { - return k_totalNumberOfModels; -} - void Probability::DistributionController::willDisplayCellForIndex(HighlightCell * cell, int index) { Cell * myCell = (Cell *)cell; myCell->setLabel(m_messages[index]); @@ -129,7 +112,8 @@ void Probability::DistributionController::willDisplayCellForIndex(HighlightCell ImageStore::ChiSquaredIcon, ImageStore::StudentIcon, ImageStore::GeometricIcon, - ImageStore::PoissonIcon + ImageStore::PoissonIcon, + ImageStore::FisherIcon }; const Image * focusedImages[k_totalNumberOfModels] = { ImageStore::FocusedBinomialIcon, @@ -139,16 +123,13 @@ void Probability::DistributionController::willDisplayCellForIndex(HighlightCell ImageStore::FocusedChiSquaredIcon, ImageStore::FocusedStudentIcon, ImageStore::FocusedGeometricIcon, - ImageStore::FocusedPoissonIcon + ImageStore::FocusedPoissonIcon, + ImageStore::FocusedFisherIcon }; myCell->setImage(images[index], focusedImages[index]); myCell->reloadCell(); } -KDCoordinate Probability::DistributionController::cellHeight() { - return Metric::ParameterCellHeight; -} - void Probability::DistributionController::setDistributionAccordingToIndex(int index) { if ((int)m_distribution->type() == index) { return; @@ -179,7 +160,9 @@ void Probability::DistributionController::setDistributionAccordingToIndex(int in case 7: new(m_distribution) PoissonDistribution(); break; - + case 8: + new(m_distribution) FisherDistribution(); + break; default: return; } diff --git a/apps/probability/distribution_controller.h b/apps/probability/distribution_controller.h index d81fca8c7..349a3519c 100644 --- a/apps/probability/distribution_controller.h +++ b/apps/probability/distribution_controller.h @@ -12,30 +12,35 @@ namespace Probability { class DistributionController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { public: DistributionController(Responder * parentResponder, Distribution * m_distribution, ParametersController * parametersController); - View * view() override; + View * view() override { return &m_contentView; } bool handleEvent(Ion::Events::Event event) override; void didBecomeFirstResponder() override; void viewWillAppear() override; - int numberOfRows() const override; + int numberOfRows() const override { return k_totalNumberOfModels; } void willDisplayCellForIndex(HighlightCell * cell, int index) override; - KDCoordinate cellHeight() override; + KDCoordinate cellHeight() override { return k_cellHeight; } HighlightCell * reusableCell(int index) override; - int reusableCellCount() const override; + int reusableCellCount() const override { return k_numberOfCells; } private: class ContentView : public View { public: - ContentView(SelectableTableView * selectableTableView); + ContentView(SelectableTableView * selectableTableView) : + m_titleView(KDFont::SmallFont, I18n::Message::ChooseDistribution, 0.5f, 0.5f, Palette::GreyDark, Palette::WallScreen), + m_selectableTableView(selectableTableView) + {} constexpr static KDCoordinate k_titleMargin = 8; private: - int numberOfSubviews() const override; + int numberOfSubviews() const override { return 2; } View * subviewAtIndex(int index) override; - void layoutSubviews() override; - MessageTextView m_titleView;; + void layoutSubviews(bool force = false) override; + MessageTextView m_titleView; SelectableTableView * m_selectableTableView; }; void setDistributionAccordingToIndex(int index); - constexpr static int k_totalNumberOfModels = 8; - Cell m_cells[k_totalNumberOfModels]; + constexpr static KDCoordinate k_cellHeight = Metric::ParameterCellHeight; + constexpr static int k_totalNumberOfModels = 9; + constexpr static int k_numberOfCells = (Ion::Display::Height - Metric::TitleBarHeight - 14 - ContentView::k_titleMargin) /k_cellHeight + 1 + 1; // 14 for the small font height, + 1 to get the upper rounding and + 1 for half-displayed rows + Cell m_cells[k_numberOfCells]; SelectableTableView m_selectableTableView; ContentView m_contentView; I18n::Message * m_messages; diff --git a/apps/probability/distribution_curve_view.cpp b/apps/probability/distribution_curve_view.cpp index f90046499..4cf8cf407 100644 --- a/apps/probability/distribution_curve_view.cpp +++ b/apps/probability/distribution_curve_view.cpp @@ -18,7 +18,7 @@ void DistributionCurveView::drawRect(KDContext * ctx, KDRect rect) const { float upperBound = m_calculation->upperBound(); ctx->fillRect(bounds(), k_backgroundColor); drawAxis(ctx, rect, Axis::Horizontal); - drawLabels(ctx, rect, Axis::Horizontal, false, false, false, 0, k_backgroundColor); + drawLabelsAndGraduations(ctx, rect, Axis::Horizontal, false, false, false, 0, k_backgroundColor); if (m_distribution->type() == Distribution::Type::Normal) { /* Special case for the normal distribution, which has always the same curve * We indicate the pixels from and to which we color under the curve, not @@ -29,7 +29,7 @@ void DistributionCurveView::drawRect(KDContext * ctx, KDRect rect) const { return; } if (m_distribution->isContinuous()) { - drawCartesianCurve(ctx, rect, -INFINITY, INFINITY, EvaluateXYAtAbscissa, m_distribution, nullptr, Palette::ProbabilityCurve, true, lowerBound, upperBound); + drawCartesianCurve(ctx, rect, -INFINITY, INFINITY, EvaluateXYAtAbscissa, m_distribution, nullptr, Palette::ProbabilityCurve, true, true, lowerBound, upperBound); } else { drawHistogram(ctx, rect, EvaluateAtAbscissa, m_distribution, nullptr, 0, 1, false, Palette::ProbabilityHistogramBar, Palette::ProbabilityCurve, lowerBound, upperBound+0.5f); } @@ -59,7 +59,7 @@ void DistributionCurveView::drawStandardNormal(KDContext * ctx, KDRect rect, flo // Draw a centered reduced normal curve NormalDistribution n; constCastedThis->setCurveViewRange(&n); - drawCartesianCurve(ctx, rect, -INFINITY, INFINITY, EvaluateXYAtAbscissa, &n, nullptr, Palette::ProbabilityCurve, true, pixelToFloat(Axis::Horizontal, colorLowerBoundPixel), pixelToFloat(Axis::Horizontal, colorUpperBoundPixel)); + drawCartesianCurve(ctx, rect, -INFINITY, INFINITY, EvaluateXYAtAbscissa, &n, nullptr, Palette::ProbabilityCurve, true, true, pixelToFloat(Axis::Horizontal, colorLowerBoundPixel), pixelToFloat(Axis::Horizontal, colorUpperBoundPixel)); // Put back the previous curve view range constCastedThis->setCurveViewRange(previousRange); diff --git a/apps/probability/image_cell.cpp b/apps/probability/image_cell.cpp index dae9db792..abe33fe05 100644 --- a/apps/probability/image_cell.cpp +++ b/apps/probability/image_cell.cpp @@ -35,8 +35,8 @@ View * ImageCell::subviewAtIndex(int index) { return &m_iconView; } -void ImageCell::layoutSubviews() { - m_iconView.setFrame(bounds()); +void ImageCell::layoutSubviews(bool force) { + m_iconView.setFrame(bounds(), force); } } diff --git a/apps/probability/image_cell.h b/apps/probability/image_cell.h index bb65869c4..24daee8e8 100644 --- a/apps/probability/image_cell.h +++ b/apps/probability/image_cell.h @@ -15,7 +15,7 @@ public: private: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; ImageView m_iconView; const Image * m_icon; const Image * m_focusedIcon; diff --git a/apps/probability/images/fisher_icon.png b/apps/probability/images/fisher_icon.png new file mode 100644 index 000000000..c24b718fd Binary files /dev/null and b/apps/probability/images/fisher_icon.png differ diff --git a/apps/probability/images/focused_fisher_icon.png b/apps/probability/images/focused_fisher_icon.png new file mode 100644 index 000000000..196f83c70 Binary files /dev/null and b/apps/probability/images/focused_fisher_icon.png differ diff --git a/apps/probability/parameters_controller.cpp b/apps/probability/parameters_controller.cpp index 80ad3d32f..f3372dbe0 100644 --- a/apps/probability/parameters_controller.cpp +++ b/apps/probability/parameters_controller.cpp @@ -6,7 +6,7 @@ namespace Probability { -ParametersController::ContentView::ContentView(Responder * parentResponder, SelectableTableView * selectableTableView) : +ParametersController::ContentView::ContentView(SelectableTableView * selectableTableView) : m_numberOfParameters(1), m_titleView(KDFont::SmallFont, I18n::Message::ChooseParameters, 0.5f, 0.5f, Palette::SecondaryText, Palette::BackgroundApps), m_firstParameterDefinition(KDFont::SmallFont, (I18n::Message)0, 0.5f, 0.5f, Palette::PrimaryText, Palette::BackgroundApps), @@ -50,26 +50,26 @@ View * ParametersController::ContentView::subviewAtIndex(int index) { return &m_secondParameterDefinition; } -void ParametersController::ContentView::layoutSubviews() { +void ParametersController::ContentView::layoutSubviews(bool force) { KDCoordinate titleHeight = KDFont::SmallFont->glyphSize().height()+k_titleMargin; - m_titleView.setFrame(KDRect(0, 0, bounds().width(), titleHeight)); + m_titleView.setFrame(KDRect(0, 0, bounds().width(), titleHeight), force); KDCoordinate tableHeight = m_selectableTableView->minimalSizeForOptimalDisplay().height(); - m_selectableTableView->setFrame(KDRect(0, titleHeight, bounds().width(), tableHeight)); + m_selectableTableView->setFrame(KDRect(0, titleHeight, bounds().width(), tableHeight), force); KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height(); KDCoordinate defOrigin = (titleHeight+tableHeight)/2+(bounds().height()-textHeight)/2; - m_secondParameterDefinition.setFrame(KDRectZero); + m_secondParameterDefinition.setFrame(KDRectZero, force); if (m_numberOfParameters == 2) { defOrigin = (titleHeight+tableHeight)/2+(bounds().height()-2*textHeight-k_textMargin)/2; - m_secondParameterDefinition.setFrame(KDRect(0, defOrigin+textHeight+k_textMargin, bounds().width(), textHeight)); + m_secondParameterDefinition.setFrame(KDRect(0, defOrigin+textHeight+k_textMargin, bounds().width(), textHeight), force); } - m_firstParameterDefinition.setFrame(KDRect(0, defOrigin, bounds().width(), textHeight)); + m_firstParameterDefinition.setFrame(KDRect(0, defOrigin, bounds().width(), textHeight), force); } /* Parameters Controller */ ParametersController::ParametersController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, Distribution * distribution, CalculationController * calculationController) : FloatParameterController(parentResponder), - m_contentView(this, &m_selectableTableView), + m_contentView(&m_selectableTableView), m_menuListCell{}, m_distribution(distribution), m_calculationController(calculationController) diff --git a/apps/probability/parameters_controller.h b/apps/probability/parameters_controller.h index 390b3099d..0e723982b 100644 --- a/apps/probability/parameters_controller.h +++ b/apps/probability/parameters_controller.h @@ -28,10 +28,10 @@ private: bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; class ContentView : public View { public: - ContentView(Responder * parentResponder, SelectableTableView * selectableTableView); + ContentView(SelectableTableView * selectableTableView); void drawRect(KDContext * ctx, KDRect rect) const override; MessageTextView * parameterDefinitionAtIndex(int index); - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; void setNumberOfParameters(int numberOfParameters); private: constexpr static KDCoordinate k_textMargin = 5; diff --git a/apps/probability/responder_image_cell.cpp b/apps/probability/responder_image_cell.cpp index 94410552d..f9079077e 100644 --- a/apps/probability/responder_image_cell.cpp +++ b/apps/probability/responder_image_cell.cpp @@ -46,8 +46,8 @@ View * ResponderImageCell::subviewAtIndex(int index) { return &m_imageCell; } -void ResponderImageCell::layoutSubviews() { - m_imageCell.setFrame(KDRect(k_outline, k_outline, bounds().width()-2*k_outline, bounds().height()-2*k_outline)); +void ResponderImageCell::layoutSubviews(bool force) { + m_imageCell.setFrame(KDRect(k_outline, k_outline, bounds().width()-2*k_outline, bounds().height()-2*k_outline), force); } } diff --git a/apps/probability/responder_image_cell.h b/apps/probability/responder_image_cell.h index acb8ad584..88341f0c7 100644 --- a/apps/probability/responder_image_cell.h +++ b/apps/probability/responder_image_cell.h @@ -25,7 +25,7 @@ public: private: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; ImageCell m_imageCell; CalculationTypeController m_calculationTypeController; }; diff --git a/apps/probability/test/distributions.cpp b/apps/probability/test/distributions.cpp index 13ef18a9d..4cb4b5c84 100644 --- a/apps/probability/test/distributions.cpp +++ b/apps/probability/test/distributions.cpp @@ -7,6 +7,7 @@ #include "../distribution/chi_squared_distribution.h" #include "../distribution/geometric_distribution.h" #include "../distribution/student_distribution.h" +#include "../distribution/fisher_distribution.h" void assert_cumulative_distributive_function_direct_and_inverse_is(Probability::Distribution * distribution, double x, double result) { double r = distribution->cumulativeDistributiveFunctionAtAbscissa(x); @@ -92,3 +93,26 @@ QUIZ_CASE(geometric_distribution) { 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); } + +QUIZ_CASE(fisher_distribution) { + // Fisher distribution with d1 = 1 and d2 = 1 + Probability::FisherDistribution distribution; + distribution.setParameterAtIndex(1.0, 0); + distribution.setParameterAtIndex(1.0, 1); + assert_cumulative_distributive_function_direct_and_inverse_is(&distribution, 1.7, 0.5834784097728860); + assert_cumulative_distributive_function_direct_and_inverse_is(&distribution, 3.3, 0.6796445888753); + + // Fisher distribution with d1 = 3 and d2 = 2 + distribution.setParameterAtIndex(3.0, 0); + distribution.setParameterAtIndex(2.0, 1); + assert_cumulative_distributive_function_direct_and_inverse_is(&distribution, 2.0, 0.6495190528383); + assert_cumulative_distributive_function_direct_and_inverse_is(&distribution, 3.0, 0.7400733003272); + + // Fisher distribution with d1 = 100 and d2 = 87 + distribution.setParameterAtIndex(100.0, 0); + distribution.setParameterAtIndex(87.0, 1); + assert_cumulative_distributive_function_direct_and_inverse_is(&distribution, 0.6, 0.00688477308162587); + assert_cumulative_distributive_function_direct_and_inverse_is(&distribution, 1.4, 0.94560850441205857); + assert_cumulative_distributive_function_direct_and_inverse_is(&distribution, 1.425, 0.95425004959692871775); + +} diff --git a/apps/regression/app.cpp b/apps/regression/app.cpp index 6f27c7e8e..4760b7841 100644 --- a/apps/regression/app.cpp +++ b/apps/regression/app.cpp @@ -1,5 +1,6 @@ #include "app.h" #include "regression_icon.h" +#include "../apps_container.h" #include using namespace Shared; @@ -33,7 +34,7 @@ App::Snapshot::Snapshot() : } App * App::Snapshot::unpack(Container * container) { - return new (container->currentAppBuffer()) App(this); + return new (container->currentAppBuffer()) App(this, static_cast(container)->globalContext()); } void App::Snapshot::reset() { @@ -53,7 +54,7 @@ void App::Snapshot::tidy() { m_store.tidy(); } -App::App(Snapshot * snapshot) : +App::App(Snapshot * snapshot, Poincare::Context * parentContext) : TextFieldDelegateApp(snapshot, &m_tabViewController), m_calculationController(&m_calculationAlternateEmptyViewController, &m_calculationHeader, snapshot->store()), m_calculationAlternateEmptyViewController(&m_calculationHeader, &m_calculationController, &m_calculationController), @@ -62,7 +63,7 @@ App::App(Snapshot * snapshot) : m_graphAlternateEmptyViewController(&m_graphHeader, &m_graphController, &m_graphController), m_graphHeader(&m_graphStackViewController, &m_graphAlternateEmptyViewController, &m_graphController), m_graphStackViewController(&m_tabViewController, &m_graphHeader), - m_storeController(&m_storeHeader, this, snapshot->store(), &m_storeHeader), + m_storeController(&m_storeHeader, this, snapshot->store(), &m_storeHeader, parentContext), m_storeHeader(&m_storeStackViewController, &m_storeController, &m_storeController), m_storeStackViewController(&m_tabViewController, &m_storeHeader), m_tabViewController(&m_modalViewController, snapshot, &m_storeStackViewController, &m_graphStackViewController, &m_calculationHeader), diff --git a/apps/regression/app.h b/apps/regression/app.h index bf44d16cc..9a1921efc 100644 --- a/apps/regression/app.h +++ b/apps/regression/app.h @@ -46,7 +46,7 @@ public: } RegressionController * regressionController() { return &m_regressionController; } private: - App(Snapshot * snapshot); + App(Snapshot * snapshot, Poincare::Context * parentContext); CalculationController m_calculationController; AlternateEmptyViewController m_calculationAlternateEmptyViewController; ButtonRowController m_calculationHeader; diff --git a/apps/regression/calculation_controller.cpp b/apps/regression/calculation_controller.cpp index ff02fa19c..fce340550 100644 --- a/apps/regression/calculation_controller.cpp +++ b/apps/regression/calculation_controller.cpp @@ -176,8 +176,8 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int const int numberSignificantDigits = Preferences::LargeNumberOfSignificantDigits; if (i > 0 && j > 0 && j <= k_totalNumberOfDoubleBufferRows) { ArgCalculPointer calculationMethods[k_totalNumberOfDoubleBufferRows] = {&Store::meanOfColumn, &Store::sumOfColumn, &Store::squaredValueSumOfColumn, &Store::standardDeviationOfColumn, &Store::varianceOfColumn}; - double calculation1 = (m_store->*calculationMethods[j-1])(seriesNumber, 0); - double calculation2 = (m_store->*calculationMethods[j-1])(seriesNumber, 1); + double calculation1 = (m_store->*calculationMethods[j-1])(seriesNumber, 0, false); + double calculation2 = (m_store->*calculationMethods[j-1])(seriesNumber, 1, false); EvenOddDoubleBufferTextCellWithSeparator * myCell = (EvenOddDoubleBufferTextCellWithSeparator *)cell; constexpr int bufferSize = PrintFloat::charSizeForFloatsWithPrecision(numberSignificantDigits); char buffer[bufferSize]; @@ -196,8 +196,16 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int } if (i > 0 && j > k_totalNumberOfDoubleBufferRows && j < k_regressionCellIndex) { assert(j != k_regressionCellIndex); - CalculPointer calculationMethods[] = {&Store::doubleCastedNumberOfPairsOfSeries, &Store::covariance, &Store::columnProductSum}; - double calculation = (m_store->*calculationMethods[j-k_totalNumberOfDoubleBufferRows-1])(seriesNumber); + double calculation = 0; + const int calculationIndex = j-k_totalNumberOfDoubleBufferRows-1; + if (calculationIndex == 0) { + calculation = m_store->doubleCastedNumberOfPairsOfSeries(seriesNumber); + } else if (calculationIndex == 1) { + calculation = m_store->covariance(seriesNumber); + } else { + assert(calculationIndex == 2); + calculation = m_store->columnProductSum(seriesNumber); + } constexpr int bufferSize = PrintFloat::charSizeForFloatsWithPrecision(numberSignificantDigits); char buffer[bufferSize]; PoincareHelpers::ConvertFloatToText(calculation, buffer, bufferSize, numberSignificantDigits); @@ -228,8 +236,8 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int if (j > k_regressionCellIndex + maxNumberCoefficients) { // Fill r and r2 if needed if (modelType == Model::Type::Linear) { - CalculPointer calculationMethods[2] = {&Store::correlationCoefficient, &Store::squaredCorrelationCoefficient}; - double calculation = (m_store->*calculationMethods[j - k_regressionCellIndex - maxNumberCoefficients - 1])(seriesNumber); + const int calculationIndex = j - k_regressionCellIndex - maxNumberCoefficients - 1; + double calculation = calculationIndex == 0 ? m_store->correlationCoefficient(seriesNumber) : m_store->squaredCorrelationCoefficient(seriesNumber); constexpr int bufferSize = PrintFloat::charSizeForFloatsWithPrecision(numberSignificantDigits); char buffer[bufferSize]; PoincareHelpers::ConvertFloatToText(calculation, buffer, bufferSize, numberSignificantDigits); diff --git a/apps/regression/column_title_cell.cpp b/apps/regression/column_title_cell.cpp index 3a97fb4da..a858715cf 100644 --- a/apps/regression/column_title_cell.cpp +++ b/apps/regression/column_title_cell.cpp @@ -14,11 +14,11 @@ void ColumnTitleCell::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(KDRect(Metric::TableSeparatorThickness, 0, bounds().width(), k_colorIndicatorThickness), m_functionColor); } -void ColumnTitleCell::layoutSubviews() { +void ColumnTitleCell::layoutSubviews(bool force) { KDCoordinate width = bounds().width() - Metric::TableSeparatorThickness; KDCoordinate height = bounds().height(); - m_firstBufferTextView.setFrame(KDRect(Metric::TableSeparatorThickness, k_colorIndicatorThickness, width/2, height - k_colorIndicatorThickness)); - m_secondBufferTextView.setFrame(KDRect(Metric::TableSeparatorThickness + width/2, k_colorIndicatorThickness, width - width/2, height - k_colorIndicatorThickness)); + m_firstBufferTextView.setFrame(KDRect(Metric::TableSeparatorThickness, k_colorIndicatorThickness, width/2, height - k_colorIndicatorThickness), force); + m_secondBufferTextView.setFrame(KDRect(Metric::TableSeparatorThickness + width/2, k_colorIndicatorThickness, width - width/2, height - k_colorIndicatorThickness), force); } } diff --git a/apps/regression/column_title_cell.h b/apps/regression/column_title_cell.h index fa85817a1..d0a14ca49 100644 --- a/apps/regression/column_title_cell.h +++ b/apps/regression/column_title_cell.h @@ -14,7 +14,7 @@ public: } virtual void setColor(KDColor color); void drawRect(KDContext * ctx, KDRect rect) const override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; private: constexpr static KDCoordinate k_colorIndicatorThickness = 2; KDColor m_functionColor; diff --git a/apps/regression/even_odd_double_buffer_text_cell_with_separator.cpp b/apps/regression/even_odd_double_buffer_text_cell_with_separator.cpp index 96fbe5b25..e87e506b7 100644 --- a/apps/regression/even_odd_double_buffer_text_cell_with_separator.cpp +++ b/apps/regression/even_odd_double_buffer_text_cell_with_separator.cpp @@ -88,11 +88,11 @@ View * EvenOddDoubleBufferTextCellWithSeparator::subviewAtIndex(int index) { return &m_secondBufferTextView; } -void EvenOddDoubleBufferTextCellWithSeparator::layoutSubviews() { +void EvenOddDoubleBufferTextCellWithSeparator::layoutSubviews(bool force) { KDCoordinate width = bounds().width() - Metric::TableSeparatorThickness; KDCoordinate height = bounds().height(); - m_firstBufferTextView.setFrame(KDRect(Metric::TableSeparatorThickness, 0, width/2, height)); - m_secondBufferTextView.setFrame(KDRect(Metric::TableSeparatorThickness + width/2, 0, width - width/2, height)); + m_firstBufferTextView.setFrame(KDRect(Metric::TableSeparatorThickness, 0, width/2, height), force); + m_secondBufferTextView.setFrame(KDRect(Metric::TableSeparatorThickness + width/2, 0, width - width/2, height), force); } bool EvenOddDoubleBufferTextCellWithSeparator::handleEvent(Ion::Events::Event event) { diff --git a/apps/regression/even_odd_double_buffer_text_cell_with_separator.h b/apps/regression/even_odd_double_buffer_text_cell_with_separator.h index 8f7c8c260..7e263eae3 100644 --- a/apps/regression/even_odd_double_buffer_text_cell_with_separator.h +++ b/apps/regression/even_odd_double_buffer_text_cell_with_separator.h @@ -25,7 +25,7 @@ public: void drawRect(KDContext * ctx, KDRect rect) const override; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; bool handleEvent(Ion::Events::Event event) override; protected: bool m_firstTextSelected; diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index a78abf7e9..78f8ea350 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -232,28 +232,25 @@ void GraphController::reloadBannerView() { m_bannerView.reload(); } -bool GraphController::moveCursorHorizontally(int direction) { +bool GraphController::moveCursorHorizontally(int direction, bool fast) { + double x; + double y; if (*m_selectedDotIndex >= 0) { int dotSelected = m_store->nextDot(*m_selectedSeriesIndex, direction, *m_selectedDotIndex); if (dotSelected >= 0 && dotSelected < m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) { - *m_selectedDotIndex = dotSelected; - double x = m_store->get(*m_selectedSeriesIndex, 0, *m_selectedDotIndex); - double y = m_store->get(*m_selectedSeriesIndex, 1, *m_selectedDotIndex); - m_cursor->moveTo(x, x, y); - return true; + x = m_store->get(*m_selectedSeriesIndex, 0, dotSelected); + y = m_store->get(*m_selectedSeriesIndex, 1, dotSelected); + } else if (dotSelected == m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) { + x = m_store->meanOfColumn(*m_selectedSeriesIndex, 0); + y = m_store->meanOfColumn(*m_selectedSeriesIndex, 1); + } else { + return false; } - if (dotSelected == m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) { - *m_selectedDotIndex = dotSelected; - double x = m_store->meanOfColumn(*m_selectedSeriesIndex, 0); - double y = m_store->meanOfColumn(*m_selectedSeriesIndex, 1); - m_cursor->moveTo(x, x, y); - return true; - } - return false; + *m_selectedDotIndex = dotSelected; + } else { + x = m_cursor->x() + direction * m_store->xGridUnit()/k_numberOfCursorStepsInGradUnit; + y = yValue(*m_selectedSeriesIndex, x, globalContext()); } - double x = direction > 0 ? m_cursor->x() + m_store->xGridUnit()/k_numberOfCursorStepsInGradUnit : - m_cursor->x() - m_store->xGridUnit()/k_numberOfCursorStepsInGradUnit; - double y = yValue(*m_selectedSeriesIndex, x, globalContext()); m_cursor->moveTo(x, x, y); return true; } diff --git a/apps/regression/graph_controller.h b/apps/regression/graph_controller.h index ad6c10529..0c35ad9ab 100644 --- a/apps/regression/graph_controller.h +++ b/apps/regression/graph_controller.h @@ -25,7 +25,7 @@ public: int selectedSeriesIndex() const { return *m_selectedSeriesIndex; } // moveCursorHorizontally and Vertically are public to be used in tests - bool moveCursorHorizontally(int direction) override; + bool moveCursorHorizontally(int direction, bool fast = false) override; bool moveCursorVertically(int direction) override; private: diff --git a/apps/regression/graph_view.cpp b/apps/regression/graph_view.cpp index 1cbc69252..f5883cb16 100644 --- a/apps/regression/graph_view.cpp +++ b/apps/regression/graph_view.cpp @@ -35,7 +35,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { for (int index = 0; index < m_store->numberOfPairsOfSeries(series); index++) { drawDot(ctx, rect, m_store->get(series, 0, index), m_store->get(series, 1, index), color); } - drawDot(ctx, rect, m_store->meanOfColumn(series, 0), m_store->meanOfColumn(series, 1), color, true); + drawDot(ctx, rect, m_store->meanOfColumn(series, 0), m_store->meanOfColumn(series, 1), color, Size::Medium); drawDot(ctx, rect, m_store->meanOfColumn(series, 0), m_store->meanOfColumn(series, 1), Palette::BackgroundHard); } } diff --git a/apps/regression/model/linear_model.h b/apps/regression/model/linear_model.h index 6f1c91153..4fd9d2988 100644 --- a/apps/regression/model/linear_model.h +++ b/apps/regression/model/linear_model.h @@ -12,7 +12,7 @@ public: I18n::Message formulaMessage() const override { return I18n::Message::LinearRegressionFormula; } double evaluate(double * modelCoefficients, double x) const override; double levelSet(double * modelCoefficients, double xMin, double step, double xMax, double y, Poincare::Context * context) override; - virtual void fit(Store * store, int series, double * modelCoefficients, 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 2; } int bannerLinesCount() const override { return 3; } diff --git a/apps/regression/model/logistic_model.cpp b/apps/regression/model/logistic_model.cpp index 5ffe02a48..f74db8f3d 100644 --- a/apps/regression/model/logistic_model.cpp +++ b/apps/regression/model/logistic_model.cpp @@ -1,4 +1,5 @@ #include "logistic_model.h" +#include "../store.h" #include #include #include @@ -81,4 +82,16 @@ double LogisticModel::partialDerivate(double * modelCoefficients, int derivateCo return 0.0; } +void LogisticModel::specializedInitCoefficientsForFit(double * modelCoefficients, double defaultValue, Store * store, int series) const { + assert(store != nullptr && series >= 0 && series < Store::k_numberOfSeries && !store->seriesIsEmpty(series)); + modelCoefficients[0] = defaultValue; + modelCoefficients[1] = defaultValue; + /* If the data is a standard logistic function, the ordinates are between 0 + * and c. Twice the standard vertical deviation is a rough estimate of c + * that is "close enough" to c to seed the coefficient, without being too + * dependent on outliers.*/ + modelCoefficients[2] = 2.0*store->standardDeviationOfColumn(series, 1); +} + + } diff --git a/apps/regression/model/logistic_model.h b/apps/regression/model/logistic_model.h index aa338e88e..fbdca27e9 100644 --- a/apps/regression/model/logistic_model.h +++ b/apps/regression/model/logistic_model.h @@ -15,6 +15,8 @@ public: double partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const override; int numberOfCoefficients() const override { return 3; } int bannerLinesCount() const override { return 3; } +private: + void specializedInitCoefficientsForFit(double * modelCoefficients, double defaultValue, Store * store, int series) const override; }; } diff --git a/apps/regression/model/model.cpp b/apps/regression/model/model.cpp index d949b1571..13f711574 100644 --- a/apps/regression/model/model.cpp +++ b/apps/regression/model/model.cpp @@ -33,14 +33,10 @@ double Model::levelSet(double * modelCoefficients, double xMin, double step, dou void Model::fit(Store * store, int series, double * modelCoefficients, Poincare::Context * context) { if (dataSuitableForFit(store, series)) { - for (int i = 0; i < numberOfCoefficients(); i++) { - modelCoefficients[i] = k_initialCoefficientValue; - } + initCoefficientsForFit(modelCoefficients, k_initialCoefficientValue, false, store, series); fitLevenbergMarquardt(store, series, modelCoefficients, context); } else { - for (int i = 0; i < numberOfCoefficients(); i++) { - modelCoefficients[i] = NAN; - } + initCoefficientsForFit(modelCoefficients, NAN, true); } } @@ -205,5 +201,20 @@ int Model::solveLinearSystem(double * solutions, double * coefficients, double * return 0; } +void Model::initCoefficientsForFit(double * modelCoefficients, double defaultValue, bool forceDefaultValue, Store * store, int series) const { + assert(forceDefaultValue || (store != nullptr && series >= 0 && series < Store::k_numberOfSeries && !store->seriesIsEmpty(series))); + if (forceDefaultValue) { + Model::specializedInitCoefficientsForFit(modelCoefficients, defaultValue); + } else { + specializedInitCoefficientsForFit(modelCoefficients, defaultValue, store, series); + } } +void Model::specializedInitCoefficientsForFit(double * modelCoefficients, double defaultValue, Store * store, int series) const { + const int nbCoef = numberOfCoefficients(); + for (int i = 0; i < nbCoef; i++) { + modelCoefficients[i] = defaultValue; + } +} + +} diff --git a/apps/regression/model/model.h b/apps/regression/model/model.h index b45247961..c018a3528 100644 --- a/apps/regression/model/model.h +++ b/apps/regression/model/model.h @@ -61,6 +61,8 @@ private: double alphaCoefficient(Store * store, int series, double * modelCoefficients, int k, int l) const; double betaCoefficient(Store * store, int series, double * modelCoefficients, int k) const; int solveLinearSystem(double * solutions, double * coefficients, double * constants, int solutionDimension, Poincare::Context * context); + void initCoefficientsForFit(double * modelCoefficients, double defaultValue, bool forceDefaultValue, Store * store = nullptr, int series = -1) const; + virtual void specializedInitCoefficientsForFit(double * modelCoefficients, double defaultValue, Store * store = nullptr, int series = -1) const; }; } diff --git a/apps/regression/model/power_model.cpp b/apps/regression/model/power_model.cpp index 928e25514..c152c9c1d 100644 --- a/apps/regression/model/power_model.cpp +++ b/apps/regression/model/power_model.cpp @@ -61,6 +61,12 @@ double PowerModel::partialDerivate(double * modelCoefficients, int derivateCoeff return 0.0; } +void PowerModel::fit(Store * store, int series, double * modelCoefficients, Poincare::Context * context) { + /* Y1 = aX1^b => ln(Y1) = ln(a) + b*ln(X1)*/ + modelCoefficients[0] = exp(store->yIntercept(series, true)); + modelCoefficients[1] = store->slope(series, true); +} + bool PowerModel::dataSuitableForFit(Store * store, int series) const { if (!Model::dataSuitableForFit(store, series)) { return false; diff --git a/apps/regression/model/power_model.h b/apps/regression/model/power_model.h index ac544e5c1..638467386 100644 --- a/apps/regression/model/power_model.h +++ b/apps/regression/model/power_model.h @@ -13,10 +13,11 @@ public: double evaluate(double * modelCoefficients, double x) const override; double levelSet(double * modelCoefficients, double xMin, double step, double xMax, double y, Poincare::Context * context) override; double partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const override; + void fit(Store * store, int series, double * modelCoefficients, Poincare::Context * context) override; int numberOfCoefficients() const override { return 2; } int bannerLinesCount() const override { return 2; } protected: - virtual bool dataSuitableForFit(Store * store, int series) const override; + bool dataSuitableForFit(Store * store, int series) const override; }; } diff --git a/apps/regression/model/trigonometric_model.cpp b/apps/regression/model/trigonometric_model.cpp index 166af2be4..f32f2c66b 100644 --- a/apps/regression/model/trigonometric_model.cpp +++ b/apps/regression/model/trigonometric_model.cpp @@ -1,4 +1,5 @@ #include "trigonometric_model.h" +#include #include "../../shared/poincare_helpers.h" #include #include @@ -69,6 +70,23 @@ double TrigonometricModel::partialDerivate(double * modelCoefficients, int deriv return 0.0; } +void TrigonometricModel::specializedInitCoefficientsForFit(double * modelCoefficients, double defaultValue, Store * store, int series) const { + assert(store != nullptr && series >= 0 && series < Store::k_numberOfSeries && !store->seriesIsEmpty(series)); + for (int i = 1; i < k_numberOfCoefficients - 1; i++) { + modelCoefficients[i] = defaultValue; + } + /* We try a better initialization than the default value. We hope that this + * will improve the gradient descent to find correct coefficients. + * + * Init the "amplitude" coefficient. We take twice the standard deviation, + * because for a normal law, this interval contains 99.73% of the values. We + * do not take half of the apmlitude of the series, because this would be too + * dependant on outliers. */ + modelCoefficients[0] = 3.0*store->standardDeviationOfColumn(series, 1); + // Init the "y delta" coefficient + modelCoefficients[k_numberOfCoefficients - 1] = store->meanOfColumn(series, 1); +} + Expression TrigonometricModel::expression(double * modelCoefficients) { double a = modelCoefficients[0]; double b = modelCoefficients[1]; diff --git a/apps/regression/model/trigonometric_model.h b/apps/regression/model/trigonometric_model.h index a22bf69a0..83ed05113 100644 --- a/apps/regression/model/trigonometric_model.h +++ b/apps/regression/model/trigonometric_model.h @@ -12,9 +12,11 @@ public: I18n::Message formulaMessage() const override { return I18n::Message::TrigonometricRegressionFormula; } double evaluate(double * modelCoefficients, double x) const override; double partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const override; - int numberOfCoefficients() const override { return 4; } + int numberOfCoefficients() const override { return k_numberOfCoefficients; } int bannerLinesCount() const override { return 4; } private: + static constexpr int k_numberOfCoefficients = 4; + void specializedInitCoefficientsForFit(double * modelCoefficients, double defaultValue, Store * store, int series) const override; Poincare::Expression expression(double * modelCoefficients) override; }; diff --git a/apps/regression/regression_context.cpp b/apps/regression/regression_context.cpp index 87e5d61fb..956da75d8 100644 --- a/apps/regression/regression_context.cpp +++ b/apps/regression/regression_context.cpp @@ -10,7 +10,7 @@ using namespace Shared; namespace Regression { const Expression RegressionContext::expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone) { - if (symbol.type() == ExpressionNode::Type::Symbol && Symbol::isRegressionSymbol(symbol.name())) { + if (symbol.type() == ExpressionNode::Type::Symbol && Symbol::isRegressionSymbol(symbol.name(), nullptr)) { const char * seriesName = symbol.name(); assert(strlen(seriesName) == 2); @@ -24,7 +24,7 @@ const Expression RegressionContext::expressionForSymbolAbstract(const SymbolAbst assert(m_seriesPairIndex < m_store->numberOfPairsOfSeries(series)); return Float::Builder(m_store->get(series, storeI, m_seriesPairIndex)); } else { - return m_parentContext->expressionForSymbolAbstract(symbol, clone); + return ContextWithParent::expressionForSymbolAbstract(symbol, clone); } } diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index ceba3633a..0afd7941a 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -204,45 +204,53 @@ float Store::minValueOfColumn(int series, int i) const { return minColumn; } -double Store::squaredValueSumOfColumn(int series, int i) const { +double Store::squaredValueSumOfColumn(int series, int i, bool lnOfSeries) const { double result = 0; for (int k = 0; k < numberOfPairsOfSeries(series); k++) { - result += m_data[series][i][k]*m_data[series][i][k]; + if (lnOfSeries) { + result += log(m_data[series][i][k]) * log(m_data[series][i][k]); + } else { + result += m_data[series][i][k]*m_data[series][i][k]; + } } return result; } -double Store::columnProductSum(int series) const { +double Store::columnProductSum(int series, bool lnOfSeries) const { double result = 0; for (int k = 0; k < numberOfPairsOfSeries(series); k++) { - result += m_data[series][0][k]*m_data[series][1][k]; + if (lnOfSeries) { + result += log(m_data[series][0][k]) * log(m_data[series][1][k]); + } else { + result += m_data[series][0][k] * m_data[series][1][k]; + } } return result; } -double Store::meanOfColumn(int series, int i) const { - return numberOfPairsOfSeries(series) == 0 ? 0 : sumOfColumn(series, i)/numberOfPairsOfSeries(series); +double Store::meanOfColumn(int series, int i, bool lnOfSeries) const { + return numberOfPairsOfSeries(series) == 0 ? 0 : sumOfColumn(series, i, lnOfSeries)/numberOfPairsOfSeries(series); } -double Store::varianceOfColumn(int series, int i) const { - double mean = meanOfColumn(series, i); - return squaredValueSumOfColumn(series, i)/numberOfPairsOfSeries(series) - mean*mean; +double Store::varianceOfColumn(int series, int i, bool lnOfSeries) const { + double mean = meanOfColumn(series, i, lnOfSeries); + return squaredValueSumOfColumn(series, i, lnOfSeries)/numberOfPairsOfSeries(series) - mean*mean; } -double Store::standardDeviationOfColumn(int series, int i) const { - return std::sqrt(varianceOfColumn(series, i)); +double Store::standardDeviationOfColumn(int series, int i, bool lnOfSeries) const { + return std::sqrt(varianceOfColumn(series, i, lnOfSeries)); } -double Store::covariance(int series) const { - return columnProductSum(series)/numberOfPairsOfSeries(series) - meanOfColumn(series, 0)*meanOfColumn(series, 1); +double Store::covariance(int series, bool lnOfSeries) const { + return columnProductSum(series, lnOfSeries)/numberOfPairsOfSeries(series) - meanOfColumn(series, 0, lnOfSeries)*meanOfColumn(series, 1, lnOfSeries); } -double Store::slope(int series) const { - return LinearModelHelper::Slope(covariance(series), varianceOfColumn(series, 0)); +double Store::slope(int series, bool lnOfSeries) const { + return LinearModelHelper::Slope(covariance(series, lnOfSeries), varianceOfColumn(series, 0, lnOfSeries)); } -double Store::yIntercept(int series) const { - return LinearModelHelper::YIntercept(meanOfColumn(series, 1), meanOfColumn(series, 0), slope(series)); +double Store::yIntercept(int series, bool lnOfSeries) const { + return LinearModelHelper::YIntercept(meanOfColumn(series, 1, lnOfSeries), meanOfColumn(series, 0, lnOfSeries), slope(series, lnOfSeries)); } double Store::yValueForXValue(int series, double x, Poincare::Context * globalContext) { diff --git a/apps/regression/store.h b/apps/regression/store.h index da5a24252..dfdc682c1 100644 --- a/apps/regression/store.h +++ b/apps/regression/store.h @@ -56,14 +56,14 @@ public: // Calculation double * coefficientsForSeries(int series, Poincare::Context * globalContext); double doubleCastedNumberOfPairsOfSeries(int series) const; - double squaredValueSumOfColumn(int series, int i) const; - double columnProductSum(int series) const; - double meanOfColumn(int series, int i) const; - double varianceOfColumn(int series, int i) const; - double standardDeviationOfColumn(int series, int i) const; - double covariance(int series) const; - double slope(int series) const; - double yIntercept(int series) const; + double squaredValueSumOfColumn(int series, int i, bool lnOfSeries = false) const; + double columnProductSum(int series, bool lnOfSeries = false) const; + double meanOfColumn(int series, int i, bool lnOfSeries = false) const; + double varianceOfColumn(int series, int i, bool lnOfSeries = false) const; + double standardDeviationOfColumn(int series, int i, bool lnOfSeries = false) const; + double covariance(int series, bool lnOfSeries = false) const; + double slope(int series, bool lnOfSeries = false) const; + double yIntercept(int series, bool lnOfSeries = false) const; double yValueForXValue(int series, double x, Poincare::Context * globalContext); double xValueForYValue(int series, double y, Poincare::Context * globalContext); double correlationCoefficient(int series) const; @@ -89,8 +89,7 @@ private: Poincare::Preferences::AngleUnit m_angleUnit; }; -typedef double (Store::*ArgCalculPointer)(int, int) const; -typedef double (Store::*CalculPointer)(int) const; +typedef double (Store::*ArgCalculPointer)(int, int, bool) const; typedef void (Store::*RangeMethodPointer)(); } diff --git a/apps/regression/store_controller.cpp b/apps/regression/store_controller.cpp index 6a0496a34..69293bf19 100644 --- a/apps/regression/store_controller.cpp +++ b/apps/regression/store_controller.cpp @@ -10,19 +10,14 @@ using namespace Shared; namespace Regression { -StoreController::StoreController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, Store * store, ButtonRowController * header) : +StoreController::StoreController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, Store * store, ButtonRowController * header, Context * parentContext) : Shared::StoreController(parentResponder, inputEventHandlerDelegate, store, header), m_titleCells{}, - m_regressionContext(store), + m_regressionContext(store, parentContext), m_storeParameterController(this, store, this) { } -StoreContext * StoreController::storeContext() { - m_regressionContext.setParentContext(AppsContainer::sharedAppsContainer()->globalContext()); - return &m_regressionContext; -} - void StoreController::setFormulaLabel() { int series = selectedColumn() / Store::k_numberOfColumnsPerSeries; int isXColumn = selectedColumn() % Store::k_numberOfColumnsPerSeries == 0; diff --git a/apps/regression/store_controller.h b/apps/regression/store_controller.h index b141c559a..3e5ede1cc 100644 --- a/apps/regression/store_controller.h +++ b/apps/regression/store_controller.h @@ -12,8 +12,8 @@ namespace Regression { class StoreController : public Shared::StoreController { public: - StoreController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, Store * store, ButtonRowController * header); - Shared::StoreContext * storeContext() override; + StoreController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, Store * store, ButtonRowController * header, Poincare::Context * parentContext); + Shared::StoreContext * storeContext() override { return &m_regressionContext; } void setFormulaLabel() override; bool fillColumnWithFormula(Poincare::Expression formula) override; void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; diff --git a/apps/regression/test/model.cpp b/apps/regression/test/model.cpp index d03f42fa0..b38d9936d 100644 --- a/apps/regression/test/model.cpp +++ b/apps/regression/test/model.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "../model/model.h" #include "../regression_context.h" #include "../store.h" @@ -21,7 +22,8 @@ void assert_regression_is(double * xi, double * yi, int numberOfPoints, Model::T store.set(xi[i], series, 0, i); store.set(yi[i], series, 1, i); } - RegressionContext context(&store); + Shared::GlobalContext globalContext; + RegressionContext context(&store, &globalContext); store.setSeriesRegressionType(series, modelType); // Compute the coefficients @@ -99,11 +101,18 @@ QUIZ_CASE(power_regression) { // No case for trigonometric regression, because it has no unique solution -/* This data was generated without the random error, otherwise it did not pass - * the test. */ + QUIZ_CASE(logistic_regression) { - double x[] = {2.3, 5.6, 1.1, 4.3}; - double y[] = {3.948, 4.694, 2.184, 4.656}; - double coefficients[] = {6, 1.5, 4.7}; - assert_regression_is(x, y, 4, Model::Type::Logistic, coefficients); + /* This data was generated without the random error, otherwise it did not pass + * the test. */ + double x1[] = {2.3, 5.6, 1.1, 4.3}; + double y1[] = {3.948, 4.694, 2.184, 4.656}; + double coefficients1[] = {6, 1.5, 4.7}; + assert_regression_is(x1, y1, 4, Model::Type::Logistic, coefficients1); + + // This data produced a wrong fit before + double x2[] = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}; + double y2[] = {5.0, 9.0, 40.0, 64.0, 144.0, 200.0, 269.0, 278.0, 290.0, 295.0}; + double coefficients2[] = {64.9, 1.0, 297.4}; + assert_regression_is(x2, y2, 10, Model::Type::Logistic, coefficients2); } diff --git a/apps/sequence/base.pt.i18n b/apps/sequence/base.pt.i18n index c8ac78f76..cb0d9caec 100644 --- a/apps/sequence/base.pt.i18n +++ b/apps/sequence/base.pt.i18n @@ -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 ativada" +NoActivatedSequence = "Sem sequência activada" NStart = "N início" NEnd = "N fim" TermSum = "Soma dos termos" diff --git a/apps/sequence/cache_context.cpp b/apps/sequence/cache_context.cpp index bac977d7b..be5a834b0 100644 --- a/apps/sequence/cache_context.cpp +++ b/apps/sequence/cache_context.cpp @@ -8,8 +8,8 @@ namespace Sequence { template CacheContext::CacheContext(Context * parentContext) : - m_values{{NAN, NAN},{NAN, NAN},{NAN,NAN}}, - m_parentContext(parentContext) + ContextWithParent(parentContext), + m_values{{NAN, NAN},{NAN, NAN},{NAN,NAN}} { } @@ -24,12 +24,7 @@ const Expression CacheContext::expressionForSymbolAbstract(const SymbolAbstra Symbol s = const_cast(static_cast(symbol)); return Float::Builder(m_values[nameIndexForSymbol(s)][rankIndexForSymbol(s)]); } - return m_parentContext->expressionForSymbolAbstract(symbol, clone); -} - -template -void CacheContext::setExpressionForSymbolAbstract(const Expression & expression, const SymbolAbstract & symbol) { - m_parentContext->setExpressionForSymbolAbstract(expression, symbol); + return ContextWithParent::expressionForSymbolAbstract(symbol, clone); } template diff --git a/apps/sequence/cache_context.h b/apps/sequence/cache_context.h index 26191b8b6..37c6200e5 100644 --- a/apps/sequence/cache_context.h +++ b/apps/sequence/cache_context.h @@ -9,17 +9,15 @@ namespace Sequence { template -class CacheContext : public Poincare::Context { +class CacheContext : public Poincare::ContextWithParent { public: CacheContext(Poincare::Context * parentContext); const Poincare::Expression expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone) override; - void setExpressionForSymbolAbstract(const Poincare::Expression & expression, const Poincare::SymbolAbstract & symbol) override; void setValueForSymbol(T value, const Poincare::Symbol & symbol); private: int nameIndexForSymbol(const Poincare::Symbol & symbol); int rankIndexForSymbol(const Poincare::Symbol & symbol); T m_values[MaxNumberOfSequences][MaxRecurrenceDepth]; - Context * m_parentContext; }; } diff --git a/apps/sequence/graph/curve_view_range.cpp b/apps/sequence/graph/curve_view_range.cpp index 4c2fb865b..85c828d86 100644 --- a/apps/sequence/graph/curve_view_range.cpp +++ b/apps/sequence/graph/curve_view_range.cpp @@ -8,6 +8,8 @@ using namespace Poincare; namespace Sequence { +static inline float maxFloat(float x, float y) { return x > y ? x : y; } + CurveViewRange::CurveViewRange(InteractiveCurveViewRangeDelegate * delegate) : InteractiveCurveViewRange(delegate) { @@ -35,13 +37,16 @@ void CurveViewRange::normalize() { float xMean = xCenter(); float yMean = yCenter(); + const float unit = maxFloat(xGridUnit(), yGridUnit()); + // Compute the X - float newXMin = xMean - NormalizedXHalfRange(); - float newXMax = xMean + NormalizedXHalfRange(); + const float newXHalfRange = NormalizedXHalfRange(unit); + float newXMin = xMean - newXHalfRange; + float newXMax = xMean + newXHalfRange; float interestingXMin = m_delegate->interestingXMin(); if (newXMin < interestingXMin) { - newXMin = interestingXMin -k_displayLeftMarginRatio*2.0f*NormalizedXHalfRange(); - newXMax = newXMin + 2.0f*NormalizedXHalfRange(); + newXMin = interestingXMin -k_displayLeftMarginRatio*2.0f*newXHalfRange; + newXMax = newXMin + 2.0f*newXHalfRange; } if (!std::isnan(newXMin) && !std::isnan(newXMax)) { m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat); @@ -50,8 +55,9 @@ void CurveViewRange::normalize() { // Compute the Y m_yAuto = false; - float newYMin = yMean - NormalizedYHalfRange(); - float newYMax = clipped(yMean + NormalizedYHalfRange(), true); + const float newYHalfRange = NormalizedYHalfRange(unit); + float newYMin = yMean - newYHalfRange; + float newYMax = clipped(yMean + newYHalfRange, true); if (!std::isnan(newYMin) && !std::isnan(newYMax)) { m_yRange.setMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat); diff --git a/apps/sequence/graph/graph_controller.cpp b/apps/sequence/graph/graph_controller.cpp index d77870675..c2c4b8fea 100644 --- a/apps/sequence/graph/graph_controller.cpp +++ b/apps/sequence/graph/graph_controller.cpp @@ -81,7 +81,7 @@ bool GraphController::handleEnter() { return FunctionGraphController::handleEnter(); } -bool GraphController::moveCursorHorizontally(int direction) { +bool GraphController::moveCursorHorizontally(int direction, bool fast) { double xCursorPosition = std::round(m_cursor->x()); if (direction < 0 && xCursorPosition <= 0) { return false; diff --git a/apps/sequence/graph/graph_controller.h b/apps/sequence/graph/graph_controller.h index f0e4bb118..da0e64006 100644 --- a/apps/sequence/graph/graph_controller.h +++ b/apps/sequence/graph/graph_controller.h @@ -25,7 +25,7 @@ public: private: Shared::XYBannerView * bannerView() override { return &m_bannerView; } bool handleEnter() override; - bool moveCursorHorizontally(int direction) override; + bool moveCursorHorizontally(int direction, bool fast = false) override; double defaultCursorT(Ion::Storage::Record record) override; CurveViewRange * interactiveCurveViewRange() override { return m_graphRange; } SequenceStore * functionStore() const override { return static_cast(Shared::FunctionGraphController::functionStore()); } diff --git a/apps/sequence/list/list_controller.cpp b/apps/sequence/list/list_controller.cpp index c9e0f7fc5..8b97f791b 100644 --- a/apps/sequence/list/list_controller.cpp +++ b/apps/sequence/list/list_controller.cpp @@ -149,7 +149,8 @@ bool ListController::editInitialConditionOfSelectedRecordWithText(const char * t resetMemoizationForIndex(k_memoizedCellsCount/2); Ion::Storage::Record record = modelStore()->recordAtIndex(modelIndexForRow(selectedRow())); Sequence * sequence = modelStore()->modelForRecord(record); - Ion::Storage::Record::ErrorStatus error = firstInitialCondition? sequence->setFirstInitialConditionContent(text) : sequence->setSecondInitialConditionContent(text); + Context * context = App::app()->localContext(); + Ion::Storage::Record::ErrorStatus error = firstInitialCondition? sequence->setFirstInitialConditionContent(text, context) : sequence->setSecondInitialConditionContent(text, context); return (error == Ion::Storage::Record::ErrorStatus::None); } @@ -265,19 +266,19 @@ void ListController::reinitSelectedExpression(ExpiringPointerfirstInitialConditionExpressionClone().isUninitialized()) { return; } - sequence->setFirstInitialConditionContent(""); + sequence->setFirstInitialConditionContent("", nullptr); // No context needed here break; case 2: if (sequence->secondInitialConditionExpressionClone().isUninitialized()) { return; } - sequence->setSecondInitialConditionContent(""); + sequence->setSecondInitialConditionContent("", nullptr); // No context needed here break; default: if (sequence->expressionClone().isUninitialized()) { return; } - sequence->setContent(""); + sequence->setContent("", nullptr); // No context needed break; } selectableTableView()->reloadData(); diff --git a/apps/sequence/list/list_parameter_controller.cpp b/apps/sequence/list/list_parameter_controller.cpp index 56850b68f..b40cab658 100644 --- a/apps/sequence/list/list_parameter_controller.cpp +++ b/apps/sequence/list/list_parameter_controller.cpp @@ -12,7 +12,7 @@ ListParameterController::ListParameterController(::InputEventHandlerDelegate * i Shared::ListParameterController(listController, I18n::Message::SequenceColor, I18n::Message::DeleteSequence, this), m_typeCell(I18n::Message::SequenceType), m_initialRankCell(&m_selectableTableView, inputEventHandlerDelegate, this, I18n::Message::FirstTermIndex), - m_typeParameterController(this, listController, TableCell::Layout::Horizontal, Metric::CommonTopMargin, Metric::CommonRightMargin, + m_typeParameterController(this, listController, TableCell::Layout::HorizontalLeftOverlap, Metric::CommonTopMargin, Metric::CommonRightMargin, Metric::CommonBottomMargin, Metric::CommonLeftMargin) { } diff --git a/apps/sequence/list/sequence_toolbox.cpp b/apps/sequence/list/sequence_toolbox.cpp index 9037a34c5..a25ce35cf 100644 --- a/apps/sequence/list/sequence_toolbox.cpp +++ b/apps/sequence/list/sequence_toolbox.cpp @@ -14,6 +14,9 @@ SequenceToolbox::SequenceToolbox() : m_addedCellLayout{}, m_numberOfAddedCells(0) { + for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) { + m_addedCells[i].setParentResponder(&m_selectableTableView); + } } bool SequenceToolbox::handleEvent(Ion::Events::Event event) { @@ -91,7 +94,7 @@ void SequenceToolbox::buildExtraCellsLayouts(const char * sequenceName, int recu bool SequenceToolbox::selectAddedCell(int selectedRow){ constexpr int bufferSize = 10; char buffer[bufferSize]; - m_addedCellLayout[selectedRow].serializeParsedExpression(buffer, bufferSize); + m_addedCellLayout[selectedRow].serializeParsedExpression(buffer, bufferSize, nullptr); // No need of context here sender()->handleEventWithText(buffer); Container::activeApp()->dismissModalViewController(); return true; diff --git a/apps/sequence/list/type_parameter_controller.cpp b/apps/sequence/list/type_parameter_controller.cpp index fca5f400d..cec1c3e04 100644 --- a/apps/sequence/list/type_parameter_controller.cpp +++ b/apps/sequence/list/type_parameter_controller.cpp @@ -14,9 +14,9 @@ namespace Sequence { TypeParameterController::TypeParameterController(Responder * parentResponder, ListController * list, TableCell::Layout cellLayout, KDCoordinate topMargin, KDCoordinate rightMargin, KDCoordinate bottomMargin, KDCoordinate leftMargin) : ViewController(parentResponder), - m_explicitCell(I18n::Message::Explicit, cellLayout), - m_singleRecurrenceCell(I18n::Message::SingleRecurrence, cellLayout), - m_doubleRecurenceCell(I18n::Message::DoubleRecurrence, cellLayout), + m_explicitCell(&m_selectableTableView, I18n::Message::Explicit, cellLayout), + m_singleRecurrenceCell(&m_selectableTableView, I18n::Message::SingleRecurrence, cellLayout), + m_doubleRecurenceCell(&m_selectableTableView, I18n::Message::DoubleRecurrence, cellLayout), m_layouts{}, m_selectableTableView(this), m_record(), diff --git a/apps/sequence/sequence.cpp b/apps/sequence/sequence.cpp index ca2a372bc..e97f8d390 100644 --- a/apps/sequence/sequence.cpp +++ b/apps/sequence/sequence.cpp @@ -51,13 +51,13 @@ void Sequence::setType(Type t) { /* Reset all contents */ switch (t) { case Type::Explicit: - setContent(""); + setContent("", nullptr); // No context needed here break; case Type::SingleRecurrence: { char ex[5] = "u(n)"; ex[0] = fullName()[0]; - setContent(ex); + setContent(ex, nullptr); // No context needed here break; } case Type::DoubleRecurrence: @@ -66,12 +66,12 @@ void Sequence::setType(Type t) { char name = fullName()[0]; ex[0] = name; ex[7] = name; - setContent(ex); + setContent(ex, nullptr); // No context needed here break; } } - setFirstInitialConditionContent(""); - setSecondInitialConditionContent(""); + setFirstInitialConditionContent("", nullptr); // No context needed here + setSecondInitialConditionContent("", nullptr); // No context needed here } void Sequence::setInitialRank(int rank) { @@ -129,7 +129,7 @@ T Sequence::approximateToNextRank(int n, SequenceContext * sqctx) const { constexpr int bufferSize = CodePoint::MaxCodePointCharLength + 1; char unknownN[bufferSize]; - Poincare::SerializationHelper::CodePoint(unknownN, bufferSize, UCodePointUnknownX); + Poincare::SerializationHelper::CodePoint(unknownN, bufferSize, UCodePointUnknown); CacheContext ctx = CacheContext(sqctx); // Hold values u(n), u(n-1), u(n-2), v(n), v(n-1), v(n-2)... diff --git a/apps/sequence/sequence.h b/apps/sequence/sequence.h index b866d4fb2..e7901de68 100644 --- a/apps/sequence/sequence.h +++ b/apps/sequence/sequence.h @@ -43,14 +43,14 @@ public: Poincare::Expression firstInitialConditionExpressionReduced(Poincare::Context * context) const { return m_firstInitialCondition.expressionReduced(this, context); } Poincare::Expression firstInitialConditionExpressionClone() const { return m_firstInitialCondition.expressionClone(this); } Poincare::Layout firstInitialConditionLayout() { return m_firstInitialCondition.layout(this); } - Ion::Storage::Record::ErrorStatus setFirstInitialConditionContent(const char * c) { return m_firstInitialCondition.setContent(this, c); } + Ion::Storage::Record::ErrorStatus setFirstInitialConditionContent(const char * c, Poincare::Context * context) { return m_firstInitialCondition.setContent(this, c, context); } // Second initial condition Poincare::Layout secondInitialConditionName() { return m_secondInitialCondition.name(this); } void secondInitialConditionText(char * buffer, size_t bufferSize) const { return m_secondInitialCondition.text(this, buffer, bufferSize); } Poincare::Expression secondInitialConditionExpressionReduced(Poincare::Context * context) const { return m_secondInitialCondition.expressionReduced(this, context); } Poincare::Expression secondInitialConditionExpressionClone() const { return m_secondInitialCondition.expressionClone(this); } Poincare::Layout secondInitialConditionLayout() { return m_secondInitialCondition.layout(this); } - Ion::Storage::Record::ErrorStatus setSecondInitialConditionContent(const char * c) { return m_secondInitialCondition.setContent(this, c); } + Ion::Storage::Record::ErrorStatus setSecondInitialConditionContent(const char * c, Poincare::Context * context) { return m_secondInitialCondition.setContent(this, c, context); } // Sequence properties int numberOfElements() { return (int)type() + 1; } @@ -121,18 +121,16 @@ private: }; class DefinitionModel : public SequenceModel { - public: - void * expressionAddress(const Ion::Storage::Record * record) const override; private: + void * expressionAddress(const Ion::Storage::Record * record) const override; size_t expressionSize(const Ion::Storage::Record * record) const override; void buildName(Sequence * sequence) override; }; class InitialConditionModel : public SequenceModel { - public: - void * expressionAddress(const Ion::Storage::Record * record) const override; private: void updateMetaData(const Ion::Storage::Record * record, size_t newSize) override; + void * expressionAddress(const Ion::Storage::Record * record) const override; size_t expressionSize(const Ion::Storage::Record * record) const override; void buildName(Sequence * sequence) override; virtual int conditionIndex() const = 0; diff --git a/apps/sequence/sequence_context.h b/apps/sequence/sequence_context.h index 91d3067a4..e4ca2cb42 100644 --- a/apps/sequence/sequence_context.h +++ b/apps/sequence/sequence_context.h @@ -1,7 +1,7 @@ #ifndef SEQUENCE_SEQUENCE_CONTEXT_H #define SEQUENCE_SEQUENCE_CONTEXT_H -#include +#include #include #include @@ -33,24 +33,17 @@ private: T m_values[MaxNumberOfSequences][MaxRecurrenceDepth+1]; }; -class SequenceContext : public Poincare::Context { +class SequenceContext : public Poincare::ContextWithParent { public: SequenceContext(Poincare::Context * parentContext, SequenceStore * sequenceStore) : - Context(), + ContextWithParent(parentContext), m_floatSequenceContext(), m_doubleSequenceContext(), - m_sequenceStore(sequenceStore), - m_parentContext(parentContext) {} + m_sequenceStore(sequenceStore) {} /* expressionForSymbolAbstract & setExpressionForSymbolAbstractName directly call the parent * context respective methods. Indeed, special chars like n, u(n), u(n+1), * v(n), v(n+1) are taken into accound only when evaluating sequences which * is done in another context. */ - const Poincare::Expression expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone) override { - return m_parentContext->expressionForSymbolAbstract(symbol, clone); - } - void setExpressionForSymbolAbstract(const Poincare::Expression & expression, const Poincare::SymbolAbstract & symbol) override { - m_parentContext->setExpressionForSymbolAbstract(expression, symbol); - } template T valueOfSequenceAtPreviousRank(int sequenceIndex, int rank) const { if (sizeof(T) == sizeof(float)) { return m_floatSequenceContext.valueOfSequenceAtPreviousRank(sequenceIndex, rank); @@ -71,7 +64,6 @@ private: TemplatedSequenceContext m_floatSequenceContext; TemplatedSequenceContext m_doubleSequenceContext; SequenceStore * m_sequenceStore; - Poincare::Context * m_parentContext; }; } diff --git a/apps/sequence/sequence_title_cell.cpp b/apps/sequence/sequence_title_cell.cpp index e2357959d..f1802b10a 100644 --- a/apps/sequence/sequence_title_cell.cpp +++ b/apps/sequence/sequence_title_cell.cpp @@ -52,11 +52,11 @@ View * SequenceTitleCell::subviewAtIndex(int index) { return &m_titleTextView; } -void SequenceTitleCell::layoutSubviews() { +void SequenceTitleCell::layoutSubviews(bool force) { if (m_orientation == Orientation::VerticalIndicator) { m_titleTextView.setAlignment(k_verticalOrientationHorizontalAlignment, verticalAlignment()); } - m_titleTextView.setFrame(subviewFrame()); + m_titleTextView.setFrame(subviewFrame(), force); } float SequenceTitleCell::verticalAlignmentGivenExpressionBaselineAndRowHeight(KDCoordinate expressionBaseline, KDCoordinate rowHeight) const { diff --git a/apps/sequence/sequence_title_cell.h b/apps/sequence/sequence_title_cell.h index bcebbd4a9..b858fe1a0 100644 --- a/apps/sequence/sequence_title_cell.h +++ b/apps/sequence/sequence_title_cell.h @@ -25,7 +25,7 @@ private: static constexpr float k_verticalOrientationHorizontalAlignment = 0.9f; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; float verticalAlignmentGivenExpressionBaselineAndRowHeight(KDCoordinate expressionBaseline, KDCoordinate rowHeight) const override; EvenOddExpressionCell m_titleTextView; }; diff --git a/apps/sequence/test/sequence.cpp b/apps/sequence/test/sequence.cpp index 02aaddec9..8d423b010 100644 --- a/apps/sequence/test/sequence.cpp +++ b/apps/sequence/test/sequence.cpp @@ -12,19 +12,19 @@ using namespace Shared; namespace Sequence { -Sequence * addSequence(SequenceStore * store, Sequence::Type type, const char * definition, const char * condition1, const char * condition2) { +Sequence * addSequence(SequenceStore * store, Sequence::Type type, const char * definition, const char * condition1, const char * condition2, Context * context) { Ion::Storage::Record::ErrorStatus err = store->addEmptyModel(); assert(err == Ion::Storage::Record::ErrorStatus::None); (void) err; // Silence compilation warning about unused variable. Ion::Storage::Record record = store->recordAtIndex(store->numberOfModels()-1); Sequence * u = store->modelForRecord(record); u->setType(type); - u->setContent(definition); + u->setContent(definition, context); if (condition1) { - u->setFirstInitialConditionContent(condition1); + u->setFirstInitialConditionContent(condition1, context); } if (condition2) { - u->setSecondInitialConditionContent(condition2); + u->setSecondInitialConditionContent(condition2, context); } return u; } @@ -36,7 +36,7 @@ void check_sequences_defined_by(double result[MaxNumberOfSequences][10], Sequenc Sequence * seqs[MaxNumberOfSequences]; for (int i = 0; i < MaxNumberOfSequences; i++) { - seqs[i] = addSequence(&store, types[i], definitions[i], conditions1[i], conditions2[i]); + seqs[i] = addSequence(&store, types[i], definitions[i], conditions1[i], conditions2[i], &globalContext); } for (int j = 0; j < 10; j++) { @@ -55,7 +55,7 @@ void check_sum_of_sequence_between_bounds(double result, double start, double en SequenceStore store; SequenceContext sequenceContext(&globalContext, &store); - Sequence * seq = addSequence(&store, type, definition, condition1, condition2); + Sequence * seq = addSequence(&store, type, definition, condition1, condition2, &globalContext); double sum = PoincareHelpers::ApproximateToScalar(seq->sumBetweenBounds(start, end, &sequenceContext), &globalContext); quiz_assert(std::fabs(sum - result) < 0.00000001); diff --git a/apps/settings/Makefile b/apps/settings/Makefile index 9b963d8ac..17229f77c 100644 --- a/apps/settings/Makefile +++ b/apps/settings/Makefile @@ -1,28 +1,46 @@ apps += Settings::App app_headers += apps/settings/app.h +app_settings_test_src = $(addprefix apps/settings/,\ + settings_message_tree.cpp \ +) + app_settings_src = $(addprefix apps/settings/,\ app.cpp \ + cell_with_separator.cpp \ main_controller.cpp \ - settings_message_tree.cpp \ sub_menu/about_controller.cpp \ sub_menu/accessibility_controller.cpp \ sub_menu/display_mode_controller.cpp \ sub_menu/exam_mode_controller.cpp \ sub_menu/generic_sub_controller.cpp \ sub_menu/language_controller.cpp \ - sub_menu/message_table_cell_with_editable_text_with_separator.cpp \ sub_menu/preferences_controller.cpp \ sub_menu/contributors_controller.cpp \ sub_menu/math_options_controller.cpp \ + sub_menu/selectable_view_with_messages.cpp \ ) +app_settings_src += $(app_settings_test_src) app_src += $(app_settings_src) apps_prompt_none_src += apps/settings/main_controller_prompt_none.cpp apps_prompt_beta_src += apps/settings/main_controller_prompt_beta.cpp apps_prompt_update_src += apps/settings/main_controller_prompt_update.cpp +apps_settings_official += $(addprefix apps/settings/,\ + sub_menu/about_controller_official.cpp \ + sub_menu/exam_mode_controller_official.cpp \ +) + +apps_settings_non_official += $(addprefix apps/settings/,\ + sub_menu/about_controller_non_official.cpp \ + sub_menu/exam_mode_controller_non_official.cpp \ +) + +apps_official += $(apps_settings_official) +apps_non_official += $(apps_settings_non_official) + i18n_files += $(addprefix apps/settings/,\ base.de.i18n\ base.en.i18n\ diff --git a/apps/settings/base.de.i18n b/apps/settings/base.de.i18n index e9239b737..e8e4b3750 100644 --- a/apps/settings/base.de.i18n +++ b/apps/settings/base.de.i18n @@ -9,6 +9,19 @@ ComplexFormat = "Komplex" ExamMode = "Testmodus" ActivateExamMode = "Starten Testmodus" ExamModeActive = "Wieder starten Testmodus" +ActivateDutchExamMode = "Activate Dutch exam mode" +ToDeactivateExamMode1 = "Um den Testmodus auszuschalten," +ToDeactivateExamMode2 = "schließen Sie den Rechner an einen" +ToDeactivateExamMode3 = "Computer oder eine Steckdose an." +# --------------------- 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." +# ----------------------------------------------------------------------------- About = "Über" Degrees = "Grad " Gradians = "Gone " @@ -25,6 +38,9 @@ SoftwareVersion = "Epsilon version" CustomSoftwareVersion = "Omega version" Username = "Name" MicroPythonVersion = "µPythonversion" +FontSizes = "Python Schriftgröße" +LargeFont = "Große " +SmallFont = "Kleine " SerialNumber = "Seriennummer" UpdatePopUp = "Erinnerung: Update" BetaPopUp = "Beta pop-up" diff --git a/apps/settings/base.en.i18n b/apps/settings/base.en.i18n index edcab2930..377c81d92 100644 --- a/apps/settings/base.en.i18n +++ b/apps/settings/base.en.i18n @@ -9,6 +9,19 @@ ComplexFormat = "Complex format" ExamMode = "Exam mode" ActivateExamMode = "Activate exam mode" ExamModeActive = "Reactivate exam mode" +ActivateDutchExamMode = "Activate Dutch exam mode" +ToDeactivateExamMode1 = "To deactivate the exam mode," +ToDeactivateExamMode2 = "plug the calculator to a computer" +ToDeactivateExamMode3 = "or to a power socket." +# --------------------- 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." +# ----------------------------------------------------------------------------- About = "About" Degrees = "Degrees " Gradians = "Gradians " @@ -25,6 +38,9 @@ SoftwareVersion = "Epsilon version" CustomSoftwareVersion = "Omega version" Username = "Name" MicroPythonVersion = "µPython version" +FontSizes = "Python font size" +LargeFont = "Large " +SmallFont = "Small " SerialNumber = "Serial number" UpdatePopUp = "Update pop-up" BetaPopUp = "Beta pop-up" diff --git a/apps/settings/base.es.i18n b/apps/settings/base.es.i18n index 0192f2e8e..68435d29a 100644 --- a/apps/settings/base.es.i18n +++ b/apps/settings/base.es.i18n @@ -9,6 +9,19 @@ ComplexFormat = "Forma compleja" ExamMode = "Modo examen" ActivateExamMode = "Activar el modo examen" ExamModeActive = "Reactivar el modo examen" +ActivateDutchExamMode = "Activate Dutch exam mode" +ToDeactivateExamMode1 = "Para desactivar el modo examen," +ToDeactivateExamMode2 = "conecte la calculadora a un ordenador" +ToDeactivateExamMode3 = "o a un enchufe eléctrico." +# --------------------- 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." +# ----------------------------------------------------------------------------- About = "Acerca" Degrees = "Grados " Gradians = "Gradianes " @@ -25,6 +38,9 @@ SoftwareVersion = "Versión de Epsilon" CustomSoftwareVersion = "Versión de Omega" Username = "Apellido" MicroPythonVersion = "Version de µPython" +FontSizes = "Tipografía Python" +LargeFont = "Grande " +SmallFont = "Pequeño " SerialNumber = "Número serie" UpdatePopUp = "Pop-up de actualización" BetaPopUp = "Beta pop-up" diff --git a/apps/settings/base.fr.i18n b/apps/settings/base.fr.i18n index 01baa2fa7..2575bf2a9 100644 --- a/apps/settings/base.fr.i18n +++ b/apps/settings/base.fr.i18n @@ -9,6 +9,19 @@ ComplexFormat = "Forme complexe" ExamMode = "Mode examen" ActivateExamMode = "Activer le mode examen" ExamModeActive = "Réactiver le mode examen" +ActivateDutchExamMode = "Activate Dutch exam mode" +ToDeactivateExamMode1 = "Pour désactiver le mode examen," +ToDeactivateExamMode2 = "brancher la calculatrice à un" +ToDeactivateExamMode3 = "ordinateur ou à une prise de courant." +# --------------------- Please do not edit these messages --------------------- +ExamModeWarning1 = "Attention, la conformité du mode" +ExamModeWarning2 = "examen de ce logiciel non officiel" +ExamModeWarning3 = "n'est pas garantie par NumWorks." +AboutWarning1 = "Attention, vous utilisez une version" +AboutWarning2 = "non officielle du logiciel. NumWorks" +AboutWarning3 = "ne saurait être tenu responsable des" +AboutWarning4 = "problèmes que cela pourrait entraîner." +# ----------------------------------------------------------------------------- About = "À propos" Degrees = "Degrés " Gradians = "Grades " @@ -25,6 +38,9 @@ SoftwareVersion = "Version d'Epsilon" CustomSoftwareVersion = "Version d'Omega" Username = "Nom" MicroPythonVersion = "Version de µPython" +FontSizes = "Police Python" +LargeFont = "Grand " +SmallFont = "Petit " SerialNumber = "Numéro série" UpdatePopUp = "Rappel mise à jour" BetaPopUp = "Rappel version bêta" diff --git a/apps/settings/base.hu.i18n b/apps/settings/base.hu.i18n index be3be787d..17b58f4db 100644 --- a/apps/settings/base.hu.i18n +++ b/apps/settings/base.hu.i18n @@ -9,6 +9,19 @@ ComplexFormat = "Komplex formátum" ExamMode = "Vizsga mód" ActivateExamMode = "A vizsgálati mód aktiválása" ExamModeActive = "A vizsgamód újraaktiválása" +ActivateDutchExamMode = "Activate Dutch exam mode" +ToDeactivateExamMode1 = "To deactivate the exam mode," +ToDeactivateExamMode2 = "plug the calculator to a computer" +ToDeactivateExamMode3 = "or to a power socket." +# --------------------- 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." +# ----------------------------------------------------------------------------- About = "Egyéb" Degrees = "Fokok " Gradians = "Gradians " @@ -25,6 +38,9 @@ SoftwareVersion = "Epsilon verzió" CustomSoftwareVersion = "Omega verzió" Username = "Felhasználónév" MicroPythonVersion = "µPython verzió" +FontSizes = "Python font size" +LargeFont = "Large " +SmallFont = "Small " SerialNumber = "Sorozatszám" UpdatePopUp = "Elöugró ablak frissítése" BetaPopUp = "Béta pop-up" diff --git a/apps/settings/base.pt.i18n b/apps/settings/base.pt.i18n index 591173082..c12556e39 100644 --- a/apps/settings/base.pt.i18n +++ b/apps/settings/base.pt.i18n @@ -7,8 +7,21 @@ EditionLinear = "Em linha " Edition2D = "Natural " ComplexFormat = "Complexos" ExamMode = "Modo de exame" -ActivateExamMode = "Ativar o modo de exame" -ExamModeActive = "Reativar o modo de exame" +ActivateExamMode = "Activar o modo de exame" +ExamModeActive = "Reactivar o modo de exame" +ActivateDutchExamMode = "Activate Dutch exam mode" +ToDeactivateExamMode1 = "Para desactivar o modo de exame," +ToDeactivateExamMode2 = "ligue a calculadora a um computador" +ToDeactivateExamMode3 = "ou a uma tomada eléctrica." +# --------------------- 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." +# ----------------------------------------------------------------------------- About = "Acerca" Degrees = "Graus " Gradians = "Grados " @@ -25,6 +38,9 @@ SoftwareVersion = "Versão do Epsilon" CustomSoftwareVersion = "Versão do Omega" Username = "Nome" MicroPythonVersion = "Versao do µPython" +FontSizes = "Tipografia Python" +LargeFont = "Grande " +SmallFont = "Pequeno " SerialNumber = "Número serie" UpdatePopUp = "Alertas de atualização" BetaPopUp = "Beta pop-up" diff --git a/apps/settings/cell_with_separator.cpp b/apps/settings/cell_with_separator.cpp new file mode 100644 index 000000000..7f421ce28 --- /dev/null +++ b/apps/settings/cell_with_separator.cpp @@ -0,0 +1,27 @@ +#include "cell_with_separator.h" + +namespace Settings { + +void CellWithSeparator::setHighlighted(bool highlight) { + cell()->setHighlighted(highlight); + HighlightCell::setHighlighted(highlight); +} + +void CellWithSeparator::drawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(KDRect(0, Metric::CellSeparatorThickness, bounds().width(), k_margin), Palette::WallScreen); +} + +int CellWithSeparator::numberOfSubviews() const { + return 1; +} + +View * CellWithSeparator::subviewAtIndex(int index) { + assert(index == 0); + return cell(); +} + +void CellWithSeparator::layoutSubviews(bool force) { + cell()->setFrame(KDRect(0, k_margin, bounds().width(), bounds().height()-k_margin), force); +} + +} diff --git a/apps/settings/cell_with_separator.h b/apps/settings/cell_with_separator.h new file mode 100644 index 000000000..b27eef9a7 --- /dev/null +++ b/apps/settings/cell_with_separator.h @@ -0,0 +1,25 @@ +#ifndef SETTINGS_CELL_WITH_SEPARATOR_H +#define SETTINGS_CELL_WITH_SEPARATOR_H + +#include + +namespace Settings { + +class CellWithSeparator : public HighlightCell { +public: + CellWithSeparator() {} + void setHighlighted(bool highlight) override; + void drawRect(KDContext * ctx, KDRect rect) const override; + void reloadCell() override { cell()->reloadCell(); } + Responder * responder() override { return cell()->responder(); } + constexpr static KDCoordinate k_margin = 10; +private: + int numberOfSubviews() const override; + View * subviewAtIndex(int index) override; + void layoutSubviews(bool force = false) override; + virtual HighlightCell * cell() = 0; +}; + +} + +#endif diff --git a/apps/settings/main_controller.cpp b/apps/settings/main_controller.cpp index 248a59a1b..3eded7a38 100644 --- a/apps/settings/main_controller.cpp +++ b/apps/settings/main_controller.cpp @@ -8,6 +8,13 @@ using namespace Poincare; namespace Settings { +constexpr SettingsMessageTree s_modelAngleChildren[3] = {SettingsMessageTree(I18n::Message::Degrees), SettingsMessageTree(I18n::Message::Radian), SettingsMessageTree(I18n::Message::Gradians)}; +constexpr SettingsMessageTree s_modelEditionModeChildren[2] = {SettingsMessageTree(I18n::Message::Edition2D), SettingsMessageTree(I18n::Message::EditionLinear)}; +constexpr SettingsMessageTree s_modelFloatDisplayModeChildren[4] = {SettingsMessageTree(I18n::Message::Decimal), SettingsMessageTree(I18n::Message::Scientific), SettingsMessageTree(I18n::Message::Engineering), SettingsMessageTree(I18n::Message::SignificantFigures)}; +constexpr SettingsMessageTree s_modelComplexFormatChildren[3] = {SettingsMessageTree(I18n::Message::Real), SettingsMessageTree(I18n::Message::Cartesian), SettingsMessageTree(I18n::Message::Polar)}; +constexpr SettingsMessageTree s_modelFontChildren[2] = {SettingsMessageTree(I18n::Message::LargeFont), SettingsMessageTree(I18n::Message::SmallFont)}; +constexpr SettingsMessageTree s_modelAboutChildren[3] = {SettingsMessageTree(I18n::Message::SoftwareVersion), SettingsMessageTree(I18n::Message::SerialNumber), SettingsMessageTree(I18n::Message::FccId)}; + MainController::MainController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate) : ViewController(parentResponder), m_brightnessCell(I18n::Message::Default, KDFont::LargeFont), @@ -74,11 +81,22 @@ bool MainController::handleEvent(Ion::Events::Event event) { } if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { GenericSubController * subController = nullptr; +<<<<<<< HEAD if (model()->children(selectedRow())->label() == I18n::Message::Brightness || model()->children(selectedRow())->label() == I18n::Message::Language) { assert(false); } else if (model()->children(selectedRow())->label() == I18n::Message::ExamMode) { subController = &m_examModeController; } else if (model()->children(selectedRow())->label() == I18n::Message::About) { +======= + int rowIndex = selectedRow(); + if (rowIndex == k_indexOfDisplayModeCell) { + subController = &m_displayModeController; + } else if (rowIndex == k_indexOfBrightnessCell || rowIndex == k_indexOfLanguageCell) { + assert(false); + } else if (rowIndex == k_indexOfExamModeCell) { + subController = &m_examModeController; + } else if (rowIndex == k_indexOfAboutCell + hasPrompt()) { +>>>>>>> upstream/master subController = &m_aboutController; } else if (model()->children(selectedRow())->label() == I18n::Message::Accessibility) { subController = &m_accessibilityController; @@ -100,15 +118,25 @@ int MainController::numberOfRows() const { }; KDCoordinate MainController::rowHeight(int j) { + if (j == k_indexOfBrightnessCell) { + return Metric::ParameterCellHeight + CellWithSeparator::k_margin; + } return Metric::ParameterCellHeight; } KDCoordinate MainController::cumulatedHeightFromIndex(int j) { - return j*rowHeight(0); + KDCoordinate height = j * rowHeight(0); + if (j > k_indexOfBrightnessCell) { + height += CellWithSeparator::k_margin; + } + return height; } int MainController::indexFromCumulatedHeight(KDCoordinate offsetY) { - return offsetY/rowHeight(0); + if (offsetY < rowHeight(0)*k_indexOfBrightnessCell + CellWithSeparator::k_margin) { + return offsetY/rowHeight(0); + } + return (offsetY - CellWithSeparator::k_margin)/rowHeight(0); } HighlightCell * MainController::reusableCell(int index, int type) { @@ -133,10 +161,17 @@ int MainController::reusableCellCount(int type) { } int MainController::typeAtLocation(int i, int j) { +<<<<<<< HEAD if (model()->children(j)->label() == I18n::Message::Brightness) { return 1; } if (model()->children(j)->label() == I18n::Message::UpdatePopUp || model()->children(j)->label() == I18n::Message::BetaPopUp) { +======= + if (j == k_indexOfBrightnessCell) { + return 1; + } + if (hasPrompt() && j == k_indexOfPopUpCell) { +>>>>>>> upstream/master return 2; } return 0; @@ -145,6 +180,7 @@ int MainController::typeAtLocation(int i, int j) { void MainController::willDisplayCellForIndex(HighlightCell * cell, int index) { GlobalPreferences * globalPreferences = GlobalPreferences::sharedGlobalPreferences(); Preferences * preferences = Preferences::sharedPreferences(); +<<<<<<< HEAD MessageTableCell * myCell = (MessageTableCell *)cell; I18n::Message thisLabel = model()->children(index)->label(); myCell->setMessage(thisLabel); @@ -152,33 +188,77 @@ void MainController::willDisplayCellForIndex(HighlightCell * cell, int index) { //switch to irregular cell types if (thisLabel == I18n::Message::Brightness) { MessageTableCellWithGauge * myGaugeCell = (MessageTableCellWithGauge *)cell; +======= + I18n::Message title = model()->children(index)->label(); + if (index == k_indexOfBrightnessCell) { + MessageTableCellWithGaugeWithSeparator * myGaugeCell = (MessageTableCellWithGaugeWithSeparator *)cell; + myGaugeCell->setMessage(title); +>>>>>>> upstream/master GaugeView * myGauge = (GaugeView *)myGaugeCell->accessoryView(); myGauge->setLevel((float)globalPreferences->brightnessLevel()/(float)Ion::Backlight::MaxBrightness); return; } +<<<<<<< HEAD if (thisLabel == I18n::Message::Language) { +======= + MessageTableCell * myCell = (MessageTableCell *)cell; + myCell->setMessage(title); + if (index == k_indexOfLanguageCell) { +>>>>>>> upstream/master int index = (int)globalPreferences->language()-1; static_cast(cell)->setSubtitle(I18n::LanguageNames[index]); return; } +<<<<<<< HEAD if (thisLabel == I18n::Message::PythonFont) { int childIndex = (int)preferences->pythonFont(); static_cast(cell)->setSubtitle(model()->children(index)->children(childIndex)->label()); return; } if (hasPrompt() && (thisLabel == I18n::Message::UpdatePopUp || thisLabel == I18n::Message::BetaPopUp)) { +======= + if (hasPrompt() && index == k_indexOfPopUpCell) { +>>>>>>> upstream/master MessageTableCellWithSwitch * mySwitchCell = (MessageTableCellWithSwitch *)cell; SwitchView * mySwitch = (SwitchView *)mySwitchCell->accessoryView(); mySwitch->setState(globalPreferences->showPopUp()); return; } +<<<<<<< HEAD static_cast(cell)->setSubtitle(I18n::Message::Default); +======= + MessageTableCellWithChevronAndMessage * myTextCell = (MessageTableCellWithChevronAndMessage *)cell; + int childIndex = -1; + switch (index) { + case k_indexOfAngleUnitCell: + childIndex = (int)preferences->angleUnit(); + break; + case k_indexOfDisplayModeCell: + childIndex = (int)preferences->displayMode(); + break; + case k_indexOfEditionModeCell: + childIndex = (int)preferences->editionMode(); + break; + case k_indexOfComplexFormatCell: + childIndex = (int)preferences->complexFormat(); + break; + case k_indexOfFontCell: + childIndex = GlobalPreferences::sharedGlobalPreferences()->font() == KDFont::LargeFont ? 0 : 1; + break; + } + I18n::Message message = childIndex >= 0 ? model()->children(index)->children(childIndex)->label() : I18n::Message::Default; + myTextCell->setSubtitle(message); +>>>>>>> upstream/master } void MainController::viewWillAppear() { m_selectableTableView.reloadData(); } +const SettingsMessageTree * MainController::model() { + return &s_model; +} + StackViewController * MainController::stackController() const { return (StackViewController *)parentResponder(); } diff --git a/apps/settings/main_controller.h b/apps/settings/main_controller.h index f19bf4314..53a1bb53d 100644 --- a/apps/settings/main_controller.h +++ b/apps/settings/main_controller.h @@ -3,6 +3,7 @@ #include #include "settings_message_tree.h" +#include "message_table_cell_with_gauge_with_separator.h" #include "sub_menu/about_controller.h" #include "sub_menu/accessibility_controller.h" #include "sub_menu/exam_mode_controller.h" @@ -12,6 +13,15 @@ namespace Settings { +extern const SettingsMessageTree s_modelAngleChildren[3]; +extern const SettingsMessageTree s_modelEditionModeChildren[2]; +extern const SettingsMessageTree s_modelFloatDisplayModeChildren[4]; +extern const SettingsMessageTree s_modelComplexFormatChildren[3]; +extern const SettingsMessageTree s_modelFontChildren[2]; +extern const SettingsMessageTree s_modelExamChildren[2]; +extern const SettingsMessageTree s_modelAboutChildren[3]; +extern const SettingsMessageTree s_model; + class MainController : public ViewController, public ListViewDataSource, public SelectableTableViewDataSource { public: MainController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate); @@ -27,6 +37,21 @@ public: int typeAtLocation(int i, int j) override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; void viewWillAppear() override; +private: + constexpr static int k_indexOfAngleUnitCell = 0; + constexpr static int k_indexOfDisplayModeCell = k_indexOfAngleUnitCell + 1; + constexpr static int k_indexOfEditionModeCell = k_indexOfDisplayModeCell + 1; + constexpr static int k_indexOfComplexFormatCell = k_indexOfEditionModeCell + 1; + constexpr static int k_indexOfBrightnessCell = k_indexOfComplexFormatCell + 1; + constexpr static int k_indexOfFontCell = k_indexOfBrightnessCell + 1; + constexpr static int k_indexOfLanguageCell = k_indexOfFontCell + 1; + constexpr static int k_indexOfExamModeCell = k_indexOfLanguageCell + 1; + /* Pop-up cell and About cell are located at the same index because pop-up + * cell is optional. We must always correct k_indexOfAboutCell with + * hasPrompt() (TODO: make hasPrompt() constexpr and correct + * k_indexOfAboutCell) */ + constexpr static int k_indexOfPopUpCell = k_indexOfExamModeCell + 1; + constexpr static int k_indexOfAboutCell = k_indexOfExamModeCell + 1; static const SettingsMessageTree * model(); private: StackViewController * stackController() const; @@ -34,7 +59,7 @@ private: bool hasPrompt() const { return promptMessage() != I18n::Message::Default; } constexpr static int k_numberOfSimpleChevronCells = 6; MessageTableCellWithChevronAndMessage m_cells[k_numberOfSimpleChevronCells]; - MessageTableCellWithGauge m_brightnessCell; + MessageTableCellWithGaugeWithSeparator m_brightnessCell; MessageTableCellWithSwitch m_popUpCell; SelectableTableView m_selectableTableView; MathOptionsController m_mathOptionsController; diff --git a/apps/settings/main_controller_prompt_beta.cpp b/apps/settings/main_controller_prompt_beta.cpp index a338ab80b..b3078ff1b 100644 --- a/apps/settings/main_controller_prompt_beta.cpp +++ b/apps/settings/main_controller_prompt_beta.cpp @@ -1,8 +1,10 @@ #include "main_controller.h" +#include "../exam_mode_configuration.h" #include namespace Settings { +<<<<<<< HEAD //sub-sub-menus constexpr SettingsMessageTree s_ledColorChildren[7] = {SettingsMessageTree(I18n::Message::ColorRed), SettingsMessageTree(I18n::Message::ColorWhite), SettingsMessageTree(I18n::Message::ColorGreen), SettingsMessageTree(I18n::Message::ColorBlue), SettingsMessageTree(I18n::Message::ColorYellow), SettingsMessageTree(I18n::Message::ColorPurple), SettingsMessageTree(I18n::Message::ColorOrange)}; constexpr SettingsMessageTree s_examModeMode[3] = {SettingsMessageTree(I18n::Message::ExamModeModeStandard), SettingsMessageTree(I18n::Message::ExamModeModeNoSym), SettingsMessageTree(I18n::Message::ExamModeModeDutch)}; @@ -26,8 +28,17 @@ constexpr SettingsMessageTree s_modelAboutChildren[7] = {SettingsMessageTree(I18 constexpr SettingsMessageTree s_modelMenu[] = {SettingsMessageTree(I18n::Message::MathOptions, s_modelMathOptionsChildren, 5), +======= +constexpr SettingsMessageTree s_modelMenu[] = + {SettingsMessageTree(I18n::Message::AngleUnit, s_modelAngleChildren), + SettingsMessageTree(I18n::Message::DisplayMode, s_modelFloatDisplayModeChildren), + SettingsMessageTree(I18n::Message::EditionMode, s_modelEditionModeChildren), + SettingsMessageTree(I18n::Message::ComplexFormat, s_modelComplexFormatChildren), +>>>>>>> upstream/master SettingsMessageTree(I18n::Message::Brightness), + SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Language), +<<<<<<< HEAD SettingsMessageTree(I18n::Message::ExamMode, s_modelExamChildren, 3), SettingsMessageTree(I18n::Message::PythonFont, s_pythonFont, 2), SettingsMessageTree(I18n::Message::BetaPopUp), @@ -39,13 +50,16 @@ constexpr SettingsMessageTree s_modelMenu[] = #endif constexpr SettingsMessageTree s_model = SettingsMessageTree(I18n::Message::SettingsApp, s_modelMenu, 7); +======= + SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), + SettingsMessageTree(I18n::Message::BetaPopUp), + SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; + +constexpr SettingsMessageTree s_model = SettingsMessageTree(I18n::Message::SettingsApp, s_modelMenu); +>>>>>>> upstream/master I18n::Message MainController::promptMessage() const { return I18n::Message::BetaPopUp; } -const SettingsMessageTree * MainController::model() { - return &s_model; -} - } diff --git a/apps/settings/main_controller_prompt_none.cpp b/apps/settings/main_controller_prompt_none.cpp index f99c69759..9453bc7d6 100644 --- a/apps/settings/main_controller_prompt_none.cpp +++ b/apps/settings/main_controller_prompt_none.cpp @@ -1,8 +1,10 @@ #include "main_controller.h" +#include "../exam_mode_configuration.h" #include namespace Settings { +<<<<<<< HEAD // TODO: factorize most parts of the final models with main_controller_prompt_beta and main_controller_prompt_update //sub-sub-menus constexpr SettingsMessageTree s_ledColorChildren[7] = {SettingsMessageTree(I18n::Message::ColorRed), SettingsMessageTree(I18n::Message::ColorWhite), SettingsMessageTree(I18n::Message::ColorGreen), SettingsMessageTree(I18n::Message::ColorBlue), SettingsMessageTree(I18n::Message::ColorYellow), SettingsMessageTree(I18n::Message::ColorPurple), SettingsMessageTree(I18n::Message::ColorOrange)}; @@ -27,8 +29,17 @@ constexpr SettingsMessageTree s_modelAboutChildren[7] = {SettingsMessageTree(I18 constexpr SettingsMessageTree s_modelMenu[] = {SettingsMessageTree(I18n::Message::MathOptions, s_modelMathOptionsChildren, 5), +======= +constexpr SettingsMessageTree s_modelMenu[] = + {SettingsMessageTree(I18n::Message::AngleUnit, s_modelAngleChildren), + SettingsMessageTree(I18n::Message::DisplayMode, s_modelFloatDisplayModeChildren), + SettingsMessageTree(I18n::Message::EditionMode, s_modelEditionModeChildren), + SettingsMessageTree(I18n::Message::ComplexFormat, s_modelComplexFormatChildren), +>>>>>>> upstream/master SettingsMessageTree(I18n::Message::Brightness), + SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Language), +<<<<<<< HEAD SettingsMessageTree(I18n::Message::ExamMode, s_modelExamChildren, 3), SettingsMessageTree(I18n::Message::PythonFont, s_pythonFont, 2), SettingsMessageTree(I18n::Message::Accessibility, s_accessibilityChildren, 6), @@ -39,13 +50,15 @@ constexpr SettingsMessageTree s_modelMenu[] = #endif constexpr SettingsMessageTree s_model = SettingsMessageTree(I18n::Message::SettingsApp, s_modelMenu, 7); +======= + SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), + SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; + +constexpr SettingsMessageTree s_model = SettingsMessageTree(I18n::Message::SettingsApp, s_modelMenu); +>>>>>>> upstream/master I18n::Message MainController::promptMessage() const { return I18n::Message::Default; } -const SettingsMessageTree * MainController::model() { - return &s_model; -} - } diff --git a/apps/settings/main_controller_prompt_update.cpp b/apps/settings/main_controller_prompt_update.cpp index 6e873ca4d..2b451220e 100644 --- a/apps/settings/main_controller_prompt_update.cpp +++ b/apps/settings/main_controller_prompt_update.cpp @@ -1,8 +1,10 @@ #include "main_controller.h" +#include "../exam_mode_configuration.h" #include namespace Settings { +<<<<<<< HEAD //sub-sub-menus constexpr SettingsMessageTree s_ledColorChildren[7] = {SettingsMessageTree(I18n::Message::ColorRed), SettingsMessageTree(I18n::Message::ColorWhite), SettingsMessageTree(I18n::Message::ColorGreen), SettingsMessageTree(I18n::Message::ColorBlue), SettingsMessageTree(I18n::Message::ColorYellow), SettingsMessageTree(I18n::Message::ColorPurple), SettingsMessageTree(I18n::Message::ColorOrange)}; constexpr SettingsMessageTree s_examModeMode[3] = {SettingsMessageTree(I18n::Message::ExamModeModeStandard), SettingsMessageTree(I18n::Message::ExamModeModeNoSym), SettingsMessageTree(I18n::Message::ExamModeModeDutch)}; @@ -26,8 +28,17 @@ constexpr SettingsMessageTree s_modelAboutChildren[7] = {SettingsMessageTree(I18 constexpr SettingsMessageTree s_modelMenu[] = {SettingsMessageTree(I18n::Message::MathOptions, s_modelMathOptionsChildren, 5), +======= +constexpr SettingsMessageTree s_modelMenu[] = + {SettingsMessageTree(I18n::Message::AngleUnit, s_modelAngleChildren), + SettingsMessageTree(I18n::Message::DisplayMode, s_modelFloatDisplayModeChildren), + SettingsMessageTree(I18n::Message::EditionMode, s_modelEditionModeChildren), + SettingsMessageTree(I18n::Message::ComplexFormat, s_modelComplexFormatChildren), +>>>>>>> upstream/master SettingsMessageTree(I18n::Message::Brightness), + SettingsMessageTree(I18n::Message::FontSizes, s_modelFontChildren), SettingsMessageTree(I18n::Message::Language), +<<<<<<< HEAD SettingsMessageTree(I18n::Message::ExamMode, s_modelExamChildren, 3), SettingsMessageTree(I18n::Message::PythonFont, s_pythonFont, 2), SettingsMessageTree(I18n::Message::UpdatePopUp), @@ -39,13 +50,16 @@ constexpr SettingsMessageTree s_modelMenu[] = #endif constexpr SettingsMessageTree s_model = SettingsMessageTree(I18n::Message::SettingsApp, s_modelMenu, 7); +======= + SettingsMessageTree(I18n::Message::ExamMode, ExamModeConfiguration::s_modelExamChildren), + SettingsMessageTree(I18n::Message::UpdatePopUp), + SettingsMessageTree(I18n::Message::About, s_modelAboutChildren)}; + +constexpr SettingsMessageTree s_model = SettingsMessageTree(I18n::Message::SettingsApp, s_modelMenu); +>>>>>>> upstream/master I18n::Message MainController::promptMessage() const { return I18n::Message::UpdatePopUp; } -const SettingsMessageTree * MainController::model() { - return &s_model; -} - } diff --git a/apps/settings/sub_menu/message_table_cell_with_editable_text_with_separator.h b/apps/settings/message_table_cell_with_editable_text_with_separator.h similarity index 51% rename from apps/settings/sub_menu/message_table_cell_with_editable_text_with_separator.h rename to apps/settings/message_table_cell_with_editable_text_with_separator.h index 148da96e5..fdc33df7a 100644 --- a/apps/settings/sub_menu/message_table_cell_with_editable_text_with_separator.h +++ b/apps/settings/message_table_cell_with_editable_text_with_separator.h @@ -1,26 +1,20 @@ #ifndef SETTINGS_MESSAGE_TABLE_CELL_WITH_EDITABLE_TEXT_WITH_SEPARATOR_H #define SETTINGS_MESSAGE_TABLE_CELL_WITH_EDITABLE_TEXT_WITH_SEPARATOR_H -#include +#include "cell_with_separator.h" namespace Settings { -class MessageTableCellWithEditableTextWithSeparator : public HighlightCell { +class MessageTableCellWithEditableTextWithSeparator : public CellWithSeparator { public: - MessageTableCellWithEditableTextWithSeparator(Responder * parentResponder = nullptr, InputEventHandlerDelegate * inputEventHandlerDelegate = nullptr, TextFieldDelegate * textFieldDelegate = nullptr, I18n::Message message = (I18n::Message)0); - void drawRect(KDContext * ctx, KDRect rect) const override; - void setHighlighted(bool highlight) override; - void reloadCell() override { m_cell.reloadCell(); } - Responder * responder() override { return m_cell.responder(); } + MessageTableCellWithEditableTextWithSeparator(Responder * parentResponder = nullptr, InputEventHandlerDelegate * inputEventHandlerDelegate = nullptr, TextFieldDelegate * textFieldDelegate = nullptr, I18n::Message message = (I18n::Message)0) : + CellWithSeparator(), + m_cell(parentResponder, inputEventHandlerDelegate, textFieldDelegate, message) {} const char * text() const override { return m_cell.text(); } Poincare::Layout layout() const override{ return m_cell.layout(); } MessageTableCellWithEditableText * messageTableCellWithEditableText() { return &m_cell; } - constexpr static KDCoordinate k_margin = 10; private: - constexpr static KDCoordinate k_separatorThickness = Metric::CellSeparatorThickness; - int numberOfSubviews() const override; - View * subviewAtIndex(int index) override; - void layoutSubviews() override; + HighlightCell * cell() override { return &m_cell; } MessageTableCellWithEditableText m_cell; }; diff --git a/apps/settings/message_table_cell_with_gauge_with_separator.h b/apps/settings/message_table_cell_with_gauge_with_separator.h new file mode 100644 index 000000000..fa1dd1210 --- /dev/null +++ b/apps/settings/message_table_cell_with_gauge_with_separator.h @@ -0,0 +1,22 @@ +#ifndef SETTINGS_MESSAGE_TABLE_WITH_GAUGE_WITH_SEPARATOR_H +#define SETTINGS_MESSAGE_TABLE_WITH_GAUGE_WITH_SEPARATOR_H + +#include "cell_with_separator.h" + +namespace Settings { + +class MessageTableCellWithGaugeWithSeparator : public CellWithSeparator { +public: + MessageTableCellWithGaugeWithSeparator(I18n::Message message, const KDFont * font) : + CellWithSeparator(), + m_cell(message, font) {} + View * accessoryView() const { return m_cell.accessoryView(); } + void setMessage(I18n::Message message) { return m_cell.setMessage(message); } +private: + HighlightCell * cell() override { return &m_cell; } + MessageTableCellWithGauge m_cell; +}; + +} + +#endif diff --git a/apps/settings/settings_message_tree.h b/apps/settings/settings_message_tree.h index 7d2df12cd..10d1b5205 100644 --- a/apps/settings/settings_message_tree.h +++ b/apps/settings/settings_message_tree.h @@ -7,8 +7,14 @@ namespace Settings { class SettingsMessageTree : public MessageTree { public: - constexpr SettingsMessageTree(I18n::Message label = I18n::Message::Default, const SettingsMessageTree * children = nullptr, int numberOfChildren = 0) : - MessageTree(label, numberOfChildren), + constexpr SettingsMessageTree(I18n::Message label = I18n::Message::Default) : + MessageTree(label, 0), + m_children(nullptr) + { + }; + template + constexpr SettingsMessageTree(I18n::Message label, const SettingsMessageTree (&children)[N] = nullptr) : + MessageTree(label, N), m_children(children) { }; diff --git a/apps/settings/sub_menu/about_controller.cpp b/apps/settings/sub_menu/about_controller.cpp index 71c8208bb..9013271f8 100644 --- a/apps/settings/sub_menu/about_controller.cpp +++ b/apps/settings/sub_menu/about_controller.cpp @@ -13,8 +13,12 @@ namespace Settings { AboutController::AboutController(Responder * parentResponder) : GenericSubController(parentResponder), +<<<<<<< HEAD m_contributorsController(this), m_contributorsCell(KDFont::LargeFont, KDFont::SmallFont) +======= + m_view(&m_selectableTableView) +>>>>>>> upstream/master { for (int i = 0; i < k_totalNumberOfCell; i++) { m_cells[i].setMessageFont(KDFont::LargeFont); diff --git a/apps/settings/sub_menu/about_controller.h b/apps/settings/sub_menu/about_controller.h index ea7734e60..8b5e78af3 100644 --- a/apps/settings/sub_menu/about_controller.h +++ b/apps/settings/sub_menu/about_controller.h @@ -2,6 +2,7 @@ #define SETTINGS_ABOUT_CONTROLLER_H #include "generic_sub_controller.h" +#include "selectable_view_with_messages.h" #include "../../hardware_test/pop_up_controller.h" #include "contributors_controller.h" @@ -10,6 +11,8 @@ namespace Settings { class AboutController : public GenericSubController { public: AboutController(Responder * parentResponder); + View * view() override { return &m_view; } + void viewWillAppear() override; bool handleEvent(Ion::Events::Event event) override; HighlightCell * reusableCell(int index, int type) override; int reusableCellCount(int type) override; @@ -21,8 +24,7 @@ private: #else constexpr static int k_totalNumberOfCell = 7; #endif - ContributorsController m_contributorsController; - MessageTableCellWithChevronAndMessage m_contributorsCell; + SelectableViewWithMessages m_view; MessageTableCellWithBuffer m_cells[k_totalNumberOfCell]; HardwareTest::PopUpController m_hardwareTestPopUpController; }; diff --git a/apps/settings/sub_menu/about_controller_non_official.cpp b/apps/settings/sub_menu/about_controller_non_official.cpp new file mode 100644 index 000000000..84995e4a4 --- /dev/null +++ b/apps/settings/sub_menu/about_controller_non_official.cpp @@ -0,0 +1,14 @@ +#include "about_controller.h" +#include "selectable_view_with_messages.h" + +namespace Settings { + +void AboutController::viewWillAppear() { + GenericSubController::viewWillAppear(); + // --------------------- Please don't edit these lines ---------------------- + I18n::Message cautionMessages[] = {I18n::Message::AboutWarning1, I18n::Message::AboutWarning2, I18n::Message::AboutWarning3, I18n::Message::AboutWarning4}; + m_view.setMessages(cautionMessages, sizeof(cautionMessages)/sizeof(I18n::Message)); + // -------------------------------------------------------------------------- +} + +} diff --git a/apps/settings/sub_menu/about_controller_official.cpp b/apps/settings/sub_menu/about_controller_official.cpp new file mode 100644 index 000000000..f46c96d2e --- /dev/null +++ b/apps/settings/sub_menu/about_controller_official.cpp @@ -0,0 +1,10 @@ +#include "about_controller.h" + +namespace Settings { + +void AboutController::viewWillAppear() { + GenericSubController::viewWillAppear(); + m_view.setMessages(nullptr, 0); +} + +} diff --git a/apps/settings/sub_menu/display_mode_controller.cpp b/apps/settings/sub_menu/display_mode_controller.cpp index 9af479fd7..7bb6e66b7 100644 --- a/apps/settings/sub_menu/display_mode_controller.cpp +++ b/apps/settings/sub_menu/display_mode_controller.cpp @@ -35,7 +35,7 @@ int DisplayModeController::indexFromCumulatedHeight(KDCoordinate offsetY) { } HighlightCell * DisplayModeController::reusableCell(int index, int type) { - if (type == 1) { + if (type == k_significantDigitsType) { assert(index == 0); return &m_editableCell; } @@ -43,19 +43,15 @@ HighlightCell * DisplayModeController::reusableCell(int index, int type) { } int DisplayModeController::reusableCellCount(int type) { - switch (type) { - case 0: - return PreferencesController::k_totalNumberOfCell; - case 1: - return 1; - default: - assert(false); - return 0; + if (type == k_resultFormatType) { + return PreferencesController::k_totalNumberOfCell; } + assert(type == k_significantDigitsType); + return 1; } int DisplayModeController::typeAtLocation(int i, int j) { - return (j == numberOfRows() - 1 ? 1 : 0); + return (j == numberOfRows() - 1 ? k_significantDigitsType : k_resultFormatType); } void DisplayModeController::willDisplayCellForIndex(HighlightCell * cell, int index) { @@ -63,8 +59,9 @@ void DisplayModeController::willDisplayCellForIndex(HighlightCell * cell, int in if (index == numberOfRows()-1) { MessageTableCellWithEditableTextWithSeparator * myCell = (MessageTableCellWithEditableTextWithSeparator *)cell; GenericSubController::willDisplayCellForIndex(myCell->messageTableCellWithEditableText(), index); - char buffer[3]; - Integer(Preferences::sharedPreferences()->numberOfSignificantDigits()).serialize(buffer, 3); + constexpr int bufferSize = 3; + char buffer[bufferSize]; + Integer(Preferences::sharedPreferences()->numberOfSignificantDigits()).serialize(buffer, bufferSize); myCell->messageTableCellWithEditableText()->setAccessoryText(buffer); return; } @@ -81,11 +78,11 @@ bool DisplayModeController::textFieldDidFinishEditing(TextField * textField, con if (textFieldDelegateApp()->hasUndefinedValue(text, floatBody)) { return false; } - if (floatBody < 1) { - floatBody = 1; + if (floatBody < 1.0) { + floatBody = 1.0; } - if (Preferences::sharedPreferences()->displayMode() == Preferences::PrintFloatMode::Engineering && floatBody < 3) { - floatBody = 3; + if (Preferences::sharedPreferences()->displayMode() == Preferences::PrintFloatMode::Engineering && floatBody < 3.0) { + floatBody = 3.0; } if (floatBody > PrintFloat::k_numberOfStoredSignificantDigits) { floatBody = PrintFloat::k_numberOfStoredSignificantDigits; diff --git a/apps/settings/sub_menu/display_mode_controller.h b/apps/settings/sub_menu/display_mode_controller.h index fc0a8ef6d..343fcaef6 100644 --- a/apps/settings/sub_menu/display_mode_controller.h +++ b/apps/settings/sub_menu/display_mode_controller.h @@ -2,7 +2,7 @@ #define SETTINGS_DISPLAY_MODE_CONTROLLER_H #include "preferences_controller.h" -#include "message_table_cell_with_editable_text_with_separator.h" +#include "../message_table_cell_with_editable_text_with_separator.h" #include "../../shared/parameter_text_field_delegate.h" namespace Settings { @@ -20,6 +20,8 @@ public: bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; private: + static constexpr int k_resultFormatType = 0; + static constexpr int k_significantDigitsType = 1; MessageTableCellWithEditableTextWithSeparator m_editableCell; }; diff --git a/apps/settings/sub_menu/exam_mode_controller.cpp b/apps/settings/sub_menu/exam_mode_controller.cpp index 3e94bd868..121566e6c 100644 --- a/apps/settings/sub_menu/exam_mode_controller.cpp +++ b/apps/settings/sub_menu/exam_mode_controller.cpp @@ -1,6 +1,7 @@ #include "exam_mode_controller.h" #include "../../global_preferences.h" #include "../../apps_container.h" +#include "../../exam_mode_configuration.h" #include #include #include @@ -14,40 +15,45 @@ namespace Settings { ExamModeController::ExamModeController(Responder * parentResponder) : GenericSubController(parentResponder), - m_preferencesController(this), - m_examModeCell(I18n::Message::Default, KDFont::LargeFont), - m_ledCell(KDFont::LargeFont, KDFont::SmallFont), - m_modeCell(KDFont::LargeFont, KDFont::SmallFont) + m_contentView(&m_selectableTableView), + m_cell{} { + for (int i = 0; i < k_maxNumberOfCells; i++) { + m_cell[i].setMessage(ExamModeConfiguration::examModeActivationMessage(i)); + m_cell[i].setMessageFont(KDFont::LargeFont); + } } bool ExamModeController::handleEvent(Ion::Events::Event event) { -I18n::Message childLabel = m_messageTreeModel->children(selectedRow())->label(); - if (event == Ion::Events::OK || event == Ion::Events::EXE || event == Ion::Events::Right) { - if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { - // If the exam mode is already on, this re-activate the same exam mode - GlobalPreferences::ExamMode mode = GlobalPreferences::sharedGlobalPreferences()->examMode(); - if (childLabel == I18n::Message::ActivateExamMode || childLabel == I18n::Message::ExamModeActive) - AppsContainer::sharedAppsContainer()->displayExamModePopUp(mode); - return true; - } - - if (childLabel == I18n::Message::ActivateExamMode || childLabel == I18n::Message::ExamModeActive) { - GlobalPreferences::ExamMode mode = GlobalPreferences::ExamMode::Standard; - AppsContainer::sharedAppsContainer()->displayExamModePopUp(GlobalPreferences::sharedGlobalPreferences()->tempExamMode()); - return true; - } - if (childLabel == I18n::Message::LEDColor || childLabel == I18n::Message::ExamModeMode) { - GenericSubController * subController = &m_preferencesController; - subController->setMessageTreeModel(m_messageTreeModel->children(selectedRow())); - StackViewController * stack = stackController(); - stack->push(subController); - return true; - } + if (event == Ion::Events::OK || event == Ion::Events::EXE) { + AppsContainer::sharedAppsContainer()->displayExamModePopUp(examMode()); + return true; } return GenericSubController::handleEvent(event); } +void ExamModeController::didEnterResponderChain(Responder * previousFirstResponder) { + /* When a pop-up is dismissed, the exam mode status might have changed. We + * reload the selection as the number of rows might have also changed. We + * force to reload the entire data because they might have changed. */ + selectCellAtLocation(0, initialSelectedRow()); + m_contentView.reload(); + // We add a message when the mode exam is on + if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { + I18n::Message deactivateMessages[] = {I18n::Message::ToDeactivateExamMode1, I18n::Message::ToDeactivateExamMode2, I18n::Message::ToDeactivateExamMode3}; + m_contentView.setMessages(deactivateMessages, k_numberOfDeactivationMessageLines); + // --------------------- Please don't edit these lines ---------------------- + } else if (numberOfCautionLines() > 0) { + I18n::Message cautionMessages[] = {I18n::Message::ExamModeWarning1, I18n::Message::ExamModeWarning2, I18n::Message::ExamModeWarning3}; + m_contentView.setMessages(cautionMessages, numberOfCautionLines()); + } + // -------------------------------------------------------------------------- +} + +int ExamModeController::numberOfRows() const { + return ExamModeConfiguration::numberOfAvailableExamMode(); +} + HighlightCell * ExamModeController::reusableCell(int index, int type) { assert(type == 0); assert(index >= 0 && index < 3); @@ -91,4 +97,23 @@ void ExamModeController::willDisplayCellForIndex(HighlightCell * cell, int index } } +int ExamModeController::initialSelectedRow() const { + int row = selectedRow(); + if (row >= numberOfRows()) { + return numberOfRows()-1; + } else if (row < 0) { + return 0; + } + return row; +} + +GlobalPreferences::ExamMode ExamModeController::examMode() { + GlobalPreferences::ExamMode mode = ExamModeConfiguration::examModeAtIndex(selectedRow()); + if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { + // If the exam mode is already on, this re-activate the same exam mode + mode = GlobalPreferences::sharedGlobalPreferences()->examMode(); + } + return mode; +} + } diff --git a/apps/settings/sub_menu/exam_mode_controller.h b/apps/settings/sub_menu/exam_mode_controller.h index e2e7bb505..db1e423ef 100644 --- a/apps/settings/sub_menu/exam_mode_controller.h +++ b/apps/settings/sub_menu/exam_mode_controller.h @@ -2,22 +2,30 @@ #define SETTINGS_EXAM_MODE_CONTROLLER_H #include "generic_sub_controller.h" -#include "preferences_controller.h" +#include "selectable_view_with_messages.h" +#include "../../global_preferences.h" namespace Settings { class ExamModeController : public GenericSubController { public: ExamModeController(Responder * parentResponder); + View * view() override { return &m_contentView; } bool handleEvent(Ion::Events::Event event) override; + void didEnterResponderChain(Responder * previousFirstResponder) override; + int numberOfRows() const override; HighlightCell * reusableCell(int index, int type) override; int reusableCellCount(int type) override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; private: - MessageTableCell m_examModeCell; - MessageTableCellWithChevronAndMessage m_ledCell; - MessageTableCellWithChevronAndMessage m_modeCell; - PreferencesController m_preferencesController; + static constexpr int k_numberOfDeactivationMessageLines = 3; + static constexpr int k_numberOfCautionMessageLines = 3; + int numberOfCautionLines() const; + int initialSelectedRow() const override; + GlobalPreferences::ExamMode examMode(); + static constexpr int k_maxNumberOfCells = 2; + SelectableViewWithMessages m_contentView; + MessageTableCell m_cell[k_maxNumberOfCells]; }; } diff --git a/apps/settings/sub_menu/exam_mode_controller_non_official.cpp b/apps/settings/sub_menu/exam_mode_controller_non_official.cpp new file mode 100644 index 000000000..51e0bb877 --- /dev/null +++ b/apps/settings/sub_menu/exam_mode_controller_non_official.cpp @@ -0,0 +1,12 @@ +#include "exam_mode_controller.h" + +using namespace Poincare; +using namespace Shared; + +namespace Settings { + +int ExamModeController::numberOfCautionLines() const { + return k_numberOfCautionMessageLines; +} + +} diff --git a/apps/settings/sub_menu/exam_mode_controller_official.cpp b/apps/settings/sub_menu/exam_mode_controller_official.cpp new file mode 100644 index 000000000..7b627c181 --- /dev/null +++ b/apps/settings/sub_menu/exam_mode_controller_official.cpp @@ -0,0 +1,12 @@ +#include "exam_mode_controller.h" + +using namespace Poincare; +using namespace Shared; + +namespace Settings { + +int ExamModeController::numberOfCautionLines() const { + return 0; +} + +} diff --git a/apps/settings/sub_menu/generic_sub_controller.cpp b/apps/settings/sub_menu/generic_sub_controller.cpp index 67af6c918..e1871cc3d 100644 --- a/apps/settings/sub_menu/generic_sub_controller.cpp +++ b/apps/settings/sub_menu/generic_sub_controller.cpp @@ -22,19 +22,22 @@ const char * GenericSubController::title() { return ""; } -View * GenericSubController::view() { - return &m_selectableTableView; -} - -void GenericSubController::didEnterResponderChain(Responder * previousFirstResponder) { - selectCellAtLocation(0, initialSelectedRow()); - m_selectableTableView.reloadData(); -} - void GenericSubController::didBecomeFirstResponder() { Container::activeApp()->setFirstResponder(&m_selectableTableView); } +void GenericSubController::viewWillAppear() { + /* This can't be done in didEnterResponderChain because we don't want it to + * be done everytime the pop-up disappears. For example, if we are editing a + * field and a pop-up shows up with a warning, we don't want to reload the + * entire table when dismissing the pop-up (that would erase the edition). */ + selectCellAtLocation(0, initialSelectedRow()); + /* A unique SubController is used for all sub pages of settings. We have to + * reload its data when it is displayed as it could switch from displaying + * "Angle unit" data to "Complex format" data for instance. */ + m_selectableTableView.reloadData(); +} + bool GenericSubController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::Left) { stackController()->pop(); diff --git a/apps/settings/sub_menu/generic_sub_controller.h b/apps/settings/sub_menu/generic_sub_controller.h index bcb700779..545a79c80 100644 --- a/apps/settings/sub_menu/generic_sub_controller.h +++ b/apps/settings/sub_menu/generic_sub_controller.h @@ -10,9 +10,9 @@ class GenericSubController : public ViewController, public ListViewDataSource, p public: GenericSubController(Responder * parentResponder); const char * title() override; - View * view() override; - void didEnterResponderChain(Responder * previousFirstResponder) override; + View * view() override { return &m_selectableTableView; } void didBecomeFirstResponder() override; + void viewWillAppear() override; bool handleEvent(Ion::Events::Event event) override; int numberOfRows() const override; KDCoordinate rowHeight(int j) override; diff --git a/apps/settings/sub_menu/preferences_controller.cpp b/apps/settings/sub_menu/preferences_controller.cpp index 5f1a82252..503d4ade7 100644 --- a/apps/settings/sub_menu/preferences_controller.cpp +++ b/apps/settings/sub_menu/preferences_controller.cpp @@ -183,6 +183,14 @@ Layout PreferencesController::layoutForPreferences(I18n::Message message) { return LayoutHelper::String("000", 3, KDFont::LargeFont); case I18n::Message::Small: return LayoutHelper::String("000", 3, KDFont::SmallFont); + // Font size + case I18n::Message::LargeFont: + case I18n::Message::SmallFont: + { + const char * text = "abc"; + const KDFont * font = message == I18n::Message::LargeFont ? KDFont::LargeFont : KDFont::SmallFont; + return LayoutHelper::String(text, strlen(text), font); + } default: assert(false); @@ -219,6 +227,7 @@ void PreferencesController::setPreferenceWithValueIndex(I18n::Message message, i preferences->setEditionMode((Preferences::EditionMode)valueIndex); } else if (message == I18n::Message::ComplexFormat) { preferences->setComplexFormat((Preferences::ComplexFormat)valueIndex); +<<<<<<< HEAD } else if (message == I18n::Message::LEDColor) { preferences->setColorOfLED((Preferences::LEDColor)valueIndex); } else if (message == I18n::Message::ExamModeMode) { @@ -227,7 +236,12 @@ void PreferencesController::setPreferenceWithValueIndex(I18n::Message message, i preferences->setSymbolMultiplication((Preferences::SymbolMultiplication)valueIndex); } else if (message == I18n::Message::PythonFont) { preferences->setPythonFont((Preferences::PythonFont)valueIndex); +======= + } else if (message == I18n::Message::FontSizes) { + GlobalPreferences::sharedGlobalPreferences()->setFont(valueIndex == 0 ? KDFont::LargeFont : KDFont::SmallFont); +>>>>>>> upstream/master } + } int PreferencesController::valueIndexForPreference(I18n::Message message) const { @@ -244,6 +258,7 @@ int PreferencesController::valueIndexForPreference(I18n::Message message) const if (message == I18n::Message::ComplexFormat) { return (int)preferences->complexFormat(); } +<<<<<<< HEAD if (message == I18n::Message::LEDColor) { return (int)preferences->colorOfLED(); } @@ -252,6 +267,10 @@ int PreferencesController::valueIndexForPreference(I18n::Message message) const } if (message == I18n::Message::PythonFont) { return (int)preferences->pythonFont(); +======= + if (message == I18n::Message::FontSizes) { + return GlobalPreferences::sharedGlobalPreferences()->font() == KDFont::LargeFont ? 0 : 1; +>>>>>>> upstream/master } return 0; } diff --git a/apps/settings/sub_menu/preferences_controller.h b/apps/settings/sub_menu/preferences_controller.h index 51152af12..a58d477b1 100644 --- a/apps/settings/sub_menu/preferences_controller.h +++ b/apps/settings/sub_menu/preferences_controller.h @@ -16,9 +16,9 @@ public: KDCoordinate rowHeight(int j) override; protected: constexpr static int k_totalNumberOfCell = 7; + int initialSelectedRow() const override { return valueIndexForPreference(m_messageTreeModel->label()); } private: constexpr static const KDFont * k_layoutFont = KDFont::SmallFont; - int initialSelectedRow() const override { return valueIndexForPreference(m_messageTreeModel->label()); } Poincare::Layout layoutForPreferences(I18n::Message message); void setPreferenceWithValueIndex(I18n::Message message, int valueIndex); int valueIndexForPreference(I18n::Message message) const; diff --git a/apps/settings/sub_menu/selectable_view_with_messages.cpp b/apps/settings/sub_menu/selectable_view_with_messages.cpp new file mode 100644 index 000000000..a42248873 --- /dev/null +++ b/apps/settings/sub_menu/selectable_view_with_messages.cpp @@ -0,0 +1,66 @@ +#include "selectable_view_with_messages.h" +#include +#include + +using namespace Shared; + +namespace Settings { + +static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { return x > y ? x : y; } + +SelectableViewWithMessages::SelectableViewWithMessages(SelectableTableView * selectableTableView) : + m_selectableTableView(selectableTableView), + m_numberOfMessages(0) +{ + for (int i = 0; i < k_maxNumberOfLines; i++) { + m_messageLines[i].setFont(KDFont::SmallFont); + m_messageLines[i].setAlignment(0.5f, 0.5f); + m_messageLines[i].setBackgroundColor(Palette::WallScreen); + } +} + +void SelectableViewWithMessages::drawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(bounds(), Palette::WallScreen); +} + +void SelectableViewWithMessages::setMessages(I18n::Message * m, int numberOfMessages) { + assert(numberOfMessages <= k_maxNumberOfLines); + m_numberOfMessages = numberOfMessages; + for (int i = 0; i < m_numberOfMessages; i++) { + m_messageLines[i].setMessage(m[i]); + } + layoutSubviews(); +} + +void SelectableViewWithMessages::reload() { + m_selectableTableView->reloadData(); + layoutSubviews(); +} + +View * SelectableViewWithMessages::subviewAtIndex(int index) { + assert(index >= 0 && index < numberOfSubviews()); + if (index == 0) { + return m_selectableTableView; + } + return &m_messageLines[index-1]; +} + +void SelectableViewWithMessages::layoutSubviews(bool force) { + // Layout the table view + KDCoordinate tableHeight = m_selectableTableView->minimalSizeForOptimalDisplay().height(); + m_selectableTableView->setFrame(KDRect(0, 0, bounds().width(), tableHeight), force); + + // Layout the text + KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height(); + KDCoordinate defOrigin = maxCoordinate(bounds().height() - Metric::CommonBottomMargin - m_numberOfMessages*textHeight, tableHeight); + + for (int i = 0; i < m_numberOfMessages; i++) { + m_messageLines[i].setFrame(KDRect(0, defOrigin, bounds().width(), textHeight), force); + defOrigin += textHeight; + } + for (int i = m_numberOfMessages; i < k_maxNumberOfLines; i++) { + m_messageLines[i].setFrame(KDRectZero, force); + } +} + +} diff --git a/apps/settings/sub_menu/selectable_view_with_messages.h b/apps/settings/sub_menu/selectable_view_with_messages.h new file mode 100644 index 000000000..afaeb6e46 --- /dev/null +++ b/apps/settings/sub_menu/selectable_view_with_messages.h @@ -0,0 +1,27 @@ +#ifndef SETTINGS_SELECTABLE_VIEW_WITH_MESSAGES_H +#define SETTINGS_SELECTABLE_VIEW_WITH_MESSAGES_H + +#include +#include + +namespace Settings { + +class SelectableViewWithMessages : public View { +public: + SelectableViewWithMessages(SelectableTableView * selectableTableView); + void drawRect(KDContext * ctx, KDRect rect) const override; + void setMessages(I18n::Message * messages, int numberOfMessages); + void reload(); +private: + int numberOfSubviews() const override { return 1 + m_numberOfMessages; } + View * subviewAtIndex(int index) override; + void layoutSubviews(bool force = false) override; + SelectableTableView * m_selectableTableView; + static constexpr int k_maxNumberOfLines = 4; + MessageTextView m_messageLines[k_maxNumberOfLines]; + int m_numberOfMessages; +}; + +} + +#endif diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index 7898bea3f..2d980f283 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -1,3 +1,79 @@ +UnitTimeSecondSymbol = "_s" +UnitTimeSecondMilliSymbol = "_ms" +UnitTimeSecondMicroSymbol = "_μs" +UnitTimeSecondNanoSymbol = "_ns" +UnitTimeMinuteSymbol = "_min" +UnitTimeHourSymbol = "_h" +UnitTimeDaySymbol = "_day" +UnitTimeWeekSymbol = "_week" +UnitTimeMonthSymbol = "_month" +UnitTimeYearSymbol = "_year" +UnitDistanceMeterKiloSymbol = "_km" +UnitDistanceMeterSymbol = "_m" +UnitDistanceMeterMilliSymbol = "_mm" +UnitDistanceMeterMicroSymbol = "_μm" +UnitDistanceMeterNanoSymbol = "_nm" +UnitDistanceMeterPicoSymbol = "_pm" +UnitDistanceAstronomicalUnitSymbol = "_au" +UnitDistanceLightYearSymbol = "_ly" +UnitDistanceParsecSymbol = "_pc" +UnitMassGramKiloSymbol = "_kg" +UnitMassGramSymbol = "_g" +UnitMassGramMilliSymbol = "_mg" +UnitMassGramMicroSymbol = "_μg" +UnitMassGramNanoSymbol = "_ng" +UnitMassTonneSymbol = "_t" +UnitCurrentAmpereSymbol = "_A" +UnitCurrentAmpereMilliSymbol = "_mA" +UnitCurrentAmpereMicroSymbol = "_μA" +UnitTemperatureKelvinSymbol = "_K" +UnitAmountMoleSymbol = "_mol" +UnitAmountMoleMilliSymbol = "_mmol" +UnitAmountMoleMicroSymbol = "_μmol" +UnitLuminousIntensityCandelaSymbol = "_cd" +UnitFrequencyHertzGigaSymbol = "_GHz" +UnitFrequencyHertzMegaSymbol = "_MHz" +UnitFrequencyHertzKiloSymbol = "_kHz" +UnitFrequencyHertzSymbol = "_Hz" +UnitForceNewtonKiloSymbol = "_kN" +UnitForceNewtonSymbol = "_N" +UnitForceNewtonMilliSymbol = "_mN" +UnitPressurePascalSymbol = "_Pa" +UnitPressurePascalHectoSymbol = "_hPa" +UnitPressureBarSymbol = "_bar" +UnitPressureAtmSymbol = "_atm" +UnitEnergyJouleKiloSymbol = "_kJ" +UnitEnergyJouleSymbol = "_J" +UnitEnergyJouleMilliSymbol = "_mJ" +UnitEnergyElectronVoltMegaSymbol = "_MeV" +UnitEnergyElectronVoltKiloSymbol = "_keV" +UnitEnergyElectronVoltSymbol = "_eV" +UnitEnergyElectronVoltMilliSymbol = "_meV" +UnitPowerWattGigaSymbol = "_GW" +UnitPowerWattMegaSymbol = "_MW" +UnitPowerWattKiloSymbol = "_kW" +UnitPowerWattSymbol = "_W" +UnitPowerWattMilliSymbol = "_mW" +UnitPowerWattMicroSymbol = "_μW" +UnitChargeCoulombSymbol = "_C" +UnitPotentialVoltKiloSymbol = "_kV" +UnitPotentialVoltSymbol = "_V" +UnitPotentialVoltMilliSymbol = "_mV" +UnitPotentialVoltMicroSymbol = "_μV" +UnitCapacitanceFaradSymbol = "_F" +UnitCapacitanceFaradMilliSymbol = "_mF" +UnitCapacitanceFaradMicroSymbol = "_μF" +UnitResistanceOhmKiloSymbol = "_kΩ" +UnitResistanceOhmSymbol = "_Ω" +UnitConductanceSiemensSymbol = "_S" +UnitConductanceSiemensMilliSymbol = "_mS" +UnitMagneticFieldTeslaSymbol = "_T" +UnitInductanceHenrySymbol = "_H" +UnitSurfaceHectarSymbol = "_ha" +UnitVolumeLiterSymbol = "_L" +UnitVolumeLiterDeciSymbol = "_dL" +UnitVolumeLiterCentiSymbol = "_cL" +UnitVolumeLiterMilliSymbol = "_mL" A = "a" AbsCommandWithArg = "abs(x)" AcoshCommandWithArg = "acosh(x)" diff --git a/apps/shared/Makefile b/apps/shared/Makefile index 2c4f2a951..e97b68ed0 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -11,7 +11,6 @@ app_shared_test_src = $(addprefix apps/shared/,\ interactive_curve_view_range.cpp \ memoized_curve_view_range.cpp \ range_1D.cpp \ - store_context.cpp \ ) app_shared_src = $(addprefix apps/shared/,\ @@ -19,6 +18,7 @@ app_shared_src = $(addprefix apps/shared/,\ buffer_function_title_cell.cpp \ buffer_text_view_with_text_field.cpp \ button_with_separator.cpp \ + dots.cpp \ cursor_view.cpp \ curve_view.cpp \ curve_view_cursor.cpp \ @@ -55,8 +55,8 @@ app_shared_src = $(addprefix apps/shared/,\ range_parameter_controller.cpp \ regular_table_view_data_source.cpp \ round_cursor_view.cpp \ - scrollable_exact_approximate_expressions_cell.cpp \ - scrollable_exact_approximate_expressions_view.cpp \ + scrollable_multiple_expressions_view.cpp \ + scrollable_two_expressions_cell.cpp \ separable.cpp \ separator_even_odd_buffer_text_cell.cpp \ simple_interactive_curve_view_controller.cpp \ diff --git a/apps/shared/banner_view.cpp b/apps/shared/banner_view.cpp index bd556f6f8..40d89738f 100644 --- a/apps/shared/banner_view.cpp +++ b/apps/shared/banner_view.cpp @@ -17,10 +17,14 @@ void BannerView::drawRect(KDContext * ctx, KDRect rect) const { } KDSize BannerView::minimalSizeForOptimalDisplay() const { - return KDSize(0, HeightGivenNumberOfLines(numberOfLines())); + return KDSize(Ion::Display::Width, minimalHeightForOptimalDisplayGivenWidth(Ion::Display::Width)); } -void BannerView::layoutSubviews() { +KDCoordinate BannerView::minimalHeightForOptimalDisplayGivenWidth(KDCoordinate width) const { + return HeightGivenNumberOfLines(numberOfLinesGivenWidth(width)); +} + +void BannerView::layoutSubviews(bool force) { if (m_frame.isEmpty()) { /* If the frame has not been set yet, there is no point in layouting the * subviews. @@ -48,7 +52,7 @@ void BannerView::layoutSubviews() { subviewPreviousLine = subviewAtIndex(j); KDCoordinate width = subviewPreviousLine->minimalSizeForOptimalDisplay().width() + remainingWidth/nbOfSubviewsOnLine + (j == i-1) * roundingError; KDCoordinate height = subviewPreviousLine->minimalSizeForOptimalDisplay().height(); - subviewPreviousLine->setFrame(KDRect(x, y, width, height)); + subviewPreviousLine->setFrame(KDRect(x, y, width, height), force); x += width; } // Next line @@ -61,9 +65,9 @@ void BannerView::layoutSubviews() { } } -int BannerView::numberOfLines() const { +int BannerView::numberOfLinesGivenWidth(KDCoordinate width) const { int lineNumber = 1; - const KDCoordinate lineWidth = m_frame.width(); + const KDCoordinate lineWidth = width; KDCoordinate remainingWidth = lineWidth; for (int i = 0; i < numberOfSubviews(); i++) { KDCoordinate subviewWidth = const_cast(this)->subviewAtIndex(i)->minimalSizeForOptimalDisplay().width(); diff --git a/apps/shared/banner_view.h b/apps/shared/banner_view.h index a98afded3..b1ad6e5fc 100644 --- a/apps/shared/banner_view.h +++ b/apps/shared/banner_view.h @@ -10,6 +10,7 @@ public: static KDCoordinate HeightGivenNumberOfLines(int linesCount); void drawRect(KDContext * ctx, KDRect rect) const override; KDSize minimalSizeForOptimalDisplay() const override; + KDCoordinate minimalHeightForOptimalDisplayGivenWidth(KDCoordinate width) const; void reload() { layoutSubviews(); } static constexpr const KDFont * Font() { return KDFont::SmallFont; } static constexpr KDColor TextColor() { return Palette::PrimaryText; } @@ -18,8 +19,8 @@ private: static constexpr KDCoordinate LineSpacing = 2; int numberOfSubviews() const override = 0; View * subviewAtIndex(int index) override = 0; - void layoutSubviews() override; - int numberOfLines() const; + void layoutSubviews(bool force = false) override; + int numberOfLinesGivenWidth(KDCoordinate width) const; }; } diff --git a/apps/shared/buffer_function_title_cell.cpp b/apps/shared/buffer_function_title_cell.cpp index ef9b53e8d..aaf56cbc9 100644 --- a/apps/shared/buffer_function_title_cell.cpp +++ b/apps/shared/buffer_function_title_cell.cpp @@ -37,8 +37,8 @@ View * BufferFunctionTitleCell::subviewAtIndex(int index) { return &m_bufferTextView; } -void BufferFunctionTitleCell::layoutSubviews() { - m_bufferTextView.setFrame(bufferTextViewFrame()); +void BufferFunctionTitleCell::layoutSubviews(bool force) { + m_bufferTextView.setFrame(bufferTextViewFrame(), force); } KDRect BufferFunctionTitleCell::bufferTextViewFrame() const { diff --git a/apps/shared/buffer_function_title_cell.h b/apps/shared/buffer_function_title_cell.h index 1ee507ade..6d59c14af 100644 --- a/apps/shared/buffer_function_title_cell.h +++ b/apps/shared/buffer_function_title_cell.h @@ -20,7 +20,7 @@ public: } int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; protected: KDRect bufferTextViewFrame() const; EvenOddBufferTextCell * bufferTextView() { return &m_bufferTextView; } diff --git a/apps/shared/buffer_text_view_with_text_field.cpp b/apps/shared/buffer_text_view_with_text_field.cpp index f4473505e..e6740cb2f 100644 --- a/apps/shared/buffer_text_view_with_text_field.cpp +++ b/apps/shared/buffer_text_view_with_text_field.cpp @@ -50,9 +50,9 @@ View * BufferTextViewWithTextField::subviewAtIndex(int index) { return views[index]; } -void BufferTextViewWithTextField::layoutSubviews() { - m_bufferTextView.setFrame(KDRect(Metric::TitleBarExternHorizontalMargin, 0, k_bufferTextWidth, bounds().height())); - m_textField.setFrame(textFieldFrame()); +void BufferTextViewWithTextField::layoutSubviews(bool force) { + m_bufferTextView.setFrame(KDRect(Metric::TitleBarExternHorizontalMargin, 0, k_bufferTextWidth, bounds().height()), force); + m_textField.setFrame(textFieldFrame(), force); } KDRect BufferTextViewWithTextField::textFieldFrame() const { diff --git a/apps/shared/buffer_text_view_with_text_field.h b/apps/shared/buffer_text_view_with_text_field.h index f3b207168..64dda0e8d 100644 --- a/apps/shared/buffer_text_view_with_text_field.h +++ b/apps/shared/buffer_text_view_with_text_field.h @@ -20,7 +20,7 @@ private: constexpr static KDCoordinate k_borderWidth = 1; int numberOfSubviews() const override { return 2; } View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; KDRect textFieldFrame() const; BufferTextView m_bufferTextView; TextField m_textField; diff --git a/apps/shared/button_with_separator.cpp b/apps/shared/button_with_separator.cpp index e11717828..3b3d1b69f 100644 --- a/apps/shared/button_with_separator.cpp +++ b/apps/shared/button_with_separator.cpp @@ -20,8 +20,8 @@ void ButtonWithSeparator::drawRect(KDContext * ctx, KDRect rect) const { } -void ButtonWithSeparator::layoutSubviews() { +void ButtonWithSeparator::layoutSubviews(bool force) { KDCoordinate width = bounds().width(); KDCoordinate height = bounds().height(); - m_messageTextView.setFrame(KDRect(k_lineThickness, k_margin + k_lineThickness, width-2*k_lineThickness, height - 4*k_lineThickness-k_margin)); + m_messageTextView.setFrame(KDRect(k_lineThickness, k_margin + k_lineThickness, width-2*k_lineThickness, height - 4*k_lineThickness-k_margin), force); } diff --git a/apps/shared/button_with_separator.h b/apps/shared/button_with_separator.h index e6337b9bb..7f2685902 100644 --- a/apps/shared/button_with_separator.h +++ b/apps/shared/button_with_separator.h @@ -10,7 +10,7 @@ public: private: constexpr static KDCoordinate k_margin = 5; constexpr static KDCoordinate k_lineThickness = 1; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; }; #endif diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index 6ce8aad6e..96a7cb19a 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -95,7 +95,7 @@ Poincare::Expression ContinuousFunction::expressionReduced(Poincare::Context * c static_cast(result).numberOfRows() != 2 || static_cast(result).numberOfColumns() != 1) ) { - return Poincare::Expression::Parse("[[undef][undef]]"); + return Poincare::Expression::Parse("[[undef][undef]]", nullptr); } return result; } @@ -120,7 +120,7 @@ ContinuousFunction::PlotType ContinuousFunction::plotType() const { return recordData()->plotType(); } -void ContinuousFunction::setPlotType(PlotType newPlotType, Poincare::Preferences::AngleUnit angleUnit) { +void ContinuousFunction::setPlotType(PlotType newPlotType, Poincare::Preferences::AngleUnit angleUnit, Context * context) { PlotType currentPlotType = plotType(); if (newPlotType == currentPlotType) { return; @@ -143,7 +143,7 @@ void ContinuousFunction::setPlotType(PlotType newPlotType, Poincare::Preferences constexpr int previousTextContentMaxSize = Constant::MaxSerializedExpressionSize; char previousTextContent[previousTextContentMaxSize]; m_model.text(this, previousTextContent, previousTextContentMaxSize, symbol()); - setContent(previousTextContent); + setContent(previousTextContent, context); // Handle parametric function switch if (currentPlotType == PlotType::Parametric) { @@ -165,9 +165,9 @@ void ContinuousFunction::setPlotType(PlotType newPlotType, Poincare::Preferences Expression e = expressionClone(); // Change y(t) to [t y(t)] Matrix newExpr = Matrix::Builder(); - newExpr.addChildAtIndexInPlace(Symbol::Builder(UCodePointUnknownX), 0, 0); + newExpr.addChildAtIndexInPlace(Symbol::Builder(UCodePointUnknown), 0, 0); // if y(t) was not uninitialized, insert [t 2t] to set an example - e = e.isUninitialized() ? Multiplication::Builder(Rational::Builder(2), Symbol::Builder(UCodePointUnknownX)) : e; + e = e.isUninitialized() ? Multiplication::Builder(Rational::Builder(2), Symbol::Builder(UCodePointUnknown)) : e; newExpr.addChildAtIndexInPlace(e, newExpr.numberOfChildren(), newExpr.numberOfChildren()); newExpr.setDimensions(2, 1); setExpressionContent(newExpr); @@ -237,7 +237,7 @@ double ContinuousFunction::approximateDerivative(double x, Poincare::Context * c if (x < tMin() || x > tMax()) { return NAN; } - Poincare::Derivative derivative = Poincare::Derivative::Builder(expressionReduced(context).clone(), Symbol::Builder(UCodePointUnknownX), Poincare::Float::Builder(x)); // derivative takes ownership of Poincare::Float::Builder(x) and the clone of expression + Poincare::Derivative derivative = Poincare::Derivative::Builder(expressionReduced(context).clone(), Symbol::Builder(UCodePointUnknown), Poincare::Float::Builder(x)); // derivative takes ownership of Poincare::Float::Builder(x) and the clone of expression /* TODO: when we approximate derivative, we might want to simplify the * derivative here. However, we might want to do it once for all x (to avoid * lagging in the derivative table. */ @@ -276,18 +276,18 @@ ContinuousFunction::RecordDataBuffer * ContinuousFunction::recordData() const { template Coordinate2D ContinuousFunction::templatedApproximateAtParameter(T t, Poincare::Context * context) const { - if (isCircularlyDefined(context) || t < tMin() || t > tMax()) { + if (t < tMin() || t > tMax()) { return Coordinate2D(plotType() == PlotType::Cartesian ? t : NAN, NAN); } constexpr int bufferSize = CodePoint::MaxCodePointCharLength + 1; char unknown[bufferSize]; - Poincare::SerializationHelper::CodePoint(unknown, bufferSize, UCodePointUnknownX); + Poincare::SerializationHelper::CodePoint(unknown, bufferSize, UCodePointUnknown); PlotType type = plotType(); - if (type == PlotType::Cartesian || type == PlotType::Polar) { - return Coordinate2D(t, PoincareHelpers::ApproximateWithValueForSymbol(expressionReduced(context), unknown, t, context)); - } - assert(type == PlotType::Parametric); Expression e = expressionReduced(context); + if (type != PlotType::Parametric) { + assert(type == PlotType::Cartesian || type == PlotType::Polar); + return Coordinate2D(t, PoincareHelpers::ApproximateWithValueForSymbol(e, unknown, t, context)); + } assert(e.type() == ExpressionNode::Type::Matrix); assert(static_cast(e).numberOfRows() == 2); assert(static_cast(e).numberOfColumns() == 1); @@ -312,7 +312,7 @@ Coordinate2D ContinuousFunction::nextIntersectionFrom(double start, doub assert(plotType() == PlotType::Cartesian); constexpr int bufferSize = CodePoint::MaxCodePointCharLength + 1; char unknownX[bufferSize]; - SerializationHelper::CodePoint(unknownX, bufferSize, UCodePointUnknownX); + SerializationHelper::CodePoint(unknownX, bufferSize, UCodePointUnknown); double domainMin = maxDouble(tMin(), eDomainMin); double domainMax = minDouble(tMax(), eDomainMax); if (step > 0.0f) { @@ -329,7 +329,7 @@ Coordinate2D ContinuousFunction::nextPointOfInterestFrom(double start, d assert(plotType() == PlotType::Cartesian); constexpr int bufferSize = CodePoint::MaxCodePointCharLength + 1; char unknownX[bufferSize]; - SerializationHelper::CodePoint(unknownX, bufferSize, UCodePointUnknownX); + SerializationHelper::CodePoint(unknownX, bufferSize, UCodePointUnknown); if (step > 0.0f) { start = maxDouble(start, tMin()); max = minDouble(max, tMax()); @@ -344,7 +344,7 @@ Poincare::Expression ContinuousFunction::sumBetweenBounds(double start, double e assert(plotType() == PlotType::Cartesian); start = maxDouble(start, tMin()); end = minDouble(end, tMax()); - return Poincare::Integral::Builder(expressionReduced(context).clone(), Poincare::Symbol::Builder(UCodePointUnknownX), Poincare::Float::Builder(start), Poincare::Float::Builder(end)); // Integral takes ownership of args + return Poincare::Integral::Builder(expressionReduced(context).clone(), Poincare::Symbol::Builder(UCodePointUnknown), Poincare::Float::Builder(start), Poincare::Float::Builder(end)); // Integral takes ownership of args /* TODO: when we approximate integral, we might want to simplify the integral * here. However, we might want to do it once for all x (to avoid lagging in * the derivative table. */ diff --git a/apps/shared/continuous_function.h b/apps/shared/continuous_function.h index 47dc04ddd..612651231 100644 --- a/apps/shared/continuous_function.h +++ b/apps/shared/continuous_function.h @@ -35,7 +35,7 @@ public: Parametric = 2 }; PlotType plotType() const; - void setPlotType(PlotType plotType, Poincare::Preferences::AngleUnit angleUnit); + void setPlotType(PlotType plotType, Poincare::Preferences::AngleUnit angleUnit, Poincare::Context * context); static I18n::Message ParameterMessageForPlotType(PlotType plotType); // Evaluation @@ -106,9 +106,8 @@ private: //char m_expression[0]; }; class Model : public ExpressionModel { - public: - void * expressionAddress(const Ion::Storage::Record * record) const override; private: + void * expressionAddress(const Ion::Storage::Record * record) const override; size_t expressionSize(const Ion::Storage::Record * record) const override; }; size_t metaDataSize() const override { return sizeof(RecordDataBuffer); } diff --git a/apps/shared/cursor_view.h b/apps/shared/cursor_view.h index f0b662954..4590950b2 100644 --- a/apps/shared/cursor_view.h +++ b/apps/shared/cursor_view.h @@ -7,7 +7,7 @@ namespace Shared { class CursorView : public View { public: - virtual void setCursorFrame(KDRect frame) { View::setFrame(frame); } + virtual void setCursorFrame(KDRect frame, bool force) { View::setFrame(frame, force); } void drawRect(KDContext * ctx, KDRect rect) const override; KDSize minimalSizeForOptimalDisplay() const override; private: diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index 46fadcb14..3a549ae06 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -1,5 +1,6 @@ #include "curve_view.h" #include "../constant.h" +#include "dots.h" #include #include #include @@ -177,12 +178,12 @@ void CurveView::computeLabels(Axis axis) { int axisLabelsCount = numberOfLabels(axis); for (int i = 0; i < axisLabelsCount; i++) { float labelValue = labelValueAtIndex(axis, i); - /* Label cannot hold more than k_labelBufferMaxSize characters to prevent + /* Label cannot hold more than k_labelBufferMaxGlyphLength characters to prevent * them from overprinting one another.*/ - int labelMaxGlyphLength = k_labelBufferMaxGlyphLength; + int labelMaxGlyphLength = labelMaxGlyphLengthSize(); if (axis == Axis::Horizontal) { float pixelsPerLabel = maxFloat(0.0f, ((float)Ion::Display::Width)/((float)axisLabelsCount) - k_labelMargin); - labelMaxGlyphLength = minInt(k_labelBufferMaxGlyphLength, pixelsPerLabel/k_font->glyphSize().width()); + labelMaxGlyphLength = minInt(labelMaxGlyphLengthSize(), pixelsPerLabel/k_font->glyphSize().width()); } if (labelValue < step && labelValue > -step) { @@ -219,18 +220,53 @@ void CurveView::computeLabels(Axis axis) { } } +void CurveView::simpleDrawBothAxesLabels(KDContext * ctx, KDRect rect) const { + drawLabelsAndGraduations(ctx, rect, Axis::Vertical, true); + drawLabelsAndGraduations(ctx, rect, Axis::Horizontal, true); +} + +KDPoint CurveView::positionLabel(KDCoordinate xPosition, KDCoordinate yPosition, KDSize labelSize, RelativePosition horizontalPosition, RelativePosition verticalPosition) const { + switch (horizontalPosition) { + case RelativePosition::Before: // Left + xPosition -= labelSize.width() + k_labelMargin; + break; + case RelativePosition::After: // Right + xPosition += k_labelMargin; + break; + default: + xPosition -= labelSize.width()/2; + } + switch (verticalPosition) { + case RelativePosition::After: // Above + yPosition -= labelSize.height() + k_labelMargin; + break; + case RelativePosition::Before: // Below + yPosition += k_labelMargin; + break; + default: + yPosition -= labelSize.height()/2; + } + return KDPoint(xPosition, yPosition); +} + +void CurveView::drawLabel(KDContext * ctx, KDRect rect, float xPosition, float yPosition, const char * label, KDColor color, RelativePosition horizontalPosition, RelativePosition verticalPosition) const { + KDSize labelSize = k_font->stringSize(label); + KDCoordinate xCoordinate = std::round(floatToPixel(Axis::Horizontal, xPosition)); + KDCoordinate yCoordinate = std::round(floatToPixel(Axis::Vertical, yPosition)); + KDPoint position = positionLabel(xCoordinate, yCoordinate, labelSize, horizontalPosition, verticalPosition); + if (rect.intersects(KDRect(position, labelSize))) { + // TODO: should we blend? + ctx->drawString(label, position, k_font, color, KDColorWhite); + } +} + enum class FloatingPosition : uint8_t { None, Min, Max }; -void CurveView::simpleDrawBothAxesLabels(KDContext * ctx, KDRect rect) const { - drawLabels(ctx, rect, Axis::Vertical, true); - drawLabels(ctx, rect, Axis::Horizontal, true); -} - -void CurveView::drawLabels(KDContext * ctx, KDRect rect, Axis axis, bool shiftOrigin, bool graduationOnly, bool fixCoordinate, KDCoordinate fixedCoordinate, KDColor backgroundColor) const { +void CurveView::drawLabelsAndGraduations(KDContext * ctx, KDRect rect, Axis axis, bool shiftOrigin, bool graduationOnly, bool fixCoordinate, KDCoordinate fixedCoordinate, KDColor backgroundColor) const { int numberLabels = numberOfLabels(axis); if (numberLabels <= 1) { return; @@ -239,12 +275,13 @@ void CurveView::drawLabels(KDContext * ctx, KDRect rect, Axis axis, bool shiftOr float verticalCoordinate = fixCoordinate ? fixedCoordinate : std::round(floatToPixel(Axis::Vertical, 0.0f)); float horizontalCoordinate = fixCoordinate ? fixedCoordinate : std::round(floatToPixel(Axis::Horizontal, 0.0f)); - int viewHeight = bounds().height() - (bannerIsVisible() ? m_bannerView->minimalSizeForOptimalDisplay().height() : 0); + KDCoordinate viewHeight = bounds().height() - (bannerIsVisible() ? m_bannerView->minimalSizeForOptimalDisplay().height() : 0); /* If the axis is not visible, draw floating labels on the edge of the screen. * The X axis floating status is needed when drawing both axes labels. */ FloatingPosition floatingHorizontalLabels = FloatingPosition::None; - if (verticalCoordinate > viewHeight - k_font->glyphSize().height() - k_labelMargin) { + KDCoordinate maximalVerticalPosition = graduationOnly ? viewHeight : viewHeight - k_font->glyphSize().height() - k_labelMargin; + if (verticalCoordinate > maximalVerticalPosition) { floatingHorizontalLabels = FloatingPosition::Max; } else if (max(Axis::Vertical) < 0.0f) { floatingHorizontalLabels = FloatingPosition::Min; @@ -254,7 +291,8 @@ void CurveView::drawLabels(KDContext * ctx, KDRect rect, Axis axis, bool shiftOr if (axis == Axis::Horizontal) { floatingLabels = floatingHorizontalLabels; } else { - if (horizontalCoordinate < k_labelMargin + k_font->glyphSize().width() * 3) { // We want do display at least 3 characters left of the Y axis + KDCoordinate minimalHorizontalPosition = graduationOnly ? 0 : k_labelMargin + k_font->glyphSize().width() * 3; // We want do display at least 3 characters left of the Y axis + if (horizontalCoordinate < minimalHorizontalPosition) { floatingLabels = FloatingPosition::Min; } else if (max(Axis::Horizontal) < 0.0f) { floatingLabels = FloatingPosition::Max; @@ -314,126 +352,96 @@ void CurveView::drawLabels(KDContext * ctx, KDRect rect, Axis axis, bool shiftOr KDCoordinate labelPosition = std::round(floatToPixel(axis, labelValueAtIndex(axis, i))); char * labelI = label(axis, i); KDSize textSize = k_font->stringSize(labelI); - float xPosition = 0.0f; - float yPosition = 0.0f; - - bool positioned = false; + KDPoint position = KDPointZero; if (strcmp(labelI, "0") == 0) { if (floatingLabels != FloatingPosition::None) { // Do not draw the zero, it is symbolized by the other axis continue; } if (shiftOrigin && floatingLabels == FloatingPosition::None) { - xPosition = horizontalCoordinate - k_labelMargin - textSize.width(); - yPosition = verticalCoordinate + k_labelMargin; - positioned = true; + position = positionLabel(horizontalCoordinate, verticalCoordinate, textSize, RelativePosition::Before, RelativePosition::Before); + goto DrawLabel; } } - if (!positioned) { - if (axis == Axis::Horizontal) { - xPosition = labelPosition - textSize.width()/2; - if (floatingLabels == FloatingPosition::None) { - yPosition = verticalCoordinate + k_labelMargin; - } else if (floatingLabels == FloatingPosition::Min) { - yPosition = k_labelMargin; - } else { - yPosition = viewHeight - k_font->glyphSize().height() - k_labelMargin; - } - } else { - yPosition = labelPosition - textSize.height()/2; - if (floatingLabels == FloatingPosition::None) { - xPosition = horizontalCoordinate - k_labelMargin - textSize.width(); - } else if (floatingLabels == FloatingPosition::Min) { - xPosition = k_labelMargin; - } else { - xPosition = Ion::Display::Width - textSize.width() - k_labelMargin; - } + if (axis == Axis::Horizontal) { + position = positionLabel(labelPosition, verticalCoordinate, textSize, RelativePosition::None, RelativePosition::Before); + if (floatingLabels == FloatingPosition::Min) { + position = KDPoint(position.x(), k_labelMargin); + } else if (floatingLabels == FloatingPosition::Max) { + position = KDPoint(position.x(), viewHeight - k_font->glyphSize().height() - k_labelMargin); + } + } else { + position = positionLabel(horizontalCoordinate, labelPosition, textSize, RelativePosition::Before, RelativePosition::None); + if (floatingLabels == FloatingPosition::Min) { + position = KDPoint(k_labelMargin, position.y()); + } else if (floatingLabels == FloatingPosition::Min) { + position = KDPoint(Ion::Display::Width - textSize.width() - k_labelMargin, position.y()); } } - KDPoint origin = KDPoint(xPosition, yPosition); - if (rect.intersects(KDRect(origin, textSize))) { - ctx->drawString(labelI, origin, k_font, Palette::PrimaryText, backgroundColor); + +DrawLabel: + if (rect.intersects(KDRect(position, textSize))) { + ctx->drawString(labelI, position, k_font, Palette::PrimaryText, backgroundColor); } } } -void CurveView::drawLine(KDContext * ctx, KDRect rect, Axis axis, float coordinate, KDColor color, KDCoordinate thickness) const { - KDRect lineRect = KDRectZero; - switch(axis) { - case Axis::Horizontal: - lineRect = KDRect( - rect.x(), std::round(floatToPixel(Axis::Vertical, coordinate)), - rect.width(), thickness - ); - break; - case Axis::Vertical: - lineRect = KDRect( - std::round(floatToPixel(Axis::Horizontal, coordinate)), rect.y(), - thickness, rect.height() - ); - break; +void CurveView::drawSegment(KDContext * ctx, KDRect rect, Axis axis, float coordinate, float lowerBound, float upperBound, KDColor color, KDCoordinate thickness, KDCoordinate dashSize) const { + KDCoordinate min = (axis == Axis::Horizontal) ? rect.x() : rect.y(); + KDCoordinate max = (axis == Axis::Horizontal) ? rect.x() + rect.width() : rect.y() + rect.height(); + KDCoordinate start = std::isinf(lowerBound) ? min : std::round(floatToPixel(axis, lowerBound)); + KDCoordinate end = std::isinf(upperBound) ? max : std::round(floatToPixel(axis, upperBound)); + if (start > end) { + start = end; + end = std::round(floatToPixel(axis, lowerBound)); } - if (rect.intersects(lineRect)) { - ctx->fillRect(lineRect, color); + Axis otherAxis = (axis == Axis::Horizontal) ? Axis::Vertical : Axis::Horizontal; + KDCoordinate pixelCoordinate = std::round(floatToPixel(otherAxis, coordinate)); + if (dashSize < 0) { + // Continuous segment is equivalent to one big dash + dashSize = end - start; + } + KDRect lineRect = KDRectZero; + for (KDCoordinate i = start; i < end; i += 2*dashSize) { + switch(axis) { + case Axis::Horizontal: + lineRect = KDRect(i, pixelCoordinate, dashSize, thickness); + break; + case Axis::Vertical: + lineRect = KDRect(pixelCoordinate, i, thickness, dashSize); + break; + } + if (rect.intersects(lineRect)) { + ctx->fillRect(lineRect, color); + } } } -void CurveView::drawSegment(KDContext * ctx, KDRect rect, Axis axis, float coordinate, float lowerBound, float upperBound, KDColor color, KDCoordinate thickness) const { - KDRect lineRect = KDRectZero; - switch(axis) { - case Axis::Horizontal: - lineRect = KDRect( - std::round(floatToPixel(Axis::Horizontal, lowerBound)), std::round(floatToPixel(Axis::Vertical, coordinate)), - std::round(floatToPixel(Axis::Horizontal, upperBound)) - std::round(floatToPixel(Axis::Horizontal, lowerBound)), thickness - ); +void CurveView::drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor color, Size size) const { + KDCoordinate diameter = 0; + const uint8_t * mask = nullptr; + switch (size) { + case Size::Small: + diameter = Dots::SmallDotDiameter; + mask = (const uint8_t *)Dots::SmallDotMask; break; - case Axis::Vertical: - lineRect = KDRect( - std::round(floatToPixel(Axis::Horizontal, coordinate)), std::round(floatToPixel(Axis::Vertical, upperBound)), - thickness, std::round(floatToPixel(Axis::Vertical, lowerBound)) - std::round(floatToPixel(Axis::Vertical, upperBound)) - ); + case Size::Medium: + diameter = Dots::MediumDotDiameter; + mask = (const uint8_t *)Dots::MediumDotMask; break; + default: + assert(size == Size::Large); + diameter = Dots::LargeDotDiameter; + mask = (const uint8_t *)Dots::LargeDotMask; } - if (rect.intersects(lineRect)) { - ctx->fillRect(lineRect, color); - } -} - -constexpr KDCoordinate dotDiameter = 5; -const uint8_t dotMask[dotDiameter][dotDiameter] = { - {0xE1, 0x45, 0x0C, 0x45, 0xE1}, - {0x45, 0x00, 0x00, 0x00, 0x45}, - {0x00, 0x00, 0x00, 0x00, 0x00}, - {0x45, 0x00, 0x00, 0x00, 0x45}, - {0xE1, 0x45, 0x0C, 0x45, 0xE1}, -}; - -constexpr KDCoordinate oversizeDotDiameter = 7; -const uint8_t oversizeDotMask[oversizeDotDiameter][oversizeDotDiameter] = { - {0xE1, 0x45, 0x0C, 0x00, 0x0C, 0x45, 0xE1}, - {0x45, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x45}, - {0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C}, - {0x45, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x45}, - {0xE1, 0x45, 0x0C, 0x00, 0x0C, 0x45, 0xE1}, - -}; - -void CurveView::drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor color, bool oversize) const { - const KDCoordinate diameter = oversize ? oversizeDotDiameter : dotDiameter; KDCoordinate px = std::round(floatToPixel(Axis::Horizontal, x)); KDCoordinate py = std::round(floatToPixel(Axis::Vertical, y)); KDRect dotRect(px - diameter/2, py - diameter/2, diameter, diameter); if (!rect.intersects(dotRect)) { return; } - KDColor workingBuffer[oversizeDotDiameter*oversizeDotDiameter]; - ctx->blendRectWithMask( - dotRect, color, - oversize ? (const uint8_t *)oversizeDotMask : (const uint8_t *)dotMask, - workingBuffer - ); + KDColor workingBuffer[Dots::LargeDotDiameter*Dots::LargeDotDiameter]; + ctx->blendRectWithMask(dotRect, color, mask, workingBuffer); } void CurveView::drawGrid(KDContext * ctx, KDRect rect) const { @@ -452,60 +460,58 @@ void CurveView::drawAxis(KDContext * ctx, KDRect rect, Axis axis) const { drawLine(ctx, rect, axis, 0.0f, Palette::PrimaryText, 1); } -#define LINE_THICKNESS 2 - -#if LINE_THICKNESS == 1 - -constexpr KDCoordinate circleDiameter = 1; -constexpr KDCoordinate stampSize = circleDiameter+1; -const uint8_t stampMask[stampSize+1][stampSize+1] = { - {0xFF, 0xE1, 0xFF}, - {0xE1, 0x00, 0xE1}, - {0xFF, 0xE1, 0xFF}, +constexpr KDCoordinate thinCircleDiameter = 1; +constexpr KDCoordinate thinStampSize = thinCircleDiameter+1; +const uint8_t thinStampMask[(thinStampSize+1)*(thinStampSize+1)] = { + 0xFF, 0xE1, 0xFF, + 0xE1, 0x00, 0xE1, + 0xFF, 0xE1, 0xFF, }; -#elif LINE_THICKNESS == 2 +#define LINE_THICKNESS 2 -constexpr KDCoordinate circleDiameter = 2; -constexpr KDCoordinate stampSize = circleDiameter+1; -const uint8_t stampMask[stampSize+1][stampSize+1] = { - {0xFF, 0xE6, 0xE6, 0xFF}, - {0xE6, 0x33, 0x33, 0xE6}, - {0xE6, 0x33, 0x33, 0xE6}, - {0xFF, 0xE6, 0xE6, 0xFF}, +#if LINE_THICKNESS == 2 + +constexpr KDCoordinate thickCircleDiameter = 2; +constexpr KDCoordinate thickStampSize = thickCircleDiameter+1; +const uint8_t thickStampMask[(thickStampSize+1)*(thickStampSize+1)] = { + 0xFF, 0xE6, 0xE6, 0xFF, + 0xE6, 0x33, 0x33, 0xE6, + 0xE6, 0x33, 0x33, 0xE6, + 0xFF, 0xE6, 0xE6, 0xFF, }; #elif LINE_THICKNESS == 3 -constexpr KDCoordinate circleDiameter = 3; -constexpr KDCoordinate stampSize = circleDiameter+1; -const uint8_t stampMask[stampSize+1][stampSize+1] = { - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0x7A, 0x0C, 0x7A, 0xFF}, - {0xFF, 0x0C, 0x00, 0x0C, 0xFF}, - {0xFF, 0x7A, 0x0C, 0x7A, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF} +constexpr KDCoordinate thickCircleDiameter = 3; +constexpr KDCoordinate thickStampSize = thickCircleDiameter+1; +const uint8_t thickStampMask[(thickStampSize+1)*(thickStampSize+1)] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x7A, 0x0C, 0x7A, 0xFF, + 0xFF, 0x0C, 0x00, 0x0C, 0xFF, + 0xFF, 0x7A, 0x0C, 0x7A, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; #elif LINE_THICKNESS == 5 -constexpr KDCoordinate circleDiameter = 5; -constexpr KDCoordinate stampSize = circleDiameter+1; -const uint8_t stampMask[stampSize+1][stampSize+1] = { - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xE1, 0x45, 0x0C, 0x45, 0xE1, 0xFF}, - {0xFF, 0x45, 0x00, 0x00, 0x00, 0x45, 0xFF}, - {0xFF, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0xFF}, - {0xFF, 0x45, 0x00, 0x00, 0x00, 0x45, 0xFF}, - {0xFF, 0xE1, 0x45, 0x0C, 0x45, 0xE1, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, +constexpr KDCoordinate thickCircleDiameter = 5; +constexpr KDCoordinate thickStampSize = thickCircleDiameter+1; +const uint8_t thickStampMask[(thickStampSize+1)*(thickStampSize+1)] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xE1, 0x45, 0x0C, 0x45, 0xE1, 0xFF, + 0xFF, 0x45, 0x00, 0x00, 0x00, 0x45, 0xFF, + 0xFF, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0xFF, + 0xFF, 0x45, 0x00, 0x00, 0x00, 0x45, 0xFF, + 0xFF, 0xE1, 0x45, 0x0C, 0x45, 0xE1, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }; #endif constexpr static int k_maxNumberOfIterations = 10; -void CurveView::drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd, float tStep, EvaluateXYForParameter xyEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool colorUnderCurve, float colorLowerBound, float colorUpperBound) const { +void CurveView::drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd, float tStep, EvaluateXYForParameter xyEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool thick, bool colorUnderCurve, float colorLowerBound, float colorUpperBound) const { float previousT = NAN; float t = NAN; float previousX = NAN; @@ -533,11 +539,11 @@ void CurveView::drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd if (colorUnderCurve && !std::isnan(x) && colorLowerBound < x && x < colorUpperBound && !(std::isnan(y) || std::isinf(y))) { drawSegment(ctx, rect, Axis::Vertical, x, minFloat(0.0f, y), maxFloat(0.0f, y), color, 1); } - jointDots(ctx, rect, xyEvaluation, model, context, drawStraightLinesEarly, previousT, previousX, previousY, t, x, y, color, k_maxNumberOfIterations); + joinDots(ctx, rect, xyEvaluation, model, context, drawStraightLinesEarly, previousT, previousX, previousY, t, x, y, color, thick, k_maxNumberOfIterations); } while (true); } -void CurveView::drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, EvaluateXYForParameter xyEvaluation, void * model, void * context, KDColor color, bool colorUnderCurve, float colorLowerBound, float colorUpperBound) const { +void CurveView::drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, EvaluateXYForParameter xyEvaluation, void * model, void * context, KDColor color, bool thick, bool colorUnderCurve, float colorLowerBound, float colorUpperBound) const { float rectLeft = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin); float rectRight = pixelToFloat(Axis::Horizontal, rect.right() + k_externRectMargin); float tStart = std::isnan(rectLeft) ? xMin : maxFloat(xMin, rectLeft); @@ -547,7 +553,7 @@ void CurveView::drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, flo return; } float tStep = pixelWidth(); - drawCurve(ctx, rect, tStart, tEnd, tStep, xyEvaluation, model, context, true, color, colorUnderCurve, colorLowerBound, colorUpperBound); + drawCurve(ctx, rect, tStart, tEnd, tStep, xyEvaluation, model, context, true, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound); } void CurveView::drawHistogram(KDContext * ctx, KDRect rect, EvaluateYForX yEvaluation, void * model, void * context, float firstBarAbscissa, float barWidth, @@ -588,7 +594,7 @@ void CurveView::drawHistogram(KDContext * ctx, KDRect rect, EvaluateYForX yEvalu } } -void CurveView::jointDots(KDContext * ctx, KDRect rect, EvaluateXYForParameter xyEvaluation , void * model, void * context, bool drawStraightLinesEarly, float t, float x, float y, float s, float u, float v, KDColor color, int maxNumberOfRecursion) const { +void CurveView::joinDots(KDContext * ctx, KDRect rect, EvaluateXYForParameter xyEvaluation , void * model, void * context, bool drawStraightLinesEarly, float t, float x, float y, float s, float u, float v, KDColor color, bool thick, int maxNumberOfRecursion) const { const bool isFirstDot = std::isnan(t); const bool isLeftDotValid = !( std::isnan(x) || std::isinf(x) || @@ -603,6 +609,7 @@ void CurveView::jointDots(KDContext * ctx, KDRect rect, EvaluateXYForParameter x if (!isRightDotValid && !isLeftDotValid) { return; } + KDCoordinate circleDiameter = thick ? thickCircleDiameter : thinCircleDiameter; if (isRightDotValid) { const float deltaX = pxf - puf; const float deltaY = pyf - pvf; @@ -610,7 +617,7 @@ void CurveView::jointDots(KDContext * ctx, KDRect rect, EvaluateXYForParameter x || (!isLeftDotValid && maxNumberOfRecursion == 0) // Last step of the recursion with an undefined left dot: we stamp the last right dot || (isLeftDotValid && deltaX*deltaX + deltaY*deltaY < circleDiameter * circleDiameter / 4.0f)) { // the dots are already close enough // the dots are already joined - stampAtLocation(ctx, rect, puf, pvf, color); + stampAtLocation(ctx, rect, puf, pvf, color, thick); return; } } @@ -623,12 +630,12 @@ void CurveView::jointDots(KDContext * ctx, KDRect rect, EvaluateXYForParameter x ((x <= cx && cx <= u) || (u <= cx && cx <= x)) && ((y <= cy && cy <= v) || (v <= cy && cy <= y))) { /* As the middle dot is between the two dots, we assume that we * can draw a 'straight' line between the two */ - straightJoinDots(ctx, rect, pxf, pyf, puf, pvf, color); + straightJoinDots(ctx, rect, pxf, pyf, puf, pvf, color, thick); return; } if (maxNumberOfRecursion > 0) { - jointDots(ctx, rect, xyEvaluation, model, context, drawStraightLinesEarly, t, x, y, ct, cx, cy, color, maxNumberOfRecursion-1); - jointDots(ctx, rect, xyEvaluation, model, context, drawStraightLinesEarly, ct, cx, cy, s, u, v, color, maxNumberOfRecursion-1); + joinDots(ctx, rect, xyEvaluation, model, context, drawStraightLinesEarly, t, x, y, ct, cx, cy, color, thick, maxNumberOfRecursion-1); + joinDots(ctx, rect, xyEvaluation, model, context, drawStraightLinesEarly, ct, cx, cy, s, u, v, color, thick, maxNumberOfRecursion-1); } } @@ -646,7 +653,7 @@ static void clipBarycentricCoordinatesBetweenBounds(float & start, float & end, } } -void CurveView::straightJoinDots(KDContext * ctx, KDRect rect, float pxf, float pyf, float puf, float pvf, KDColor color) const { +void CurveView::straightJoinDots(KDContext * ctx, KDRect rect, float pxf, float pyf, float puf, float pvf, KDColor color, bool thick) const { { /* Before drawing the line segment, clip it to rect: * start and end are the barycentric coordinates on the line segment (0 @@ -654,6 +661,7 @@ void CurveView::straightJoinDots(KDContext * ctx, KDRect rect, float pxf, float * points. */ float start = 0; float end = 1; + KDCoordinate stampSize = thick ? thickStampSize : thinStampSize; const KDCoordinate xBounds[2] = { static_cast(rect.left() - stampSize), static_cast(rect.right() + stampSize) @@ -674,18 +682,19 @@ void CurveView::straightJoinDots(KDContext * ctx, KDRect rect, float pxf, float } const float deltaX = pxf - puf; const float deltaY = pyf - pvf; + KDCoordinate circleDiameter = thick ? thickCircleDiameter : thinCircleDiameter; const float normsRatio = std::sqrt(deltaX*deltaX + deltaY*deltaY) / (circleDiameter / 2.0f); const float stepX = deltaX / normsRatio ; const float stepY = deltaY / normsRatio; const int numberOfStamps = std::floor(normsRatio); for (int i = 0; i < numberOfStamps; i++) { - stampAtLocation(ctx, rect, puf, pvf, color); + stampAtLocation(ctx, rect, puf, pvf, color, thick); puf += stepX; pvf += stepY; } } -void CurveView::stampAtLocation(KDContext * ctx, KDRect rect, float pxf, float pyf, KDColor color) const { +void CurveView::stampAtLocation(KDContext * ctx, KDRect rect, float pxf, float pyf, KDColor color, bool thick) const { /* The (pxf, pyf) coordinates are not generally locating the center of a * pixel. We use stampMask, which is one pixel wider and higher than * stampSize, in order to cover stampRect without aligning the pixels. Then @@ -698,6 +707,8 @@ void CurveView::stampAtLocation(KDContext * ctx, KDRect rect, float pxf, float p * (pxf,pyf) which is then translated to the center of the top-left pixel of * stampMask. */ + KDCoordinate stampSize = thick ? thickStampSize : thinStampSize; + const uint8_t * stampMask = thick ? thickStampMask : thinStampMask; pxf -= (stampSize + 1 - 1)/2.0f; pyf -= (stampSize + 1 - 1)/2.0f; const KDCoordinate px = std::ceil(pxf); @@ -713,24 +724,25 @@ void CurveView::stampAtLocation(KDContext * ctx, KDRect rect, float pxf, float p /* TODO: this could be optimized by precomputing 10 or 100 shifted masks. The * dx and dy would be rounded to one tenth or one hundredth to choose the * right shifted mask. */ + const KDCoordinate stampMaskSize = stampSize + 1; for (int i=0; iblendRectWithMask(stampRect, color, (const uint8_t *)shiftedMask, workingBuffer); } -void CurveView::layoutSubviews() { +void CurveView::layoutSubviews(bool force) { if (m_curveViewCursor != nullptr && m_cursorView != nullptr) { - m_cursorView->setCursorFrame(cursorFrame()); + m_cursorView->setCursorFrame(cursorFrame(), force); } if (m_bannerView != nullptr) { - m_bannerView->setFrame(bannerFrame()); + m_bannerView->setFrame(bannerFrame(), force); } if (m_okView != nullptr) { - m_okView->setFrame(okFrame()); + m_okView->setFrame(okFrame(), force); } } @@ -752,6 +764,7 @@ KDRect CurveView::cursorFrame() { KDRect CurveView::bannerFrame() { KDRect bannerFrame = KDRectZero; if (bannerIsVisible()) { + assert(bounds().width() == Ion::Display::Width); // Else the bannerHeight will not be properly computed KDCoordinate bannerHeight = m_bannerView->minimalSizeForOptimalDisplay().height(); bannerFrame = KDRect(0, bounds().height()- bannerHeight, bounds().width(), bannerHeight); } @@ -828,7 +841,7 @@ void CurveView::computeHorizontalExtremaLabels(bool increaseNumberOfSignificantD labelValueAtIndex(axis, i), label(axis, i), k_labelBufferMaxSize, - k_labelBufferMaxGlyphLength, + labelMaxGlyphLengthSize(), increaseNumberOfSignificantDigits ? k_bigNumberSignificantDigits : k_numberSignificantDigits, Preferences::PrintFloatMode::Decimal); } diff --git a/apps/shared/curve_view.h b/apps/shared/curve_view.h index 21f219d94..433fce06f 100644 --- a/apps/shared/curve_view.h +++ b/apps/shared/curve_view.h @@ -58,25 +58,42 @@ protected: float pixelToFloat(Axis axis, KDCoordinate p) const; float floatToPixel(Axis axis, float f) const; void drawLine(KDContext * ctx, KDRect rect, Axis axis, - float coordinate, KDColor color, KDCoordinate thickness = 1) const; + float coordinate, KDColor color, KDCoordinate thickness = 1, KDCoordinate dashSize = -1) const { + return drawSegment(ctx, rect, axis, coordinate, -INFINITY, INFINITY, color, + thickness, dashSize); + } void drawSegment(KDContext * ctx, KDRect rect, Axis axis, float coordinate, float lowerBound, float upperBound, - KDColor color, KDCoordinate thickness = 1) const; - void drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor color, bool oversize = false) const; + KDColor color, KDCoordinate thickness = 1, KDCoordinate dashSize = -1) const; + enum class Size : uint8_t { + Small, + Medium, + Large + }; + void drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor color, Size size = Size::Small) const; void drawGrid(KDContext * ctx, KDRect rect) const; void drawAxes(KDContext * ctx, KDRect rect) const; void drawAxis(KDContext * ctx, KDRect rect, Axis axis) const; - void drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd, float tStep, EvaluateXYForParameter xyEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f) const; - void drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, EvaluateXYForParameter xyEvaluation, void * model, void * context, KDColor color, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f) const; + void drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd, float tStep, EvaluateXYForParameter xyEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool thick = true, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f) const; + void drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, EvaluateXYForParameter xyEvaluation, void * model, void * context, KDColor color, bool thick = true, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f) const; void drawHistogram(KDContext * ctx, KDRect rect, EvaluateYForX yEvaluation, void * model, void * context, float firstBarAbscissa, float barWidth, bool fillBar, KDColor defaultColor, KDColor highlightColor, float highlightLowerBound = INFINITY, float highlightUpperBound = -INFINITY) const; void computeLabels(Axis axis); void simpleDrawBothAxesLabels(KDContext * ctx, KDRect rect) const; - void drawLabels(KDContext * ctx, KDRect rect, Axis axis, bool shiftOrigin, bool graduationOnly = false, bool fixCoordinate = false, KDCoordinate fixedCoordinate = 0, KDColor backgroundColor = Palette::BackgroundHard) const; + enum class RelativePosition : uint8_t { + None, + Before, + After + }; + // Draw the label at the above/below and to the left/right of the given position + void drawLabel(KDContext * ctx, KDRect rect, float xPosition, float yPosition, const char * label, KDColor color, RelativePosition horizontalPosition, RelativePosition verticalPosition) const; + void drawLabelsAndGraduations(KDContext * ctx, KDRect rect, Axis axis, bool shiftOrigin, bool graduationOnly = false, bool fixCoordinate = false, KDCoordinate fixedCoordinate = 0, KDColor backgroundColor = Palette::BackgroundHard) const; View * m_bannerView; CurveViewCursor * m_curveViewCursor; private: static constexpr const KDFont * k_font = KDFont::SmallFont; + // returns the coordinates where should be drawn the label knowing the coordinates of its graduation and its relative position + KDPoint positionLabel(KDCoordinate xPosition, KDCoordinate yPosition, KDSize labelSize, RelativePosition horizontalPosition, RelativePosition verticalPosition) const; void drawGridLines(KDContext * ctx, KDRect rect, Axis axis, float step, KDColor boldColor, KDColor lightColor) const; /* The window bounds are deduced from the model bounds but also take into account a margin (computed with k_marginFactor) */ @@ -84,17 +101,18 @@ private: float max(Axis axis) const; float gridUnit(Axis axis) const; virtual char * label(Axis axis, int index) const = 0; + virtual size_t labelMaxGlyphLengthSize() const { return k_labelBufferMaxGlyphLength; } int numberOfLabels(Axis axis) const; /* Recursively join two dots (dichotomy). The method stops when the * maxNumberOfRecursion in reached. */ - void jointDots(KDContext * ctx, KDRect rect, EvaluateXYForParameter xyEvaluation, void * model, void * context, bool drawStraightLinesEarly, float t, float x, float y, float s, float u, float v, KDColor color, int maxNumberOfRecursion) const; + void joinDots(KDContext * ctx, KDRect rect, EvaluateXYForParameter xyEvaluation, void * model, void * context, bool drawStraightLinesEarly, float t, float x, float y, float s, float u, float v, KDColor color, bool thick, int maxNumberOfRecursion) const; /* Join two dots with a straight line. */ - void straightJoinDots(KDContext * ctx, KDRect rect, float pxf, float pyf, float puf, float pvf, KDColor color) const; + void straightJoinDots(KDContext * ctx, KDRect rect, float pxf, float pyf, float puf, float pvf, KDColor color, bool thick) const; /* Stamp centered around (pxf, pyf). If pxf and pyf are not round number, the * function shifts the stamp (by blending adjacent pixel colors) to draw with * anti alising. */ - void stampAtLocation(KDContext * ctx, KDRect rect, float pxf, float pyf, KDColor color) const; - void layoutSubviews() override; + void stampAtLocation(KDContext * ctx, KDRect rect, float pxf, float pyf, KDColor color, bool thick) const; + void layoutSubviews(bool force = false) override; KDRect cursorFrame(); KDRect bannerFrame(); KDRect okFrame(); diff --git a/apps/shared/curve_view_range.cpp b/apps/shared/curve_view_range.cpp index cf27602d2..93ae6bc6d 100644 --- a/apps/shared/curve_view_range.cpp +++ b/apps/shared/curve_view_range.cpp @@ -2,6 +2,7 @@ #include "curve_view.h" #include #include +#include #include #include #include @@ -22,8 +23,8 @@ float CurveViewRange::computeGridUnit(float minNumberOfUnits, float maxNumberOfU float units[unitsCount] = {k_smallGridUnitMantissa, k_mediumGridUnitMantissa, k_largeGridUnitMantissa}; for (int k = 0; k < unitsCount; k++) { float currentA = units[k]; - int b1 = std::floor(std::log10(range/(currentA*maxNumberOfUnits))); - int b2 = std::floor(std::log10(range/(currentA*minNumberOfUnits))); + int b1 = Poincare::IEEE754::exponentBase10(range/(currentA*maxNumberOfUnits)); + int b2 = Poincare::IEEE754::exponentBase10(range/(currentA*minNumberOfUnits)); if (b1 != b2) { b = b2; a = currentA; diff --git a/apps/shared/dots.cpp b/apps/shared/dots.cpp new file mode 100644 index 000000000..0f178b44e --- /dev/null +++ b/apps/shared/dots.cpp @@ -0,0 +1,36 @@ +#include "dots.h" + +namespace Shared { + +const uint8_t Dots::SmallDotMask[Dots::SmallDotDiameter][Dots::SmallDotDiameter] = { + {0xE1, 0x45, 0x0C, 0x45, 0xE1}, + {0x45, 0x00, 0x00, 0x00, 0x45}, + {0x00, 0x00, 0x00, 0x00, 0x00}, + {0x45, 0x00, 0x00, 0x00, 0x45}, + {0xE1, 0x45, 0x0C, 0x45, 0xE1}, +}; + +const uint8_t Dots::MediumDotMask[Dots::MediumDotDiameter][Dots::MediumDotDiameter] = { + {0xE1, 0x45, 0x0C, 0x00, 0x0C, 0x45, 0xE1}, + {0x45, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x45}, + {0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C}, + {0x45, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x45}, + {0xE1, 0x45, 0x0C, 0x00, 0x0C, 0x45, 0xE1}, +}; + +const uint8_t Dots::LargeDotMask[Dots::LargeDotDiameter][Dots::LargeDotDiameter] = { + {0xFF, 0xFF, 0xFF, 0xED, 0xB6, 0xB6, 0xED, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0x7C, 0x06, 0x00, 0x00, 0x06, 0x7C, 0xFF, 0xFF}, + {0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xFF}, + {0xED, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5}, + {0xB6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB6}, + {0xB6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB6}, + {0xED, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5}, + {0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xFF}, + {0xFF, 0xFF, 0x7C, 0x06, 0x00, 0x00, 0x06, 0x7C, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xFF, 0xED, 0xB6, 0xB6, 0xED, 0xFF, 0xFF, 0xFF}, +}; + +} diff --git a/apps/shared/dots.h b/apps/shared/dots.h new file mode 100644 index 000000000..07d65ff44 --- /dev/null +++ b/apps/shared/dots.h @@ -0,0 +1,20 @@ +#ifndef SHARED_DOTS_H +#define SHARED_DOTS_H + +#include + +namespace Shared { + +class Dots { +public: + static constexpr KDCoordinate SmallDotDiameter = 5; + static const uint8_t SmallDotMask[SmallDotDiameter][SmallDotDiameter]; + static constexpr KDCoordinate MediumDotDiameter = 7; + static const uint8_t MediumDotMask[MediumDotDiameter][MediumDotDiameter]; + static constexpr KDCoordinate LargeDotDiameter = 10; + static const uint8_t LargeDotMask[LargeDotDiameter][LargeDotDiameter]; +}; + +} + +#endif diff --git a/apps/shared/double_pair_store.cpp b/apps/shared/double_pair_store.cpp index d298aa21b..b35c56de7 100644 --- a/apps/shared/double_pair_store.cpp +++ b/apps/shared/double_pair_store.cpp @@ -98,12 +98,12 @@ int DoublePairStore::indexOfKthNonEmptySeries(int k) const { return 0; } -double DoublePairStore::sumOfColumn(int series, int i) const { +double DoublePairStore::sumOfColumn(int series, int i, bool lnOfSeries) const { assert(series >= 0 && series < k_numberOfSeries); assert(i == 0 || i == 1); double result = 0; for (int k = 0; k < m_numberOfPairs[series]; k++) { - result += m_data[series][i][k]; + result += lnOfSeries ? log(m_data[series][i][k]) : m_data[series][i][k]; } return result; } diff --git a/apps/shared/double_pair_store.h b/apps/shared/double_pair_store.h index 465b78a9d..9c8859612 100644 --- a/apps/shared/double_pair_store.h +++ b/apps/shared/double_pair_store.h @@ -47,7 +47,7 @@ public: int indexOfKthNonEmptySeries(int k) const; // Calculations - double sumOfColumn(int series, int i) const; + double sumOfColumn(int series, int i, bool lnOfSeries = false) const; bool seriesNumberOfAbscissaeGreaterOrEqualTo(int series, int i) const; uint32_t storeChecksum() const; uint32_t storeChecksumForSeries(int series) const; diff --git a/apps/shared/expression_field_delegate_app.cpp b/apps/shared/expression_field_delegate_app.cpp index 2b6c9b95c..25f32a16b 100644 --- a/apps/shared/expression_field_delegate_app.cpp +++ b/apps/shared/expression_field_delegate_app.cpp @@ -39,7 +39,7 @@ bool ExpressionFieldDelegateApp::layoutFieldDidReceiveEvent(LayoutField * layout return true; } // Step 2: Parsing - Poincare::Expression e = Poincare::Expression::Parse(buffer); + Poincare::Expression e = Poincare::Expression::Parse(buffer, layoutField->context()); if (e.isUninitialized()) { // Unparsable expression displayWarning(I18n::Message::SyntaxError); diff --git a/apps/shared/expression_model.cpp b/apps/shared/expression_model.cpp index dcdb5d347..530f7b200 100644 --- a/apps/shared/expression_model.cpp +++ b/apps/shared/expression_model.cpp @@ -1,6 +1,7 @@ #include "expression_model.h" #include "global_context.h" #include "poincare_helpers.h" +#include #include #include #include @@ -27,7 +28,7 @@ void ExpressionModel::text(const Storage::Record * record, char * buffer, size_t buffer[0] = 0; } else { if (symbol != 0 && !e.isUninitialized()) { - e = e.replaceSymbolWithExpression(Symbol::Builder(UCodePointUnknownX), Symbol::Builder(symbol)); + e = e.replaceSymbolWithExpression(Symbol::Builder(UCodePointUnknown), Symbol::Builder(symbol)); } e.serialize(buffer, bufferSize); } @@ -41,6 +42,26 @@ bool ExpressionModel::isCircularlyDefined(const Storage::Record * record, Poinca } Expression ExpressionModel::expressionReduced(const Storage::Record * record, Poincare::Context * context) const { + /* TODO + * By calling isCircularlyDefined and then Simplify, the expression tree is + * browsed twice. Note that Simplify does ALMOST the job of + * isCircularlyDefined, since Simplify reduces the expression, replaces the + * symbols it encounters (see SymbolAbstract::Expand). isCircularlyDefined + * should probably be removed. The difficulty relies in the ambiguous + * conventions about the values returned or set when a symbol has no proper + * expression: for example, + * - GlobalContext::expressionForSymbolAbstract returns an uninitialized + * expression, + * - so do Expression::ExpressionWithoutSymbols and SymbolAbstract::Expand, + * - Expression::deepReplaceReplaceableSymbols leaves unchanged the symbols, + * whose expression is uninitialized, but returns Undefined if the + * expression for a symbol contains the symbol itself, + * - Symbol::shallowReduce and Function::shallowReduce return Undefined or + * the expression unaltered according to symbolic-computation setting, + * - expressionReduced returns Undefined if the expression + * isCircularlyDefined but leaves the expression unchanged if Simplify + * returns an uninitialized expression... + */ if (m_expression.isUninitialized()) { assert(record->fullName() != nullptr); if (isCircularlyDefined(record, context)) { @@ -61,13 +82,20 @@ Expression ExpressionModel::expressionClone(const Storage::Record * record) cons assert(record->fullName() != nullptr); /* A new Expression has to be created at each call (because it might be tempered with after calling) */ return Expression::ExpressionFromAddress(expressionAddress(record), expressionSize(record)); + /* TODO + * The substitution of UCodePointUnknown back and forth is done in the + * methods text, setContent (through BuildExpressionFromText), layout and + * also in GlobalContext::expressionForSymbolAbstract and + * GlobalContext::setExpressionForSymbolAbstract. When getting the + * expression, the substitutions may probably be gathered here. + */ } Layout ExpressionModel::layout(const Storage::Record * record, CodePoint symbol) const { if (m_layout.isUninitialized()) { Expression clone = expressionClone(record); if (!clone.isUninitialized() && symbol != 0) { - clone = clone.replaceSymbolWithExpression(Symbol::Builder(UCodePointUnknownX), Symbol::Builder(symbol)); + clone = clone.replaceSymbolWithExpression(Symbol::Builder(UCodePointUnknown), Symbol::Builder(symbol)); } m_layout = PoincareHelpers::CreateLayout(clone); if (m_layout.isUninitialized()) { @@ -77,8 +105,8 @@ Layout ExpressionModel::layout(const Storage::Record * record, CodePoint symbol) return m_layout; } -Ion::Storage::Record::ErrorStatus ExpressionModel::setContent(Ion::Storage::Record * record, const char * c, CodePoint symbol) { - Expression e = ExpressionModel::BuildExpressionFromText(c, symbol); +Ion::Storage::Record::ErrorStatus ExpressionModel::setContent(Ion::Storage::Record * record, const char * c, Context * context, CodePoint symbol) { + Expression e = ExpressionModel::BuildExpressionFromText(c, symbol, context); return setExpressionContent(record, e); } @@ -124,17 +152,17 @@ void ExpressionModel::updateNewDataWithExpression(Ion::Storage::Record * record, void ExpressionModel::tidy() const { m_layout = Layout(); m_expression = Expression(); - m_circular = 0; + m_circular = -1; } -Poincare::Expression ExpressionModel::BuildExpressionFromText(const char * c, CodePoint symbol) { +Poincare::Expression ExpressionModel::BuildExpressionFromText(const char * c, CodePoint symbol, Poincare::Context * context) { Expression expressionToStore; // if c = "", we want to reinit the Expression if (c && *c != 0) { // Compute the expression to store, without replacing symbols - expressionToStore = Expression::Parse(c); + expressionToStore = Expression::Parse(c, context); if (!expressionToStore.isUninitialized() && symbol != 0) { - expressionToStore = expressionToStore.replaceSymbolWithExpression(Symbol::Builder(symbol), Symbol::Builder(UCodePointUnknownX)); + expressionToStore = expressionToStore.replaceSymbolWithExpression(Symbol::Builder(symbol), Symbol::Builder(UCodePointUnknown)); } } return expressionToStore; diff --git a/apps/shared/expression_model.h b/apps/shared/expression_model.h index 87bade87c..b57ae5ac2 100644 --- a/apps/shared/expression_model.h +++ b/apps/shared/expression_model.h @@ -18,23 +18,21 @@ public: Poincare::Layout layout(const Ion::Storage::Record * record, CodePoint symbol = 0) const; // Setters - Ion::Storage::Record::ErrorStatus setContent(Ion::Storage::Record * record, const char * c, CodePoint symbol = 0); + Ion::Storage::Record::ErrorStatus setContent(Ion::Storage::Record * record, const char * c, Poincare::Context * context, CodePoint symbol = 0); Ion::Storage::Record::ErrorStatus setExpressionContent(Ion::Storage::Record * record, const Poincare::Expression & newExpression); - // Property - bool isCircularlyDefined(const Ion::Storage::Record * record, Poincare::Context * context) const; - virtual void * expressionAddress(const Ion::Storage::Record * record) const = 0; - virtual void tidy() const; protected: // Setters helper - static Poincare::Expression BuildExpressionFromText(const char * c, CodePoint symbol = 0); + static Poincare::Expression BuildExpressionFromText(const char * c, CodePoint symbol = 0, Poincare::Context * context = nullptr); mutable Poincare::Expression m_expression; mutable Poincare::Layout m_layout; private: virtual void updateNewDataWithExpression(Ion::Storage::Record * record, const Poincare::Expression & expressionToStore, void * expressionAddress, size_t expressionToStoreSize, size_t previousExpressionSize); + virtual void * expressionAddress(const Ion::Storage::Record * record) const = 0; virtual size_t expressionSize(const Ion::Storage::Record * record) const = 0; - mutable int m_circular; + bool isCircularlyDefined(const Ion::Storage::Record * record, Poincare::Context * context) const; + mutable int8_t m_circular; }; } diff --git a/apps/shared/expression_model_handle.h b/apps/shared/expression_model_handle.h index 585974c4d..b07c18e36 100644 --- a/apps/shared/expression_model_handle.h +++ b/apps/shared/expression_model_handle.h @@ -16,7 +16,6 @@ public: void text(char * buffer, size_t bufferSize) const { return model()->text(this, buffer, bufferSize, symbol()); } virtual Poincare::Expression expressionReduced(Poincare::Context * context) const { return model()->expressionReduced(this, context); } Poincare::Expression expressionClone() const { return model()->expressionClone(this); } - bool isCircularlyDefined(Poincare::Context * context) const { return model()->isCircularlyDefined(this, context); } Poincare::Layout layout() { return model()->layout(this, symbol()); } /* Here, isDefined is the exact contrary of isEmpty. However, for Sequence * inheriting from ExpressionModelHandle, isEmpty and isDefined have not exactly @@ -31,7 +30,7 @@ public: * behaviour but it is not true for its child classes (for example, in * Sequence). */ virtual void tidy() { model()->tidy(); } - Ion::Storage::Record::ErrorStatus setContent(const char * c) { return editableModel()->setContent(this, c, symbol()); } + Ion::Storage::Record::ErrorStatus setContent(const char * c, Poincare::Context * context) { return editableModel()->setContent(this, c, context, symbol()); } Ion::Storage::Record::ErrorStatus setExpressionContent(const Poincare::Expression & e) { return editableModel()->setExpressionContent(this, e); } protected: ExpressionModel * editableModel() { return const_cast(model()); } diff --git a/apps/shared/expression_model_list_controller.cpp b/apps/shared/expression_model_list_controller.cpp index bb160ec7a..a4dc882dc 100644 --- a/apps/shared/expression_model_list_controller.cpp +++ b/apps/shared/expression_model_list_controller.cpp @@ -216,7 +216,7 @@ void ExpressionModelListController::addEmptyModel() { } void ExpressionModelListController::reinitSelectedExpression(ExpiringPointer model) { - model->setContent(""); + model->setContent("", Container::activeApp()->localContext()); // Reset memoization of the selected cell which always corresponds to the k_memoizedCellsCount/2 memoized cell resetMemoizationForIndex(k_memoizedCellsCount/2); selectableTableView()->reloadData(); @@ -248,7 +248,7 @@ bool ExpressionModelListController::editSelectedRecordWithText(const char * text resetMemoizationForIndex(k_memoizedCellsCount/2); Ion::Storage::Record record = modelStore()->recordAtIndex(modelIndexForRow(selectedRow())); ExpiringPointer model = modelStore()->modelForRecord(record); - return (model->setContent(text) == Ion::Storage::Record::ErrorStatus::None); + return (model->setContent(text, Container::activeApp()->localContext()) == Ion::Storage::Record::ErrorStatus::None); } bool ExpressionModelListController::removeModelRow(Ion::Storage::Record record) { diff --git a/apps/shared/float_parameter_controller.cpp b/apps/shared/float_parameter_controller.cpp index 685d7f0ff..97196a6f3 100644 --- a/apps/shared/float_parameter_controller.cpp +++ b/apps/shared/float_parameter_controller.cpp @@ -48,6 +48,9 @@ void FloatParameterController::viewWillAppear() { template void FloatParameterController::willExitResponderChain(Responder * nextFirstResponder) { + if (nextFirstResponder == nullptr) { + return; + } if (parentResponder() == nullptr) { m_selectableTableView.deselectTable(); m_selectableTableView.scrollToCell(0,0); diff --git a/apps/shared/function.cpp b/apps/shared/function.cpp index 14ed1df0a..31b1e01e0 100644 --- a/apps/shared/function.cpp +++ b/apps/shared/function.cpp @@ -1,7 +1,6 @@ #include "function.h" #include "poincare_helpers.h" #include "poincare/src/parsing/parser.h" -#include #include #include #include @@ -11,42 +10,28 @@ using namespace Poincare; namespace Shared { -bool Function::BaseNameCompliant(const char * baseName, NameNotCompliantError * error) { +Function::NameNotCompliantError Function::BaseNameCompliant(const char * baseName) { assert(baseName[0] != 0); UTF8Decoder decoder(baseName); CodePoint c = decoder.nextCodePoint(); - if (UTF8Helper::CodePointIsNumber(c)) { - // The name cannot start with a number - if (error != nullptr) { - *error = NameNotCompliantError::NameCannotStartWithNumber; - } - return false; + if (c.isDecimalDigit()) { + return NameNotCompliantError::NameCannotStartWithNumber; } - // The name should only have allowed characters while (c != UCodePointNull) { - if (!(UTF8Helper::CodePointIsUpperCaseLetter(c) - || UTF8Helper::CodePointIsLowerCaseLetter(c) - || UTF8Helper::CodePointIsNumber(c)) - || c == '_') - { - if (error != nullptr) { - *error = NameNotCompliantError::CharacterNotAllowed; - } - return false; + // FIXME '_' should be accepted but not as first character + // TODO Factor this piece of code with similar one in the Parser + if (!(c.isDecimalDigit() || c.isLatinLetter()) || c == '_') { + return NameNotCompliantError::CharacterNotAllowed; } c = decoder.nextCodePoint(); } - // The name should not be a reserved name if (Parser::IsReservedName(baseName, strlen(baseName))) { - if (error != nullptr) { - *error = NameNotCompliantError::ReservedName; - } - return false; + return NameNotCompliantError::ReservedName; } - return true; + return NameNotCompliantError::None; } bool Function::isActive() const { diff --git a/apps/shared/function.h b/apps/shared/function.h index 617dba813..52bdff733 100644 --- a/apps/shared/function.h +++ b/apps/shared/function.h @@ -20,13 +20,14 @@ public: NameCannotStartWithNumber, ReservedName }; + static NameNotCompliantError BaseNameCompliant(const char * baseName); + /* Possible arguments: n, x, t, θ * The CodePoint θ is two char long. */ constexpr static int k_parenthesedArgumentCodePointLength = 3; constexpr static int k_parenthesedThetaArgumentByteLength = 4; constexpr static int k_parenthesedXNTArgumentByteLength = 3; constexpr static int k_maxNameWithArgumentSize = Poincare::SymbolAbstract::k_maxNameSize + k_parenthesedThetaArgumentByteLength; /* Function name and null-terminating char + "(θ)" */; - static bool BaseNameCompliant(const char * baseName, NameNotCompliantError * error = nullptr); // Constructors Function(Ion::Storage::Record record) : ExpressionModelHandle(record){} diff --git a/apps/shared/function_app.cpp b/apps/shared/function_app.cpp index 2b746e3e2..5a80460b5 100644 --- a/apps/shared/function_app.cpp +++ b/apps/shared/function_app.cpp @@ -37,7 +37,7 @@ void FunctionApp::willBecomeInactive() { bool FunctionApp::isAcceptableExpression(const Poincare::Expression expression) { /* We forbid functions whose type is equal because the input "2+f(3)" would be * simplify to an expression with an nested equal node which makes no sense. */ - if (!TextFieldDelegateApp::ExpressionCanBeSerialized(expression, false, Expression()) || expression.type() == ExpressionNode::Type::Equal) { + if (!TextFieldDelegateApp::ExpressionCanBeSerialized(expression, false, Expression(), localContext()) || expression.type() == ExpressionNode::Type::Equal) { return false; } return TextFieldDelegateApp::isAcceptableExpression(expression); diff --git a/apps/shared/function_expression_cell.cpp b/apps/shared/function_expression_cell.cpp index 9ca6fe9c4..c4ea79244 100644 --- a/apps/shared/function_expression_cell.cpp +++ b/apps/shared/function_expression_cell.cpp @@ -18,9 +18,9 @@ void FunctionExpressionCell::drawRect(KDContext * ctx, KDRect rect) const { } -void FunctionExpressionCell::layoutSubviews() { +void FunctionExpressionCell::layoutSubviews(bool force) { KDRect expressionFrame(m_leftMargin, 0, bounds().width() - m_leftMargin - m_rightMargin, bounds().height()-k_separatorThickness); - m_expressionView.setFrame(expressionFrame); + m_expressionView.setFrame(expressionFrame, force); } } diff --git a/apps/shared/function_expression_cell.h b/apps/shared/function_expression_cell.h index 34fc1a2f1..842842688 100644 --- a/apps/shared/function_expression_cell.h +++ b/apps/shared/function_expression_cell.h @@ -10,7 +10,7 @@ public: FunctionExpressionCell() : EvenOddExpressionCell() {} KDSize minimalSizeForOptimalDisplay() const override; void drawRect(KDContext * ctx, KDRect rect) const override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; private: constexpr static KDCoordinate k_separatorThickness = Metric::CellSeparatorThickness; }; diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index 02b2a9212..cf70f677d 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -135,21 +135,24 @@ FunctionStore * FunctionGraphController::functionStore() const { void FunctionGraphController::initCursorParameters() { Poincare::Context * context = textFieldDelegateApp()->localContext(); + const int activeFunctionsCount = functionStore()->numberOfActiveFunctions(); int functionIndex = 0; Coordinate2D xy; double t; do { - Ion::Storage::Record record = functionStore()->activeRecordAtIndex(functionIndex++); + Ion::Storage::Record record = functionStore()->activeRecordAtIndex(functionIndex); ExpiringPointer firstFunction = functionStore()->modelForRecord(record); t = defaultCursorT(record); xy = firstFunction->evaluateXYAtParameter(t, context); - } while ((std::isnan(xy.x2()) || std::isinf(xy.x2())) && functionIndex < functionStore()->numberOfActiveFunctions()); + } while ((std::isnan(xy.x2()) || std::isinf(xy.x2())) && ++functionIndex < activeFunctionsCount); + if (functionIndex == activeFunctionsCount) { + functionIndex = 0; + } m_cursor->moveTo(t, xy.x1(), xy.x2()); - functionIndex = (std::isnan(xy.x2()) || std::isinf(xy.x2())) ? 0 : functionIndex - 1; - selectFunctionWithCursor(functionIndex); if (interactiveCurveViewRange()->yAuto()) { interactiveCurveViewRange()->panToMakePointVisible(xy.x1(), xy.x2(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); } + selectFunctionWithCursor(functionIndex); } bool FunctionGraphController::moveCursorVertically(int direction) { diff --git a/apps/shared/function_list_controller.cpp b/apps/shared/function_list_controller.cpp index ae1548b3d..6adb5dfaa 100644 --- a/apps/shared/function_list_controller.cpp +++ b/apps/shared/function_list_controller.cpp @@ -207,6 +207,7 @@ void FunctionListController::didEnterResponderChain(Responder * previousFirstRes void FunctionListController::willExitResponderChain(Responder * nextFirstResponder) { if (nextFirstResponder == tabController()) { + assert(tabController() != nullptr); selectableTableView()->deselectTable(); footer()->setSelectedButton(-1); } diff --git a/apps/shared/global_context.cpp b/apps/shared/global_context.cpp index 6f214a5f9..8109f9468 100644 --- a/apps/shared/global_context.cpp +++ b/apps/shared/global_context.cpp @@ -32,6 +32,16 @@ void GlobalContext::DestroyRecordsBaseNamedWithoutExtension(const char * baseNam } } +Context::SymbolAbstractType GlobalContext::expressionTypeForIdentifier(const char * identifier, int length) { + const char * extension = Ion::Storage::sharedStorage()->extensionOfRecordBaseNamedWithExtensions(identifier, length, k_extensions, k_numberOfExtensions); + if (extension == nullptr) { + return Context::SymbolAbstractType::None; + } + assert(k_numberOfExtensions == 2); + assert(extension == Ion::Storage::expExtension || extension == Ion::Storage::funcExtension); + return extension == Ion::Storage::expExtension ? Context::SymbolAbstractType::Symbol : Context::SymbolAbstractType::Function; +} + const Expression GlobalContext::expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone) { Ion::Storage::Record r = SymbolAbstractRecordWithBaseName(symbol.name()); return ExpressionForSymbolAndRecord(symbol, r); @@ -52,6 +62,9 @@ void GlobalContext::setExpressionForSymbolAbstract(const Expression & expression SetExpressionForActualSymbol(finalExpression, symbol, record); } else { assert(symbol.type() == ExpressionNode::Type::Function); + Expression child = symbol.childAtIndex(0); + assert(child.type() == ExpressionNode::Type::Symbol); + finalExpression = finalExpression.replaceSymbolWithExpression(static_cast(child), Symbol::Builder(UCodePointUnknown)); SetExpressionForFunction(finalExpression, symbol, record); } } @@ -79,7 +92,11 @@ const Expression GlobalContext::ExpressionForFunction(const SymbolAbstract & sym } /* An function record value has metadata before the expression. To get the * expression, use the function record handle. */ - return ContinuousFunction(r).expressionClone(); + Expression e = ContinuousFunction(r).expressionClone(); + if (!e.isUninitialized()) { + e = e.replaceSymbolWithExpression(Symbol::Builder(UCodePointUnknown), symbol.childAtIndex(0)); + } + return e; } Ion::Storage::Record::ErrorStatus GlobalContext::SetExpressionForActualSymbol(const Expression & expression, const SymbolAbstract & symbol, Ion::Storage::Record previousRecord) { diff --git a/apps/shared/global_context.h b/apps/shared/global_context.h index 79f7c069f..de6b53fc9 100644 --- a/apps/shared/global_context.h +++ b/apps/shared/global_context.h @@ -27,6 +27,7 @@ public: /* Expression for symbol * The expression recorded in global context is already an expression. * Otherwise, we would need the context and the angle unit to evaluate it */ + SymbolAbstractType expressionTypeForIdentifier(const char * identifier, int length) override; const Poincare::Expression expressionForSymbolAbstract(const Poincare::SymbolAbstract & symbol, bool clone) override; void setExpressionForSymbolAbstract(const Poincare::Expression & expression, const Poincare::SymbolAbstract & symbol) override; diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index aa3a0e31c..5c09086c1 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -35,38 +35,29 @@ InteractiveCurveViewController::InteractiveCurveViewController(Responder * paren { } -float InteractiveCurveViewController::addMargin(float x, float range, bool isVertical, bool isMin) { - /* We are adding margins. Let's name: - * - The current range: rangeBefore - * - The next range: rangeAfter - * - The bottom margin ratio with which we will evaluate if a point is too - * low on the screen: bottomRatioAfter - * - The bottom margin ratio with which we will evaluate if a point is too - * high on the screen: topRatioAfter - * - The ratios we need to use to create the margins: bottomRatioBefore and - * topRatioBefore - * - * We want to add margins so that: - * bottomRatioAfter*rangeAfter == bottomRatioBefore * rangeBefore - * topRatioAfter*rangeAfter == topRatioBefore * rangeBefore - * Knowing that: - * rangeAfter = (1+bottomRatioBefore+topRatioBefore)*rangeBefore - * - * We thus have: - * bottomRatioBefore = bottomRatioAfter / (1-bottomRatioAfter-topRatioAfter) - * topRatioBefore = topRatioAfter / (1-bottomRatioAfter-topRatioAfter) - * - * If we just used bottomRatioBefore = bottomRatioAfter and - * topRatioBefore = topRatioAfter, we would create too small margins and the - * controller might need to pan right after a Y auto calibration. */ - +float InteractiveCurveViewController::addMargin(float y, float range, bool isVertical, bool isMin) { + /* The provided min or max range limit y is altered by adding a margin. + * In pixels, the view's height occupied by the vertical range is equal to + * viewHeight - topMargin - bottomMargin. + * Hence one pixel must correspond to + * range / (viewHeight - topMargin - bottomMargin). + * Finally, adding topMargin pixels of margin, say at the top, comes down + * to adding + * range * topMargin / (viewHeight - topMargin - bottomMargin) + * which is equal to + * range * topMarginRatio / ( 1 - topMarginRatio - bottomMarginRatio) + * where + * topMarginRation = topMargin / viewHeight + * bottomMarginRatio = bottomMargin / viewHeight. + * The same goes horizontally. + */ float topMarginRatio = isVertical ? cursorTopMarginRatio() : k_cursorRightMarginRatio; float bottomMarginRatio = isVertical ? cursorBottomMarginRatio() : k_cursorLeftMarginRatio; assert(topMarginRatio + bottomMarginRatio < 1); // Assertion so that the formula is correct float ratioDenominator = 1 - bottomMarginRatio - topMarginRatio; float ratio = isMin ? -bottomMarginRatio : topMarginRatio; ratio = ratio / ratioDenominator; - return x+ratio*range; + return y + ratio * range; } const char * InteractiveCurveViewController::title() { @@ -174,7 +165,8 @@ void InteractiveCurveViewController::viewDidDisappear() { } void InteractiveCurveViewController::willExitResponderChain(Responder * nextFirstResponder) { - if (nextFirstResponder == nullptr || nextFirstResponder == tabController()) { + if (nextFirstResponder == tabController()) { + assert(tabController() != nullptr); curveView()->selectMainView(false); header()->setSelectedButton(-1); curveView()->reload(); diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 3c4f6b9a5..7e5f9680d 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -10,6 +10,8 @@ using namespace Poincare; namespace Shared { +static inline float maxFloat(float x, float y) { return x > y ? x : y; } + uint32_t InteractiveCurveViewRange::rangeChecksum() { float data[5] = {xMin(), xMax(), yMin(), yMax(), m_yAuto ? 1.0f : 0.0f}; size_t dataLengthInBytes = 5*sizeof(float); @@ -89,18 +91,23 @@ void InteractiveCurveViewRange::roundAbscissa() { void InteractiveCurveViewRange::normalize() { /* We center the ranges on the current range center, and put each axis so that - * 1cm = 2 units. */ + * 1cm = 2 current units. */ m_yAuto = false; + + const float unit = maxFloat(xGridUnit(), yGridUnit()); + // Set x range - float newXMin = xCenter() - NormalizedXHalfRange(); - float newXMax = xCenter() + NormalizedXHalfRange(); + float newXHalfRange = NormalizedXHalfRange(unit); + float newXMin = xCenter() - newXHalfRange; + float newXMax = xCenter() + newXHalfRange; if (!std::isnan(newXMin) && !std::isnan(newXMax)) { m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetXMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat); } // Set y range - float newYMin = yCenter() - NormalizedYHalfRange(); - float newYMax = yCenter() + NormalizedYHalfRange(); + float newYHalfRange = NormalizedYHalfRange(unit); + float newYMin = yCenter() - newYHalfRange; + float newYMax = yCenter() + newYHalfRange; if (!std::isnan(newYMin) && !std::isnan(newYMax)) { m_yRange.setMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetYMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat); @@ -152,7 +159,11 @@ void InteractiveCurveViewRange::setDefault() { xRange = xMax() - xMin(); yRange = yMax() - yMin(); float xyRatio = xRange/yRange; - float normalizedXYRatio = NormalizedXHalfRange()/NormalizedYHalfRange(); + + const float unit = maxFloat(xGridUnit(), yGridUnit()); + const float newXHalfRange = NormalizedXHalfRange(unit); + const float newYHalfRange = NormalizedYHalfRange(unit); + float normalizedXYRatio = newXHalfRange/newYHalfRange; if (xyRatio < normalizedXYRatio) { float newXRange = normalizedXYRatio * yRange; assert(newXRange > xRange); @@ -160,7 +171,7 @@ void InteractiveCurveViewRange::setDefault() { m_xRange.setMin(xMin() - delta, k_lowerMaxFloat, k_upperMaxFloat); MemoizedCurveViewRange::protectedSetXMax(xMax()+delta, k_lowerMaxFloat, k_upperMaxFloat); } else if (xyRatio > normalizedXYRatio) { - float newYRange = NormalizedYHalfRange()/NormalizedXHalfRange() * xRange; + float newYRange = newYHalfRange/newXHalfRange * xRange; assert(newYRange > yRange); float delta = (newYRange - yRange) / 2.0f; m_yRange.setMin(yMin() - delta, k_lowerMaxFloat, k_upperMaxFloat); diff --git a/apps/shared/interactive_curve_view_range.h b/apps/shared/interactive_curve_view_range.h index 6392b82ee..f6484017a 100644 --- a/apps/shared/interactive_curve_view_range.h +++ b/apps/shared/interactive_curve_view_range.h @@ -46,15 +46,17 @@ protected: /* In normalized settings, we put each axis so that 1cm = 2 units. For now, * the screen has size 43.2mm * 57.6mm. * We want: - * 2*NormalizedXHalfRange -> 57.6mm - * 2*1 -> 10.0mm - * So NormalizedXHalfRange = 5.76 + * 2 * normalizedXHalfRange -> 57.6mm + * 2 * 1 unit -> 10.0mm + * So normalizedXHalfRange = 57.6mm * 1 unit / 10.0mm */ + constexpr static float NormalizedXHalfRange(float unit) { return 5.76f * unit; } + /* In normalized settings, we put each axis so that 1cm = 2 units. For now, + * the screen has size 43.2mm * 57.6mm. * We want: - * 2*NormalizedYHalfRange -> 43.2mm * 170/240 - * 2*1 -> 10.0mm - * So NormalizedYHalfRange = 3.06 */ - constexpr static float NormalizedXHalfRange() { return 5.76f; } - constexpr static float NormalizedYHalfRange() { return 3.06f; } + * 2 * normalizedYHalfRange -> 43.2mm * 170/240 + * 2 * 1 unit -> 10.0mm + * So normalizedYHalfRange = 43.2mm * 170/240 * 1 unit / 10.0mm */ + constexpr static float NormalizedYHalfRange(float unit) { return 3.06f * unit; } bool m_yAuto; InteractiveCurveViewRangeDelegate * m_delegate; private: diff --git a/apps/shared/layout_field_delegate.h b/apps/shared/layout_field_delegate.h index 042d7dfe2..79f25607d 100644 --- a/apps/shared/layout_field_delegate.h +++ b/apps/shared/layout_field_delegate.h @@ -12,6 +12,7 @@ public: bool layoutFieldDidFinishEditing(LayoutField * layoutField, Poincare::Layout layoutR, Ion::Events::Event event) override; bool layoutFieldDidAbortEditing(LayoutField * layoutField) override; void layoutFieldDidChangeSize(LayoutField * layoutField) override; + Poincare::Context * context() const override { return expressionFieldDelegateApp()->localContext(); } protected: ExpressionFieldDelegateApp * expressionFieldDelegateApp() const { return static_cast(Container::activeApp()); diff --git a/apps/shared/message_view.cpp b/apps/shared/message_view.cpp index 5a805f244..3bc257ab0 100644 --- a/apps/shared/message_view.cpp +++ b/apps/shared/message_view.cpp @@ -23,15 +23,15 @@ View * MessageView::subviewAtIndex(int index) { return &(m_messageTextViews[index]); } -void MessageView::layoutSubviews() { +void MessageView::layoutSubviews(bool force) { if (m_numberOfMessages == 0) { return; } KDCoordinate width = bounds().width(); KDCoordinate titleHeight = m_messageTextViews[0].minimalSizeForOptimalDisplay().height(); KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height(); - m_messageTextViews[0].setFrame(KDRect(0, k_titleMargin, width, titleHeight)); + m_messageTextViews[0].setFrame(KDRect(0, k_titleMargin, width, titleHeight), force); for (uint8_t i = 1; i < m_numberOfMessages; i++) { - m_messageTextViews[i].setFrame(KDRect(0, k_paragraphHeight + (i-1) * textHeight, width, textHeight)); + m_messageTextViews[i].setFrame(KDRect(0, k_paragraphHeight + (i-1) * textHeight, width, textHeight), force); } } diff --git a/apps/shared/message_view.h b/apps/shared/message_view.h index bcd110a4d..41fb9e648 100644 --- a/apps/shared/message_view.h +++ b/apps/shared/message_view.h @@ -10,7 +10,7 @@ public: protected: int numberOfSubviews() const override { return m_numberOfMessages; } View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; private: constexpr static KDCoordinate k_titleMargin = 40; constexpr static KDCoordinate k_paragraphHeight = 90; diff --git a/apps/shared/poincare_helpers.h b/apps/shared/poincare_helpers.h index 0f2ba050e..31010f61f 100644 --- a/apps/shared/poincare_helpers.h +++ b/apps/shared/poincare_helpers.h @@ -50,25 +50,26 @@ inline T ApproximateWithValueForSymbol(const Poincare::Expression e, const char } template -inline T ApproximateToScalar(const char * text, Poincare::Context * context, bool symbolicComputation = true) { +inline T ApproximateToScalar(const char * text, Poincare::Context * context, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition) { Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); Poincare::Preferences::ComplexFormat complexFormat = Poincare::Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), text); return Poincare::Expression::ApproximateToScalar(text, context, complexFormat, preferences->angleUnit(), symbolicComputation); } -inline Poincare::Expression ParseAndSimplify(const char * text, Poincare::Context * context, bool symbolicComputation = true) { +inline Poincare::Expression ParseAndSimplify(const char * text, Poincare::Context * context, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition) { Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); Poincare::Preferences::ComplexFormat complexFormat = Poincare::Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), text); return Poincare::Expression::ParseAndSimplify(text, context, complexFormat, preferences->angleUnit(), symbolicComputation); } -inline void Simplify(Poincare::Expression * e, Poincare::Context * context, Poincare::ExpressionNode::ReductionTarget target, bool symbolicComputation = true) { +inline void Simplify(Poincare::Expression * e, Poincare::Context * context, Poincare::ExpressionNode::ReductionTarget target, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition) { Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); Poincare::Preferences::ComplexFormat complexFormat = Poincare::Expression::UpdatedComplexFormatWithExpressionInput(preferences->complexFormat(), *e, context); - *e = e->simplify(context, complexFormat, preferences->angleUnit(), target, symbolicComputation); + + *e = e->simplify(Poincare::ExpressionNode::ReductionContext(context, complexFormat, preferences->angleUnit(), target, symbolicComputation)); } -inline void ParseAndSimplifyAndApproximate(const char * text, Poincare::Expression * simplifiedExpression, Poincare::Expression * approximateExpression, Poincare::Context * context, bool symbolicComputation = true) { +inline void ParseAndSimplifyAndApproximate(const char * text, Poincare::Expression * simplifiedExpression, Poincare::Expression * approximateExpression, Poincare::Context * context, Poincare::ExpressionNode::SymbolicComputation symbolicComputation = Poincare::ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition) { Poincare::Preferences * preferences = Poincare::Preferences::sharedPreferences(); Poincare::Preferences::ComplexFormat complexFormat = Poincare::Expression::UpdatedComplexFormatWithTextInput(preferences->complexFormat(), text); Poincare::Expression::ParseAndSimplifyAndApproximate(text, simplifiedExpression, approximateExpression, context, complexFormat, preferences->angleUnit(), symbolicComputation); diff --git a/apps/shared/round_cursor_view.cpp b/apps/shared/round_cursor_view.cpp index 326562aeb..2b800b3f0 100644 --- a/apps/shared/round_cursor_view.cpp +++ b/apps/shared/round_cursor_view.cpp @@ -2,43 +2,39 @@ namespace Shared { -static constexpr KDCoordinate cursorSize = 10; -static const uint8_t cursorMask[cursorSize][cursorSize] = { - {0xFF, 0xFF, 0xFF, 0xED, 0xB6, 0xB6, 0xED, 0xFF, 0xFF, 0xFF}, - {0xFF, 0xFF, 0x7C, 0x06, 0x00, 0x00, 0x06, 0x7C, 0xFF, 0xFF}, - {0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xFF}, - {0xED, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5}, - {0xB6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB6}, - {0xB6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB6}, - {0xED, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5}, - {0xFF, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xFF}, - {0xFF, 0xFF, 0x7C, 0x06, 0x00, 0x00, 0x06, 0x7C, 0xFF, 0xFF}, - {0xFF, 0xFF, 0xFF, 0xED, 0xB6, 0xB6, 0xED, 0xFF, 0xFF, 0xFF}, -}; +static KDColor s_cursorWorkingBuffer[Dots::LargeDotDiameter*Dots::LargeDotDiameter]; void RoundCursorView::drawRect(KDContext * ctx, KDRect rect) const { KDRect r = bounds(); - KDColor cursorWorkingBuffer[cursorSize*cursorSize]; +#ifdef GRAPH_CURSOR_SPEEDUP + /* Beware that only the pixels of the intersection of rect with KDContext's + * clipping rect are pulled. All other pixels are left unaltered. Indeed + * nothing outside the clipping rect should be redrawn and hence was not + * dirty. + */ ctx->getPixels(r, m_underneathPixelBuffer); m_underneathPixelBufferLoaded = true; - ctx->blendRectWithMask(r, m_color, (const uint8_t *)cursorMask, cursorWorkingBuffer); +#endif + ctx->blendRectWithMask(r, m_color, (const uint8_t *)Dots::LargeDotMask, s_cursorWorkingBuffer); } KDSize RoundCursorView::minimalSizeForOptimalDisplay() const { - return KDSize(cursorSize, cursorSize); + return KDSize(k_cursorSize, k_cursorSize); } void RoundCursorView::setColor(KDColor color) { m_color = color; +#ifdef GRAPH_CURSOR_SPEEDUP eraseCursorIfPossible(); +#endif markRectAsDirty(bounds()); } -void RoundCursorView::setCursorFrame(KDRect f) { -#if GRAPH_CURSOR_SPEEDUP +void RoundCursorView::setCursorFrame(KDRect f, bool force) { +#ifdef GRAPH_CURSOR_SPEEDUP /* TODO This is quite dirty (we are out of the dirty tracking and we assume * the cursor is the upmost view) but it works well. */ - if (m_frame == f) { + if (m_frame == f && !force) { return; } /* We want to avoid drawing the curve just because the cursor has been @@ -50,7 +46,7 @@ void RoundCursorView::setCursorFrame(KDRect f) { return; } #endif - CursorView::setCursorFrame(f); + CursorView::setCursorFrame(f, force); } #ifdef GRAPH_CURSOR_SPEEDUP diff --git a/apps/shared/round_cursor_view.h b/apps/shared/round_cursor_view.h index 025d06ee3..332467bd1 100644 --- a/apps/shared/round_cursor_view.h +++ b/apps/shared/round_cursor_view.h @@ -2,18 +2,24 @@ #define SHARED_ROUND_CURSOR_VIEW_H #include "cursor_view.h" +#include "dots.h" namespace Shared { -#define GRAPH_CURSOR_SPEEDUP 1 +#define GRAPH_CURSOR_SPEEDUP class RoundCursorView : public CursorView { public: - RoundCursorView(KDColor color = Palette::PrimaryText) : m_color(color), m_underneathPixelBufferLoaded(false) {} + RoundCursorView(KDColor color = Palette::PrimaryText) : + m_color(color) +#ifdef GRAPH_CURSOR_SPEEDUP + , m_underneathPixelBufferLoaded(false) +#endif + {} void drawRect(KDContext * ctx, KDRect rect) const override; KDSize minimalSizeForOptimalDisplay() const override; void setColor(KDColor color); - void setCursorFrame(KDRect frame) override; + void setCursorFrame(KDRect frame, bool force) override; #ifdef GRAPH_CURSOR_SPEEDUP void resetMemoization() const { m_underneathPixelBufferLoaded = false; } #endif @@ -21,10 +27,12 @@ private: #ifdef GRAPH_CURSOR_SPEEDUP bool eraseCursorIfPossible(); #endif - constexpr static int k_cursorSize = 10; - mutable KDColor m_underneathPixelBuffer[k_cursorSize*k_cursorSize]; + constexpr static int k_cursorSize = Dots::LargeDotDiameter; KDColor m_color; +#ifdef GRAPH_CURSOR_SPEEDUP + mutable KDColor m_underneathPixelBuffer[k_cursorSize*k_cursorSize]; mutable bool m_underneathPixelBufferLoaded; +#endif }; } diff --git a/apps/shared/scrollable_exact_approximate_expressions_cell.cpp b/apps/shared/scrollable_exact_approximate_expressions_cell.cpp deleted file mode 100644 index a3f912ea2..000000000 --- a/apps/shared/scrollable_exact_approximate_expressions_cell.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "scrollable_exact_approximate_expressions_cell.h" -#include -using namespace Poincare; - -namespace Shared { - -ScrollableExactApproximateExpressionsCell::ScrollableExactApproximateExpressionsCell(Responder * parentResponder) : - Responder(parentResponder), - m_view(this) -{ -} - -void ScrollableExactApproximateExpressionsCell::setLayouts(Poincare::Layout approximateLayout, Poincare::Layout exactLayout) { - m_view.setLayouts(approximateLayout, exactLayout); - m_view.setSelectedSubviewPosition(ScrollableExactApproximateExpressionsView::SubviewPosition::Left); -} - -void ScrollableExactApproximateExpressionsCell::setHighlighted(bool highlight) { - m_view.evenOddCell()->setHighlighted(highlight); - reloadScroll(); -} - -void ScrollableExactApproximateExpressionsCell::setEven(bool even) { - EvenOddCell::setEven(even); - m_view.setBackgroundColor(backgroundColor()); - m_view.evenOddCell()->setEven(even); -} - -void ScrollableExactApproximateExpressionsCell::reloadScroll() { - m_view.reloadScroll(); -} - -void ScrollableExactApproximateExpressionsCell::didBecomeFirstResponder() { - m_view.setSelectedSubviewPosition(ScrollableExactApproximateExpressionsView::SubviewPosition::Left); - Container::activeApp()->setFirstResponder(&m_view); -} - -int ScrollableExactApproximateExpressionsCell::numberOfSubviews() const { - return 1; -} - -View * ScrollableExactApproximateExpressionsCell::subviewAtIndex(int index) { - return &m_view; -} - -void ScrollableExactApproximateExpressionsCell::layoutSubviews() { - m_view.setFrame(bounds()); -} - -} diff --git a/apps/shared/scrollable_exact_approximate_expressions_view.h b/apps/shared/scrollable_exact_approximate_expressions_view.h deleted file mode 100644 index b24dd9fa6..000000000 --- a/apps/shared/scrollable_exact_approximate_expressions_view.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef SHARED_SCROLLABLE_EXACT_APPROXIMATE_EXPRESSIONS_VIEW_H -#define SHARED_SCROLLABLE_EXACT_APPROXIMATE_EXPRESSIONS_VIEW_H - -#include - -namespace Shared { - -class ScrollableExactApproximateExpressionsView : public ScrollableView, public ScrollViewDataSource { -public: - enum class SubviewPosition { - Left, - Right - }; - ScrollableExactApproximateExpressionsView(Responder * parentResponder); - ::EvenOddCell * evenOddCell() { - return &m_contentCell; - } - void setLayouts(Poincare::Layout rightlayout, Poincare::Layout leftLayout); - void setEqualMessage(I18n::Message equalSignMessage); - SubviewPosition selectedSubviewPosition() { - return m_contentCell.selectedSubviewPosition(); - } - void setSelectedSubviewPosition(SubviewPosition subviewPosition) { - m_contentCell.setSelectedSubviewPosition(subviewPosition); - } - void setDisplayLeftLayout(bool display) { m_contentCell.setDisplayLeftExpression(display); } - void reloadScroll(); - void didBecomeFirstResponder() override; - bool handleEvent(Ion::Events::Event event) override; - Poincare::Layout layout() const { - return m_contentCell.layout(); - } -private: - class ContentCell : public ::EvenOddCell { - public: - ContentCell(); - KDColor backgroundColor() const override; - void setHighlighted(bool highlight) override; - void setEven(bool even) override; - void reloadTextColor(); - KDSize minimalSizeForOptimalDisplay() const override; - ExpressionView * rightExpressionView() { - return &m_rightExpressionView; - } - ExpressionView * leftExpressionView() { - return &m_leftExpressionView; - } - MessageTextView * approximateSign() { - return &m_approximateSign; - } - SubviewPosition selectedSubviewPosition() { - return m_selectedSubviewPosition; - } - void setSelectedSubviewPosition(SubviewPosition subviewPosition); - bool displayLeftExpression() const { return m_displayLeftExpression; } - void setDisplayLeftExpression(bool display); - void layoutSubviews() override; - int numberOfSubviews() const override; - Poincare::Layout layout() const override; - private: - View * subviewAtIndex(int index) override; - ExpressionView m_rightExpressionView; - MessageTextView m_approximateSign; - ExpressionView m_leftExpressionView; - SubviewPosition m_selectedSubviewPosition; - bool m_displayLeftExpression; - }; - ContentCell m_contentCell; -}; - -} - -#endif diff --git a/apps/shared/scrollable_multiple_expressions_view.cpp b/apps/shared/scrollable_multiple_expressions_view.cpp new file mode 100644 index 000000000..f61000837 --- /dev/null +++ b/apps/shared/scrollable_multiple_expressions_view.cpp @@ -0,0 +1,231 @@ +#include "scrollable_multiple_expressions_view.h" +#include +#include +using namespace Poincare; + +namespace Shared { + +static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { return x > y ? x : y; } + +AbstractScrollableMultipleExpressionsView::ContentCell::ContentCell() : + m_rightExpressionView(), + m_approximateSign(KDFont::LargeFont, I18n::Message::AlmostEqual, 0.5f, 0.5f, Palette::GreyVeryDark), + m_centeredExpressionView(), + m_selectedSubviewPosition(SubviewPosition::Center), + m_displayCenter(true) +{ +} + +KDColor AbstractScrollableMultipleExpressionsView::ContentCell::backgroundColor() const { + KDColor background = m_even ? KDColorWhite : Palette::WallScreen; + return background; +} + +void AbstractScrollableMultipleExpressionsView::ContentCell::setHighlighted(bool highlight) { + // Do not call HighlightCell::setHighlighted to avoid marking all cell as dirty + m_highlighted = highlight; + KDColor defaultColor = backgroundColor(); + KDColor color = highlight && m_selectedSubviewPosition == SubviewPosition::Center ? Palette::Select : defaultColor; + m_centeredExpressionView.setBackgroundColor(color); + color = highlight && m_selectedSubviewPosition == SubviewPosition::Right ? Palette::Select : defaultColor; + m_rightExpressionView.setBackgroundColor(color); + m_approximateSign.setBackgroundColor(defaultColor); + if (leftExpressionView()) { + color = highlight && m_selectedSubviewPosition == SubviewPosition::Left ? Palette::Select : defaultColor; + leftExpressionView()->setBackgroundColor(color); + } +} + +void AbstractScrollableMultipleExpressionsView::ContentCell::setEven(bool even) { + EvenOddCell::setEven(even); + KDColor defaultColor = backgroundColor(); + m_centeredExpressionView.setBackgroundColor(defaultColor); + m_rightExpressionView.setBackgroundColor(defaultColor); + m_approximateSign.setBackgroundColor(defaultColor); + if (leftExpressionView()) { + leftExpressionView()->setBackgroundColor(defaultColor); + } +} + +void AbstractScrollableMultipleExpressionsView::ContentCell::reloadTextColor() { + if (displayCenter()) { + m_rightExpressionView.setTextColor(Palette::GreyVeryDark); + } else { + m_rightExpressionView.setTextColor(KDColorBlack); + } +} + +KDSize AbstractScrollableMultipleExpressionsView::ContentCell::minimalSizeForOptimalDisplay() const { + KDSize leftSize = KDSizeZero; + KDCoordinate leftViewBaseline = 0; + KDCoordinate width = 0; + if (leftExpressionView() && !leftExpressionView()->layout().isUninitialized()) { + leftSize = leftExpressionView()->minimalSizeForOptimalDisplay(); + leftViewBaseline = leftExpressionView()->layout().baseline(); + width += leftSize.width() + Metric::CommonLargeMargin; + } + KDSize rightExpressionSize = m_rightExpressionView.minimalSizeForOptimalDisplay(); + width += rightExpressionSize.width(); + Layout l = m_rightExpressionView.layout(); + KDCoordinate rightBaseline = l.isUninitialized() ? 0 : l.baseline(); + KDSize centeredExpressionSize = KDSizeZero; + KDCoordinate centeredBaseline = 0; + if (displayCenter()) { + centeredBaseline = m_centeredExpressionView.layout().baseline(); + centeredExpressionSize = m_centeredExpressionView.minimalSizeForOptimalDisplay(); + width += centeredExpressionSize.width() + 2*Metric::CommonLargeMargin + m_approximateSign.minimalSizeForOptimalDisplay().width(); + } + KDCoordinate height = maxCoordinate(maxCoordinate(centeredBaseline, rightBaseline), leftViewBaseline) + maxCoordinate(maxCoordinate(centeredExpressionSize.height()-centeredBaseline, rightExpressionSize.height()-rightBaseline), leftSize.height()-leftViewBaseline); + return KDSize(width, height); +} + +void AbstractScrollableMultipleExpressionsView::ContentCell::setSelectedSubviewPosition(AbstractScrollableMultipleExpressionsView::SubviewPosition subviewPosition) { + m_selectedSubviewPosition = subviewPosition; + setHighlighted(isHighlighted()); +} + +void AbstractScrollableMultipleExpressionsView::ContentCell::setDisplayCenter(bool display) { + m_displayCenter = display; + reloadTextColor(); + layoutSubviews(); +} + +Poincare::Layout AbstractScrollableMultipleExpressionsView::ContentCell::layout() const { + if (m_selectedSubviewPosition == SubviewPosition::Center) { + return m_centeredExpressionView.layout(); + } else if (m_selectedSubviewPosition == SubviewPosition::Right) { + return m_rightExpressionView.layout(); + } + assert(m_selectedSubviewPosition == SubviewPosition::Left); + assert(leftExpressionView()); + return leftExpressionView()->layout(); +} + +int AbstractScrollableMultipleExpressionsView::ContentCell::numberOfSubviews() const { + int nbOfSubviews = 1; + if (displayCenter()) { + nbOfSubviews += 2; + } + if (leftExpressionView()) { + nbOfSubviews += 1; + } + return nbOfSubviews; +} + +View * AbstractScrollableMultipleExpressionsView::ContentCell::subviewAtIndex(int index) { + bool leftIsVisible = leftExpressionView() != nullptr; + if (leftIsVisible && index == 0) { + return leftExpressionView(); + } + View * views[3] = {&m_rightExpressionView, &m_approximateSign, &m_centeredExpressionView}; + return views[index - leftIsVisible]; +} + +void AbstractScrollableMultipleExpressionsView::ContentCell::layoutSubviews(bool force) { + // Subviews sizes + KDSize leftSize = leftExpressionView() ? leftExpressionView()->minimalSizeForOptimalDisplay() : KDSizeZero; + KDCoordinate leftViewBaseline = leftExpressionView() && !leftExpressionView()->layout().isUninitialized() ? leftExpressionView()->layout().baseline() : 0; + KDSize centeredExpressionSize = KDSizeZero; + KDCoordinate centeredBaseline = 0; + if (displayCenter()) { + centeredBaseline = m_centeredExpressionView.layout().baseline(); + centeredExpressionSize = m_centeredExpressionView.minimalSizeForOptimalDisplay(); + } + KDSize rightExpressionSize = m_rightExpressionView.minimalSizeForOptimalDisplay(); + KDCoordinate rightBaseline = m_rightExpressionView.layout().isUninitialized() ? 0 : m_rightExpressionView.layout().baseline(); + // Compute baseline + KDCoordinate baseline = maxCoordinate(maxCoordinate(leftViewBaseline, rightBaseline), centeredBaseline); + // Layout left view + KDCoordinate currentWidth = 0; + if (leftExpressionView()) { + leftExpressionView()->setFrame(KDRect(currentWidth, baseline-leftViewBaseline, leftSize), force); + currentWidth += leftSize.width() + Metric::CommonLargeMargin; + } + // Layout centered expression + if (displayCenter()) { + KDSize approximateSignSize = m_approximateSign.minimalSizeForOptimalDisplay(); + m_centeredExpressionView.setFrame(KDRect(currentWidth, baseline-centeredBaseline, centeredExpressionSize), force); + currentWidth += Metric::CommonLargeMargin+centeredExpressionSize.width(); + m_approximateSign.setFrame(KDRect(currentWidth, baseline-approximateSignSize.height()/2, approximateSignSize), force); + currentWidth += Metric::CommonLargeMargin + approximateSignSize.width(); + } + // Layout right expression + m_rightExpressionView.setFrame(KDRect(currentWidth, baseline-rightBaseline, rightExpressionSize), force); +} + +AbstractScrollableMultipleExpressionsView::AbstractScrollableMultipleExpressionsView(Responder * parentResponder, View * contentCell) : + ScrollableView(parentResponder, contentCell, this) +{ + setDecoratorType(ScrollView::Decorator::Type::Arrows); +} + +void AbstractScrollableMultipleExpressionsView::setLayouts(Poincare::Layout leftLayout, Poincare::Layout centerLayout, Poincare::Layout rightLayout) { + bool updateRightLayout = contentCell()->rightExpressionView()->setLayout(rightLayout); + bool updateCenterLayout = contentCell()->centeredExpressionView()->setLayout(centerLayout); + bool updateLeftLayout = false; + if (contentCell()->leftExpressionView()) { + updateLeftLayout = contentCell()->leftExpressionView()->setLayout(leftLayout); + } + if (updateLeftLayout || updateCenterLayout || updateRightLayout) { + contentCell()->reloadTextColor(); + contentCell()->layoutSubviews(); + // Do no reload scroll here as 'setLayouts' is called every time the table is re-layout (when scrolling for instance) + } +} + +void AbstractScrollableMultipleExpressionsView::setEqualMessage(I18n::Message equalSignMessage) { + contentCell()->approximateSign()->setMessage(equalSignMessage); +} + +void AbstractScrollableMultipleExpressionsView::reloadScroll() { + if (selectedSubviewPosition() == SubviewPosition::Right) { + // Scroll to the right extremity + scrollToContentPoint(KDPoint(contentCell()->bounds().width(), 0), true); + } else { + // Scroll to the left extremity + ScrollableView::reloadScroll(); + } +} +void AbstractScrollableMultipleExpressionsView::setDisplayCenter(bool display) { + contentCell()->setDisplayCenter(display); + layoutSubviews(); +} + +bool AbstractScrollableMultipleExpressionsView::handleEvent(Ion::Events::Event event) { + bool leftIsVisible = false; + KDCoordinate leftWidth = 0; + if (contentCell()->leftExpressionView()) { + leftWidth = contentCell()->leftExpressionView()->minimalSizeForOptimalDisplay().width(); + leftIsVisible = leftWidth - contentOffset().x() > 0; + } + KDCoordinate rightExpressionWidth = contentCell()->rightExpressionView()->minimalSizeForOptimalDisplay().width(); + bool rightExpressionIsVisible = minimalSizeForOptimalDisplay().width() - rightExpressionWidth - contentOffset().x() < bounds().width(); + bool centeredExpressionIsVisibleOnTheLeft = false; + bool centeredExpressionIsVisibleOnTheRight = false; + if (contentCell()->displayCenter()) { + KDCoordinate centerExpressionWidth = contentCell()->centeredExpressionView()->minimalSizeForOptimalDisplay().width(); + KDCoordinate signWidth = contentCell()->approximateSign()->minimalSizeForOptimalDisplay().width(); + centeredExpressionIsVisibleOnTheLeft = leftWidth + Metric::CommonLargeMargin + centerExpressionWidth - contentOffset().x() > 0; + centeredExpressionIsVisibleOnTheRight = minimalSizeForOptimalDisplay().width() - rightExpressionWidth - signWidth - centerExpressionWidth - 2*Metric::CommonLargeMargin - contentOffset().x() < bounds().width(); + } + // Select center + if ((event == Ion::Events::Left && selectedSubviewPosition() == SubviewPosition::Right && centeredExpressionIsVisibleOnTheLeft) || + (event == Ion::Events::Right && selectedSubviewPosition() == SubviewPosition::Left && centeredExpressionIsVisibleOnTheRight)) { + setSelectedSubviewPosition(SubviewPosition::Center); + return true; + } + // Select left + if ((event == Ion::Events::Left && selectedSubviewPosition() == SubviewPosition::Right && leftIsVisible) || + (event == Ion::Events::Left && selectedSubviewPosition() == SubviewPosition::Center && leftIsVisible)) { + setSelectedSubviewPosition(SubviewPosition::Left); + return true; + } + if ((event == Ion::Events::Right && selectedSubviewPosition() == SubviewPosition::Center && rightExpressionIsVisible) || + (event == Ion::Events::Right && selectedSubviewPosition() == SubviewPosition::Left && rightExpressionIsVisible)) { + setSelectedSubviewPosition(SubviewPosition::Right); + return true; + } + return ScrollableView::handleEvent(event); +} + +} diff --git a/apps/shared/scrollable_multiple_expressions_view.h b/apps/shared/scrollable_multiple_expressions_view.h new file mode 100644 index 000000000..2a561ac41 --- /dev/null +++ b/apps/shared/scrollable_multiple_expressions_view.h @@ -0,0 +1,95 @@ +#ifndef SHARED_SCROLLABLE_MULTIPLE_EXPRESSIONS_VIEW_H +#define SHARED_SCROLLABLE_MULTIPLE_EXPRESSIONS_VIEW_H + +#include + +namespace Shared { + +class AbstractScrollableMultipleExpressionsView : public ScrollableView, public ScrollViewDataSource { +public: + enum class SubviewPosition : uint8_t { + Left = 0, + Center = 1, + Right = 2 + }; + AbstractScrollableMultipleExpressionsView(Responder * parentResponder, View * contentCell); + ::EvenOddCell * evenOddCell() { + return contentCell(); + } + void setLayouts(Poincare::Layout leftLayout, Poincare::Layout centerlayout, Poincare::Layout rightLayout); + void setEqualMessage(I18n::Message equalSignMessage); + SubviewPosition selectedSubviewPosition() { + return contentCell()->selectedSubviewPosition(); + } + void setSelectedSubviewPosition(SubviewPosition subviewPosition) { + contentCell()->setSelectedSubviewPosition(subviewPosition); + } + bool displayCenter() const { return constContentCell()->displayCenter(); } + void setDisplayCenter(bool display); + void reloadScroll(); + bool handleEvent(Ion::Events::Event event) override; + Poincare::Layout layout() const { + return constContentCell()->layout(); + } +protected: + class ContentCell : public ::EvenOddCell { + public: + ContentCell(); + KDColor backgroundColor() const override; + void setHighlighted(bool highlight) override; + void setEven(bool even) override; + void reloadTextColor(); + KDSize minimalSizeForOptimalDisplay() const override; + virtual ExpressionView * leftExpressionView() const { return nullptr; } + ExpressionView * rightExpressionView() { + return &m_rightExpressionView; + } + ExpressionView * centeredExpressionView() { + return &m_centeredExpressionView; + } + MessageTextView * approximateSign() { + return &m_approximateSign; + } + SubviewPosition selectedSubviewPosition() const { + return m_selectedSubviewPosition; + } + void setSelectedSubviewPosition(SubviewPosition subviewPosition); + bool displayCenter() const { return m_displayCenter && !m_centeredExpressionView.layout().isUninitialized(); } + void setDisplayCenter(bool display); + void layoutSubviews(bool force = false) override; + int numberOfSubviews() const override; + virtual Poincare::Layout layout() const override; + + private: + View * subviewAtIndex(int index) override; + ExpressionView m_rightExpressionView; + MessageTextView m_approximateSign; + ExpressionView m_centeredExpressionView; + SubviewPosition m_selectedSubviewPosition; + bool m_displayCenter; + }; + virtual ContentCell * contentCell() = 0; + virtual const ContentCell * constContentCell() const = 0; +}; + +class ScrollableTwoExpressionsView : public AbstractScrollableMultipleExpressionsView { +public: + ScrollableTwoExpressionsView(Responder * parentResponder) : AbstractScrollableMultipleExpressionsView(parentResponder, &m_contentCell) { + setMargins( + Metric::CommonSmallMargin, + Metric::CommonLargeMargin, + Metric::CommonSmallMargin, + Metric::CommonLargeMargin + ); + } + +private: + ContentCell * contentCell() override { return &m_contentCell; }; + const ContentCell * constContentCell() const override { return &m_contentCell; }; + ContentCell m_contentCell; +}; + + +} + +#endif diff --git a/apps/shared/scrollable_two_expressions_cell.cpp b/apps/shared/scrollable_two_expressions_cell.cpp new file mode 100644 index 000000000..2de15853e --- /dev/null +++ b/apps/shared/scrollable_two_expressions_cell.cpp @@ -0,0 +1,54 @@ +#include "scrollable_two_expressions_cell.h" +#include +using namespace Poincare; + +namespace Shared { + +ScrollableTwoExpressionsCell::ScrollableTwoExpressionsCell(Responder * parentResponder) : + Responder(parentResponder), + m_view(this) +{ +} + +void ScrollableTwoExpressionsCell::setLayouts(Poincare::Layout exactLayout, Poincare::Layout approximateLayout) { + m_view.setLayouts(Layout(), exactLayout, approximateLayout); +} + +void ScrollableTwoExpressionsCell::setHighlighted(bool highlight) { + m_view.evenOddCell()->setHighlighted(highlight); +} + +void ScrollableTwoExpressionsCell::setEven(bool even) { + EvenOddCell::setEven(even); + m_view.setBackgroundColor(backgroundColor()); + m_view.evenOddCell()->setEven(even); +} + +void ScrollableTwoExpressionsCell::reloadScroll() { + m_view.reloadScroll(); +} + +void ScrollableTwoExpressionsCell::didBecomeFirstResponder() { + reinitSelection(); + Container::activeApp()->setFirstResponder(&m_view); +} + +void ScrollableTwoExpressionsCell::reinitSelection() { + ScrollableTwoExpressionsView::SubviewPosition selectedSubview = m_view.displayCenter() ? ScrollableTwoExpressionsView::SubviewPosition::Center : ScrollableTwoExpressionsView::SubviewPosition::Right; + m_view.setSelectedSubviewPosition(selectedSubview); + reloadScroll(); +} + +int ScrollableTwoExpressionsCell::numberOfSubviews() const { + return 1; +} + +View * ScrollableTwoExpressionsCell::subviewAtIndex(int index) { + return &m_view; +} + +void ScrollableTwoExpressionsCell::layoutSubviews(bool force) { + m_view.setFrame(bounds(), force); +} + +} diff --git a/apps/shared/scrollable_exact_approximate_expressions_cell.h b/apps/shared/scrollable_two_expressions_cell.h similarity index 58% rename from apps/shared/scrollable_exact_approximate_expressions_cell.h rename to apps/shared/scrollable_two_expressions_cell.h index 39c232b16..b5c5d6e5f 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_cell.h +++ b/apps/shared/scrollable_two_expressions_cell.h @@ -1,14 +1,14 @@ -#ifndef SHARED_SCROLLABLE_EXACT_APPROXIMATE_EXPRESSIONS_CELL_H -#define SHARED_SCROLLABLE_EXACT_APPROXIMATE_EXPRESSIONS_CELL_H +#ifndef SHARED_SCROLLABLE_TWO_EXPRESSIONS_CELL_H +#define SHARED_SCROLLABLE_TWO_EXPRESSIONS_CELL_H #include -#include "scrollable_exact_approximate_expressions_view.h" +#include "scrollable_multiple_expressions_view.h" namespace Shared { -class ScrollableExactApproximateExpressionsCell : public ::EvenOddCell, public Responder { +class ScrollableTwoExpressionsCell : public ::EvenOddCell, public Responder { public: - ScrollableExactApproximateExpressionsCell(Responder * parentResponder = nullptr); + ScrollableTwoExpressionsCell(Responder * parentResponder = nullptr); void setLayouts(Poincare::Layout approximateLayout, Poincare::Layout exactLayout); void setEqualMessage(I18n::Message equalSignMessage) { return m_view.setEqualMessage(equalSignMessage); @@ -21,11 +21,12 @@ public: } Poincare::Layout layout() const override { return m_view.layout(); } void didBecomeFirstResponder() override; + void reinitSelection(); private: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; - ScrollableExactApproximateExpressionsView m_view; + void layoutSubviews(bool force = false) override; + ScrollableTwoExpressionsView m_view; }; } diff --git a/apps/shared/separator_even_odd_buffer_text_cell.cpp b/apps/shared/separator_even_odd_buffer_text_cell.cpp index 10eb5ef83..3117a2f64 100644 --- a/apps/shared/separator_even_odd_buffer_text_cell.cpp +++ b/apps/shared/separator_even_odd_buffer_text_cell.cpp @@ -11,9 +11,14 @@ void SeparatorEvenOddBufferTextCell::drawRect(KDContext * ctx, KDRect rect) cons ctx->fillRect(separatorRect, Shared::HideableEvenOddEditableTextCell::hideColor()); } -void SeparatorEvenOddBufferTextCell::layoutSubviews() { +void SeparatorEvenOddBufferTextCell::layoutSubviews(bool force) { KDRect boundsThis = bounds(); - m_bufferTextView.setFrame(KDRect(boundsThis.left() + Metric::TableSeparatorThickness + k_horizontalMargin, boundsThis.top(), boundsThis.width() - Metric::TableSeparatorThickness - 2*k_horizontalMargin, boundsThis.height())); + KDRect frame = KDRect( + boundsThis.left() + Metric::TableSeparatorThickness + k_horizontalMargin, + boundsThis.top(), + boundsThis.width() - Metric::TableSeparatorThickness - 2*k_horizontalMargin, + boundsThis.height()); + m_bufferTextView.setFrame(frame, force); } } diff --git a/apps/shared/separator_even_odd_buffer_text_cell.h b/apps/shared/separator_even_odd_buffer_text_cell.h index eba84ffef..0a5371f0a 100644 --- a/apps/shared/separator_even_odd_buffer_text_cell.h +++ b/apps/shared/separator_even_odd_buffer_text_cell.h @@ -10,7 +10,7 @@ class SeparatorEvenOddBufferTextCell : public EvenOddBufferTextCell { public: using EvenOddBufferTextCell::EvenOddBufferTextCell; void drawRect(KDContext * ctx, KDRect rect) const override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; private: constexpr static KDCoordinate k_rightMargin = Metric::CellMargin; }; diff --git a/apps/shared/simple_interactive_curve_view_controller.cpp b/apps/shared/simple_interactive_curve_view_controller.cpp index cfde9ec6d..46a4673b1 100644 --- a/apps/shared/simple_interactive_curve_view_controller.cpp +++ b/apps/shared/simple_interactive_curve_view_controller.cpp @@ -45,7 +45,7 @@ bool SimpleInteractiveCurveViewController::handleZoom(Ion::Events::Event event) bool SimpleInteractiveCurveViewController::handleLeftRightEvent(Ion::Events::Event event) { int direction = event == Ion::Events::Left ? -1 : 1; - if (moveCursorHorizontally(direction)) { + if (moveCursorHorizontally(direction, Ion::Events::isLongRepetition())) { interactiveCurveViewRange()->panToMakePointVisible( m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio diff --git a/apps/shared/simple_interactive_curve_view_controller.h b/apps/shared/simple_interactive_curve_view_controller.h index bf772dfde..fd30d2998 100644 --- a/apps/shared/simple_interactive_curve_view_controller.h +++ b/apps/shared/simple_interactive_curve_view_controller.h @@ -30,7 +30,7 @@ protected: /* the result of moveCursorVertically/Horizontally means: * false -> the cursor cannot move in this direction * true -> the cursor moved */ - virtual bool moveCursorHorizontally(int direction) { return false; }; + virtual bool moveCursorHorizontally(int direction, bool fast = false) { return false; } virtual InteractiveCurveViewRange * interactiveCurveViewRange() = 0; virtual CurveView * curveView() = 0; virtual bool handleEnter() = 0; diff --git a/apps/shared/store_cell.cpp b/apps/shared/store_cell.cpp index 8fe040e25..89fc90ae3 100644 --- a/apps/shared/store_cell.cpp +++ b/apps/shared/store_cell.cpp @@ -10,9 +10,9 @@ void StoreCell::drawRect(KDContext * ctx, KDRect rect) const { } } -void StoreCell::layoutSubviews() { +void StoreCell::layoutSubviews(bool force) { KDRect boundsThis = bounds(); - editableTextCell()->setFrame(rectWithoutSeparator(KDRect(boundsThis.left(), boundsThis.top(), boundsThis.width() - k_rightMargin, boundsThis.height()))); + editableTextCell()->setFrame(rectWithoutSeparator(KDRect(boundsThis.left(), boundsThis.top(), boundsThis.width() - k_rightMargin, boundsThis.height())), force); } void StoreCell::didSetSeparator() { diff --git a/apps/shared/store_cell.h b/apps/shared/store_cell.h index c6642f3e9..5e1a0c260 100644 --- a/apps/shared/store_cell.h +++ b/apps/shared/store_cell.h @@ -13,7 +13,7 @@ public: Separable() {} void drawRect(KDContext * ctx, KDRect rect) const override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; private: static constexpr KDCoordinate k_rightMargin = Metric::CellMargin; void didSetSeparator() override; diff --git a/apps/shared/store_context.cpp b/apps/shared/store_context.cpp deleted file mode 100644 index a5b5229f8..000000000 --- a/apps/shared/store_context.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "store_context.h" -#include -#include -#include - -using namespace Poincare; - -namespace Shared { - -void StoreContext::setExpressionForSymbolAbstract(const Expression & expression, const SymbolAbstract & symbol) { - m_parentContext->setExpressionForSymbolAbstract(expression, symbol); -} - -} diff --git a/apps/shared/store_context.h b/apps/shared/store_context.h index ad025fd40..f8eed0157 100644 --- a/apps/shared/store_context.h +++ b/apps/shared/store_context.h @@ -2,28 +2,24 @@ #define SHARED_STORE_CONTEXT_H #include -#include +#include #include #include "double_pair_store.h" #include namespace Shared { -class StoreContext : public Poincare::Context { +class StoreContext : public Poincare::ContextWithParent { public: - StoreContext(Shared::DoublePairStore * store) : - Poincare::Context(), + StoreContext(Shared::DoublePairStore * store, Context * parentContext) : + Poincare::ContextWithParent(parentContext), m_store(store), - m_seriesPairIndex(-1), - m_parentContext(nullptr) + m_seriesPairIndex(-1) {} - void setParentContext(Poincare::Context * parentContext) { m_parentContext = parentContext; } void setSeriesPairIndex(int j) { m_seriesPairIndex = j; } - void setExpressionForSymbolAbstract(const Poincare::Expression & expression, const Poincare::SymbolAbstract & symbol) override; protected: Shared::DoublePairStore * m_store; int m_seriesPairIndex; - Poincare::Context * m_parentContext; }; } diff --git a/apps/shared/store_controller.cpp b/apps/shared/store_controller.cpp index 0fa05cfa9..c52f205c9 100644 --- a/apps/shared/store_controller.cpp +++ b/apps/shared/store_controller.cpp @@ -44,10 +44,10 @@ View * StoreController::ContentView::subviewAtIndex(int index) { return views[index]; } -void StoreController::ContentView::layoutSubviews() { +void StoreController::ContentView::layoutSubviews(bool force) { KDRect dataViewFrame(0, 0, bounds().width(), bounds().height() - (m_displayFormulaInputView ? k_formulaInputHeight : 0)); - m_dataView.setFrame(dataViewFrame); - m_formulaInputView.setFrame(formulaFrame()); + m_dataView.setFrame(dataViewFrame, force); + m_formulaInputView.setFrame(formulaFrame(), force); } KDRect StoreController::ContentView::formulaFrame() const { @@ -82,7 +82,7 @@ bool StoreController::textFieldShouldFinishEditing(TextField * textField, Ion::E bool StoreController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { if (textField == m_contentView.formulaInputView()->textField()) { // Handle formula input - Expression expression = Expression::Parse(textField->text()); + Expression expression = Expression::Parse(textField->text(), storeContext()); if (expression.isUninitialized()) { Container::activeApp()->displayWarning(I18n::Message::SyntaxError); return false; diff --git a/apps/shared/store_controller.h b/apps/shared/store_controller.h index 3158e4715..33eb9de69 100644 --- a/apps/shared/store_controller.h +++ b/apps/shared/store_controller.h @@ -64,7 +64,7 @@ protected: static constexpr KDCoordinate k_formulaInputHeight = 31; int numberOfSubviews() const override { return 1 + m_displayFormulaInputView; } View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; KDRect formulaFrame() const; StoreSelectableTableView m_dataView; BufferTextViewWithTextField m_formulaInputView; diff --git a/apps/shared/store_title_cell.cpp b/apps/shared/store_title_cell.cpp index 473db9912..6a99e60f1 100644 --- a/apps/shared/store_title_cell.cpp +++ b/apps/shared/store_title_cell.cpp @@ -10,8 +10,8 @@ void StoreTitleCell::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(r, m_separatorLeft ? HideableEvenOddEditableTextCell::hideColor() : backgroundColor()); } -void StoreTitleCell::layoutSubviews() { - bufferTextView()->setFrame(rectWithoutSeparator(bufferTextViewFrame())); +void StoreTitleCell::layoutSubviews(bool force) { + bufferTextView()->setFrame(rectWithoutSeparator(bufferTextViewFrame()), force); } void StoreTitleCell::didSetSeparator() { diff --git a/apps/shared/store_title_cell.h b/apps/shared/store_title_cell.h index 05899c24f..375c261eb 100644 --- a/apps/shared/store_title_cell.h +++ b/apps/shared/store_title_cell.h @@ -13,7 +13,7 @@ public: Separable() {} void drawRect(KDContext * ctx, KDRect rect) const override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; private: void didSetSeparator() override; }; diff --git a/apps/shared/sum_graph_controller.cpp b/apps/shared/sum_graph_controller.cpp index a4b4a7308..a831b4d89 100644 --- a/apps/shared/sum_graph_controller.cpp +++ b/apps/shared/sum_graph_controller.cpp @@ -174,7 +174,7 @@ KDSize SumGraphController::LegendView::minimalSizeForOptimalDisplay() const { void SumGraphController::LegendView::setLegendMessage(I18n::Message message, Step step) { m_legend.setMessage(message); - layoutSubviews(step); + layoutSubviews(step, false); } void SumGraphController::LegendView::setEditableZone(double d) { @@ -226,7 +226,7 @@ void SumGraphController::LegendView::setSumSymbol(Step step, double start, doubl } else { m_sum.setAlignment(0.0f, 0.5f); } - layoutSubviews(step); + layoutSubviews(step, false); } View * SumGraphController::LegendView::subviewAtIndex(int index) { @@ -240,21 +240,21 @@ View * SumGraphController::LegendView::subviewAtIndex(int index) { return &m_legend; } -void SumGraphController::LegendView::layoutSubviews() { - layoutSubviews(Step::FirstParameter); +void SumGraphController::LegendView::layoutSubviews(bool force) { + layoutSubviews(Step::FirstParameter, force); } -void SumGraphController::LegendView::layoutSubviews(Step step) { +void SumGraphController::LegendView::layoutSubviews(Step step, bool force) { KDCoordinate width = bounds().width(); KDCoordinate heigth = bounds().height(); KDSize legendSize = m_legend.minimalSizeForOptimalDisplay(); if (legendSize.width() > 0) { - m_sum.setFrame(KDRect(0, k_symbolHeightMargin, width-legendSize.width(), m_sum.minimalSizeForOptimalDisplay().height())); - m_legend.setFrame(KDRect(width-legendSize.width(), 0, legendSize.width(), heigth)); + m_sum.setFrame(KDRect(0, k_symbolHeightMargin, width-legendSize.width(), m_sum.minimalSizeForOptimalDisplay().height()), force); + m_legend.setFrame(KDRect(width-legendSize.width(), 0, legendSize.width(), heigth), force); } else { - m_sum.setFrame(bounds()); - m_legend.setFrame(KDRectZero); + m_sum.setFrame(bounds(), force); + m_legend.setFrame(KDRectZero, force); } KDRect frame = (step == Step::Result) ? KDRectZero : KDRect( @@ -262,7 +262,7 @@ void SumGraphController::LegendView::layoutSubviews(Step step) { k_symbolHeightMargin + k_sigmaHeight/2 - (step == Step::SecondParameter) * editableZoneHeight(), editableZoneWidth(), editableZoneHeight() ); - m_editableZone.setFrame(frame); + m_editableZone.setFrame(frame, force); } } diff --git a/apps/shared/sum_graph_controller.h b/apps/shared/sum_graph_controller.h index 0915616a5..b6fcad81a 100644 --- a/apps/shared/sum_graph_controller.h +++ b/apps/shared/sum_graph_controller.h @@ -65,8 +65,8 @@ private: constexpr static KDCoordinate k_sigmaHeight = 18; int numberOfSubviews() const override { return 3; } View * subviewAtIndex(int index) override; - void layoutSubviews() override; - void layoutSubviews(Step step); + void layoutSubviews(bool force = false) override; + void layoutSubviews(Step step, bool force); ExpressionView m_sum; Poincare::Layout m_sumLayout; MessageTextView m_legend; diff --git a/apps/shared/tab_table_controller.cpp b/apps/shared/tab_table_controller.cpp index 767f0d916..30933ce98 100644 --- a/apps/shared/tab_table_controller.cpp +++ b/apps/shared/tab_table_controller.cpp @@ -17,6 +17,7 @@ void TabTableController::viewWillAppear() { void TabTableController::willExitResponderChain(Responder * nextFirstResponder) { if (nextFirstResponder == tabController()) { + assert(tabController() != nullptr); selectableTableView()->deselectTable(); selectableTableView()->scrollToCell(0,0); } diff --git a/apps/shared/text_field_delegate_app.cpp b/apps/shared/text_field_delegate_app.cpp index 1cd9e3a7a..ece157f9b 100644 --- a/apps/shared/text_field_delegate_app.cpp +++ b/apps/shared/text_field_delegate_app.cpp @@ -32,7 +32,7 @@ bool TextFieldDelegateApp::textFieldDidReceiveEvent(TextField * textField, Ion:: bool TextFieldDelegateApp::isAcceptableText(const char * text) { - Expression exp = Expression::Parse(text); + Expression exp = Expression::Parse(text, localContext()); bool isAcceptable = isAcceptableExpression(exp); if (!isAcceptable) { displayWarning(I18n::Message::SyntaxError); @@ -94,7 +94,7 @@ bool TextFieldDelegateApp::isAcceptableExpression(const Expression exp) { return true; } -bool TextFieldDelegateApp::ExpressionCanBeSerialized(const Expression expression, bool replaceAns, Expression ansExpression) { +bool TextFieldDelegateApp::ExpressionCanBeSerialized(const Expression expression, bool replaceAns, Expression ansExpression, Context * context) { if (expression.isUninitialized()) { return false; } @@ -113,7 +113,7 @@ bool TextFieldDelegateApp::ExpressionCanBeSerialized(const Expression expression return false; } if (replaceAns) { - exp = Expression::Parse(buffer); + exp = Expression::Parse(buffer, context); if (exp.isUninitialized()) { // The ans replacement made the expression unparsable return false; diff --git a/apps/shared/text_field_delegate_app.h b/apps/shared/text_field_delegate_app.h index 09cacc5d4..185646dd6 100644 --- a/apps/shared/text_field_delegate_app.h +++ b/apps/shared/text_field_delegate_app.h @@ -13,7 +13,7 @@ namespace Shared { class TextFieldDelegateApp : public InputEventHandlerDelegateApp, public TextFieldDelegate { public: virtual ~TextFieldDelegateApp() = default; - virtual Poincare::Context * localContext(); + Poincare::Context * localContext() override; virtual bool XNTCanBeOverriden() const { return true; } virtual CodePoint XNT() { return 'x'; } bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; @@ -26,7 +26,7 @@ protected: bool fieldDidReceiveEvent(EditableField * field, Responder * responder, Ion::Events::Event event); bool isFinishingEvent(Ion::Events::Event event); virtual bool isAcceptableExpression(const Poincare::Expression expression); - static bool ExpressionCanBeSerialized(const Poincare::Expression expression, bool replaceAns, Poincare::Expression ansExpression); + static bool ExpressionCanBeSerialized(const Poincare::Expression expression, bool replaceAns, Poincare::Expression ansExpression, Poincare::Context * context); }; } diff --git a/apps/shared/values_controller.cpp b/apps/shared/values_controller.cpp index d6c9cec48..199c84d4b 100644 --- a/apps/shared/values_controller.cpp +++ b/apps/shared/values_controller.cpp @@ -129,6 +129,7 @@ void ValuesController::didBecomeFirstResponder() { void ValuesController::willExitResponderChain(Responder * nextFirstResponder) { if (nextFirstResponder == tabController()) { + assert(tabController() != nullptr); selectableTableView()->deselectTable(); selectableTableView()->scrollToCell(0,0); header()->setSelectedButton(-1); diff --git a/apps/shared/zoom_parameter_controller.cpp b/apps/shared/zoom_parameter_controller.cpp index 38bcc37e5..617837670 100644 --- a/apps/shared/zoom_parameter_controller.cpp +++ b/apps/shared/zoom_parameter_controller.cpp @@ -102,10 +102,10 @@ View * ZoomParameterController::ContentView::subviewAtIndex(int index) { return &m_legendView; } -void ZoomParameterController::ContentView::layoutSubviews() { +void ZoomParameterController::ContentView::layoutSubviews(bool force) { assert(bounds().height() == ZoomParameterController::k_standardViewHeight); - m_curveView->setFrame(KDRect(0, 0, bounds().width(), bounds().height() - k_legendHeight)); - m_legendView.setFrame(KDRect(0, bounds().height() - k_legendHeight, bounds().width(), k_legendHeight)); + m_curveView->setFrame(KDRect(0, 0, bounds().width(), bounds().height() - k_legendHeight), force); + m_legendView.setFrame(KDRect(0, bounds().height() - k_legendHeight, bounds().width(), k_legendHeight), force); } CurveView * ZoomParameterController::ContentView::curveView() { @@ -146,22 +146,22 @@ View * ZoomParameterController::ContentView::LegendView::subviewAtIndex(int inde return &m_legendPictograms[index-k_numberOfLegends]; } -void ZoomParameterController::ContentView::LegendView::layoutSubviews() { +void ZoomParameterController::ContentView::LegendView::layoutSubviews(bool force) { KDCoordinate height = bounds().height(); KDCoordinate xOrigin = 0; KDCoordinate legendWidth = m_legends[0].minimalSizeForOptimalDisplay().width(); - m_legends[0].setFrame(KDRect(xOrigin, 0, legendWidth, height)); + m_legends[0].setFrame(KDRect(xOrigin, 0, legendWidth, height), force); xOrigin += legendWidth; for (int i = 0; i < k_numberOfTokens - 2; i++) { - m_legendPictograms[i].setFrame(KDRect(xOrigin, 0, k_tokenWidth, height)); + m_legendPictograms[i].setFrame(KDRect(xOrigin, 0, k_tokenWidth, height), force); xOrigin += k_tokenWidth; } xOrigin = bounds().width()/2; for (int i = 1; i < k_numberOfLegends; i++) { KDCoordinate legendWidth = m_legends[i].minimalSizeForOptimalDisplay().width(); - m_legends[i].setFrame(KDRect(xOrigin, 0, legendWidth, height)); + m_legends[i].setFrame(KDRect(xOrigin, 0, legendWidth, height), force); xOrigin += legendWidth; - m_legendPictograms[k_numberOfTokens - 3 + i].setFrame(KDRect(xOrigin, 0, k_tokenWidth, height)); + m_legendPictograms[k_numberOfTokens - 3 + i].setFrame(KDRect(xOrigin, 0, k_tokenWidth, height), force); xOrigin += k_tokenWidth; } } diff --git a/apps/shared/zoom_parameter_controller.h b/apps/shared/zoom_parameter_controller.h index 13751a1b2..3d4f566c7 100644 --- a/apps/shared/zoom_parameter_controller.h +++ b/apps/shared/zoom_parameter_controller.h @@ -23,7 +23,7 @@ private: public: constexpr static KDCoordinate k_legendHeight = 30; ContentView(CurveView * curveView); - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; CurveView * curveView(); private: class LegendView : public View { @@ -34,7 +34,7 @@ private: constexpr static int k_numberOfLegends = 3; constexpr static int k_numberOfTokens = 6; constexpr static KDCoordinate k_tokenWidth = 10; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; MessageTextView m_legends[k_numberOfLegends]; diff --git a/apps/shift_alpha_lock_view.cpp b/apps/shift_alpha_lock_view.cpp index 1dd28c406..7809c7d10 100644 --- a/apps/shift_alpha_lock_view.cpp +++ b/apps/shift_alpha_lock_view.cpp @@ -65,10 +65,10 @@ View * ShiftAlphaLockView::subviewAtIndex(int index) { return &m_lockView; } -void ShiftAlphaLockView::layoutSubviews() { +void ShiftAlphaLockView::layoutSubviews(bool force) { KDSize modifierSize = KDFont::SmallFont->stringSize(I18n::translate(I18n::Message::Alpha)); - m_shiftAlphaView.setFrame(KDRect(bounds().width() - modifierSize.width(), (bounds().height()- modifierSize.height())/2, modifierSize)); + m_shiftAlphaView.setFrame(KDRect(bounds().width() - modifierSize.width(), (bounds().height()- modifierSize.height())/2, modifierSize), force); KDSize lockSize = m_lockView.minimalSizeForOptimalDisplay(); - m_lockView.setFrame(KDRect(bounds().width() - modifierSize.width() - lockSize.width() - k_lockRightMargin, (bounds().height()- lockSize.height())/2, lockSize)); + m_lockView.setFrame(KDRect(bounds().width() - modifierSize.width() - lockSize.width() - k_lockRightMargin, (bounds().height()- lockSize.height())/2, lockSize), force); } diff --git a/apps/shift_alpha_lock_view.h b/apps/shift_alpha_lock_view.h index b3b0bd05d..0e0d85d60 100644 --- a/apps/shift_alpha_lock_view.h +++ b/apps/shift_alpha_lock_view.h @@ -14,7 +14,7 @@ public: private: constexpr static KDCoordinate k_lockRightMargin = 5; int numberOfSubviews() const override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; View * subviewAtIndex(int index) override; LockView m_lockView; MessageTextView m_shiftAlphaView; diff --git a/apps/solver/app.h b/apps/solver/app.h index 786c5c4bf..065da82c9 100644 --- a/apps/solver/app.h +++ b/apps/solver/app.h @@ -42,6 +42,7 @@ public: InputViewController * inputViewController() { return &m_inputViewController; } ViewController * solutionsControllerStack() { return &m_alternateEmptyViewController; } ViewController * intervalController() { return &m_intervalController; } + SolutionsController * solutionsController() { return &m_solutionsController; } void willBecomeInactive() override; private: App(Snapshot * snapshot); diff --git a/apps/solver/base.de.i18n b/apps/solver/base.de.i18n index 5350c81d2..f88fe1a28 100644 --- a/apps/solver/base.de.i18n +++ b/apps/solver/base.de.i18n @@ -23,3 +23,7 @@ OnlyFirstSolutionsDisplayed0 = "Es werden nur die ersten" OnlyFirstSolutionsDisplayed1 = "zehn Lösungen angezeigt." PolynomeHasNoRealSolution0 = "Das Polynom hat" PolynomeHasNoRealSolution1 = "keine reelle Nullstelle" +PredefinedVariablesUsedLeft = "Verwendete" +PredefinedVariablesUsedRight = " vordefinierte Variablen" +PredefinedVariablesIgnoredLeft = "Ignoriert" +PredefinedVariablesIgnoredRight = " vordefinierte Variablen" diff --git a/apps/solver/base.en.i18n b/apps/solver/base.en.i18n index ebbb853e6..017cad04d 100644 --- a/apps/solver/base.en.i18n +++ b/apps/solver/base.en.i18n @@ -23,3 +23,7 @@ OnlyFirstSolutionsDisplayed0 = "Only the first 10 solutions" OnlyFirstSolutionsDisplayed1 = "are displayed" PolynomeHasNoRealSolution0 = "The polynomial has no" PolynomeHasNoRealSolution1 = "real root" +PredefinedVariablesUsedLeft = "Used " +PredefinedVariablesUsedRight = "predefined variables" +PredefinedVariablesIgnoredLeft = "Ignored" +PredefinedVariablesIgnoredRight = " predefined variables" diff --git a/apps/solver/base.es.i18n b/apps/solver/base.es.i18n index 7667ba3b5..ea595cf2d 100644 --- a/apps/solver/base.es.i18n +++ b/apps/solver/base.es.i18n @@ -23,3 +23,7 @@ OnlyFirstSolutionsDisplayed0 = "Sólo se muestran las" OnlyFirstSolutionsDisplayed1 = "10 primeras soluciones" PolynomeHasNoRealSolution0 = "El polinomio no tiene" PolynomeHasNoRealSolution1 = "ninguna raíz real" +PredefinedVariablesUsedLeft = "Variables" +PredefinedVariablesUsedRight = " predefinidas utilizadas" +PredefinedVariablesIgnoredLeft = "Variables" +PredefinedVariablesIgnoredRight = " predefinidas ignoradas" diff --git a/apps/solver/base.fr.i18n b/apps/solver/base.fr.i18n index c23428889..c337a0525 100644 --- a/apps/solver/base.fr.i18n +++ b/apps/solver/base.fr.i18n @@ -23,3 +23,7 @@ OnlyFirstSolutionsDisplayed0 = "Seulement les 10 premières" OnlyFirstSolutionsDisplayed1 = "solutions sont affichées" PolynomeHasNoRealSolution0 = "Le polynôme n'admet pas" PolynomeHasNoRealSolution1 = "de racine réelle" +PredefinedVariablesUsedLeft = "Variable" +PredefinedVariablesUsedRight = "s prédéfinies utilisées" +PredefinedVariablesIgnoredLeft = "Variable" +PredefinedVariablesIgnoredRight = "s prédéfinies ignorées" diff --git a/apps/solver/base.pt.i18n b/apps/solver/base.pt.i18n index c1c28d11b..94b3df314 100644 --- a/apps/solver/base.pt.i18n +++ b/apps/solver/base.pt.i18n @@ -23,3 +23,7 @@ OnlyFirstSolutionsDisplayed0 = "Somente as 10 primeiras" OnlyFirstSolutionsDisplayed1 = "soluções são exibidas" PolynomeHasNoRealSolution0 = "O polinômio não tem" PolynomeHasNoRealSolution1 = "nenhuma raiz real" +PredefinedVariablesUsedLeft = "Variáveis " +PredefinedVariablesUsedRight = "pré-definidas utilizadas" +PredefinedVariablesIgnoredLeft = "Variáveis" +PredefinedVariablesIgnoredRight = " pré-definidas ignoradas" diff --git a/apps/solver/equation.cpp b/apps/solver/equation.cpp index 764e40390..ef578eac7 100644 --- a/apps/solver/equation.cpp +++ b/apps/solver/equation.cpp @@ -1,5 +1,7 @@ #include "equation.h" +#include #include +#include #include #include #include @@ -16,33 +18,55 @@ 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); } -Expression Equation::Model::standardForm(const Storage::Record * record, Context * context) const { - if (m_standardForm.isUninitialized()) { - const Expression e = expressionReduced(record, context); - if (e.type() == ExpressionNode::Type::Unreal) { - m_standardForm = Unreal::Builder(); - return m_standardForm; +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); + + EmptyContext emptyContext; + Context * contextToUse = replaceFunctionsButNotSymbols ? &emptyContext : context; + + // Reduce the expression + Expression expressionRed = expressionInputWithoutFunctions.clone(); + PoincareHelpers::Simplify(&expressionRed, contextToUse, ExpressionNode::ReductionTarget::SystemForApproximation); + // simplify might return an uninitialized Expression if interrupted + if (expressionRed.isUninitialized()) { + expressionRed = expressionInputWithoutFunctions; } - if (e.recursivelyMatches([](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Undefined || e.type() == ExpressionNode::Type::Infinity || Expression::IsMatrix(e, context); }, context, true)) { - m_standardForm = Undefined::Builder(); - return m_standardForm; - } - if (e.type() == ExpressionNode::Type::Equal) { + + if (expressionRed.type() == ExpressionNode::Type::Unreal) { + *returnedExpression = Unreal::Builder(); + } else if (expressionRed.recursivelyMatches( + [](const Expression e, Context * context) { + return e.type() == ExpressionNode::Type::Undefined || e.type() == ExpressionNode::Type::Infinity || Expression::IsMatrix(e, context); + }, + contextToUse, + true)) + { + *returnedExpression = Undefined::Builder(); + } else if (expressionRed.type() == ExpressionNode::Type::Equal) { Preferences * preferences = Preferences::sharedPreferences(); - m_standardForm = static_cast(e).standardEquation(context, Expression::UpdatedComplexFormatWithExpressionInput(preferences->complexFormat(), expressionClone(record), context), preferences->angleUnit()); + *returnedExpression = static_cast(expressionRed).standardEquation(contextToUse, Expression::UpdatedComplexFormatWithExpressionInput(preferences->complexFormat(), expressionInputWithoutFunctions, contextToUse), preferences->angleUnit()); } else { - assert(e.type() == ExpressionNode::Type::Rational && static_cast(e).isOne()); + assert(expressionRed.type() == ExpressionNode::Type::Rational && static_cast(expressionRed).isOne()); // The equality was reduced which means the equality was always true. - m_standardForm = Rational::Builder(0); + *returnedExpression = Rational::Builder(0); + } + if (!m_standardFormWithReplacedFunctionsButNotSymbols.isUninitialized() && !m_standardFormWithReplacedFunctionsAndSymbols.isUninitialized()) { + // Do not keep two equal expressions + if (m_standardFormWithReplacedFunctionsButNotSymbols.isIdenticalTo(m_standardFormWithReplacedFunctionsAndSymbols)) { + m_standardFormWithReplacedFunctionsButNotSymbols = m_standardFormWithReplacedFunctionsAndSymbols; + } } } - return m_standardForm; + return *returnedExpression; } void Equation::Model::tidy() const { ExpressionModel::tidy(); // Free the pool of the m_standardForm - m_standardForm = Expression(); + m_standardFormWithReplacedFunctionsAndSymbols = Expression(); + m_standardFormWithReplacedFunctionsButNotSymbols = Expression(); } void * Equation::Model::expressionAddress(const Ion::Storage::Record * record) const { diff --git a/apps/solver/equation.h b/apps/solver/equation.h index 63fe1b11e..549ab6998 100644 --- a/apps/solver/equation.h +++ b/apps/solver/equation.h @@ -11,18 +11,19 @@ public: bool shouldBeClearedBeforeRemove() override { return false; } - Poincare::Expression standardForm(Poincare::Context * context) const { return m_model.standardForm(this, context); } + Poincare::Expression standardForm(Poincare::Context * context, bool replaceFunctionsButNotSymbols) const { return m_model.standardForm(this, context, replaceFunctionsButNotSymbols); } bool containsIComplex(Poincare::Context * context) const; private: class Model : public Shared::ExpressionModel { public: - Poincare::Expression standardForm(const Ion::Storage::Record * record, Poincare::Context * context) const; + Poincare::Expression standardForm(const Ion::Storage::Record * record, Poincare::Context * context, bool replaceFunctionsButNotSymbols) const; void tidy() const override; - void * expressionAddress(const Ion::Storage::Record * record) const override; private: + void * expressionAddress(const Ion::Storage::Record * record) const override; size_t expressionSize(const Ion::Storage::Record * record) const override; - mutable Poincare::Expression m_standardForm; + mutable Poincare::Expression m_standardFormWithReplacedFunctionsAndSymbols; + mutable Poincare::Expression m_standardFormWithReplacedFunctionsButNotSymbols; }; size_t metaDataSize() const override { return 0; } const Shared::ExpressionModel * model() const override { return &m_model; } diff --git a/apps/solver/equation_list_view.cpp b/apps/solver/equation_list_view.cpp index 4aae45848..6ed4befa7 100644 --- a/apps/solver/equation_list_view.cpp +++ b/apps/solver/equation_list_view.cpp @@ -47,8 +47,8 @@ void EquationListView::didBecomeFirstResponder() { Container::activeApp()->setFirstResponder(&m_listView); } -void EquationListView::layoutSubviews() { - m_listView.setFrame(KDRect(0, 0, bounds().width(), bounds().height())); +void EquationListView::layoutSubviews(bool force) { + m_listView.setFrame(KDRect(0, 0, bounds().width(), bounds().height()), force); if (m_braceStyle != BraceStyle::None) { KDCoordinate braceWidth = m_braceView.minimalSizeForOptimalDisplay().width(); KDCoordinate braceHeight = m_listView.minimalSizeForOptimalDisplay().height()-2*k_margin; @@ -56,9 +56,9 @@ void EquationListView::layoutSubviews() { m_braceView.setSize(KDSize(braceWidth, braceHeight)); KDCoordinate scrollBraceHeight = m_listView.minimalSizeForOptimalDisplay().height()-offset().y(); scrollBraceHeight = m_braceStyle == BraceStyle::OneRowShort ? scrollBraceHeight - Metric::StoreRowHeight : scrollBraceHeight; - m_scrollBraceView.setFrame(KDRect(0, 0, k_braceTotalWidth, scrollBraceHeight)); + m_scrollBraceView.setFrame(KDRect(0, 0, k_braceTotalWidth, scrollBraceHeight), force); } else { - m_scrollBraceView.setFrame(KDRectZero); + m_scrollBraceView.setFrame(KDRectZero, force); } } diff --git a/apps/solver/equation_list_view.h b/apps/solver/equation_list_view.h index 9363c7fe6..a4392ae99 100644 --- a/apps/solver/equation_list_view.h +++ b/apps/solver/equation_list_view.h @@ -23,7 +23,7 @@ public: } constexpr static KDCoordinate k_margin = 10; constexpr static KDCoordinate k_braceTotalWidth = 30;//2*k_margin+BraceView::k_braceWidth; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; private: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; diff --git a/apps/solver/equation_models_parameter_controller.cpp b/apps/solver/equation_models_parameter_controller.cpp index 2ed3f6ac7..32aff0988 100644 --- a/apps/solver/equation_models_parameter_controller.cpp +++ b/apps/solver/equation_models_parameter_controller.cpp @@ -22,9 +22,10 @@ EquationModelsParameterController::EquationModelsParameterController(Responder * m_selectableTableView.setMargins(0); m_selectableTableView.setDecoratorType(ScrollView::Decorator::Type::None); for (int i = 0; i < k_numberOfExpressionCells; i++) { - Poincare::Expression e = Expression::Parse(k_models[i+1]); + Poincare::Expression e = Expression::Parse(k_models[i+1], nullptr); // No context needed m_layouts[i] = e.createLayout(Poincare::Preferences::PrintFloatMode::Decimal, Preferences::ShortNumberOfSignificantDigits); m_modelCells[i].setLayout(m_layouts[i]); + m_modelCells[i].setParentResponder(&m_selectableTableView); } } diff --git a/apps/solver/equation_store.cpp b/apps/solver/equation_store.cpp index 0691c77c0..d9a227574 100644 --- a/apps/solver/equation_store.cpp +++ b/apps/solver/equation_store.cpp @@ -1,6 +1,7 @@ #include "equation_store.h" #include "../constant.h" #include "../shared/poincare_helpers.h" +#include "../exam_mode_configuration.h" #include "../global_preferences.h" #include @@ -27,7 +28,8 @@ EquationStore::EquationStore() : m_type(Type::LinearSystem), m_numberOfSolutions(0), m_exactSolutionExactLayouts{}, - m_exactSolutionApproximateLayouts{} + m_exactSolutionApproximateLayouts{}, + m_numberOfUserVariables(0) { } @@ -92,22 +94,23 @@ double EquationStore::approximateSolutionAtIndex(int i) { return m_approximateSolutions[i]; } -bool EquationStore::haveMoreApproximationSolutions(Context * context) { +bool EquationStore::haveMoreApproximationSolutions(Context * context, bool solveWithoutContext) { if (m_numberOfSolutions < k_maxNumberOfEquations) { return false; } double step = (m_intervalApproximateSolutions[1]-m_intervalApproximateSolutions[0])*k_precision; - return !std::isnan(PoincareHelpers::NextRoot(modelForRecord(definedRecordAtIndex(0))->standardForm(context), m_variables[0], m_approximateSolutions[m_numberOfSolutions-1], step, m_intervalApproximateSolutions[1], context)); + return !std::isnan(PoincareHelpers::NextRoot(modelForRecord(definedRecordAtIndex(0))->standardForm(context, solveWithoutContext), m_variables[0], m_approximateSolutions[m_numberOfSolutions-1], step, m_intervalApproximateSolutions[1], context)); } -void EquationStore::approximateSolve(Poincare::Context * context) { +void EquationStore::approximateSolve(Poincare::Context * context, bool shouldReplaceFunctionsButNotSymbols) { + m_userVariablesUsed = !shouldReplaceFunctionsButNotSymbols; assert(m_variables[0][0] != 0 && m_variables[1][0] == 0); assert(m_type == Type::Monovariable); m_numberOfSolutions = 0; double start = m_intervalApproximateSolutions[0]; double step = (m_intervalApproximateSolutions[1]-m_intervalApproximateSolutions[0])*k_precision; for (int i = 0; i < k_maxNumberOfApproximateSolutions; i++) { - m_approximateSolutions[i] = PoincareHelpers::NextRoot(modelForRecord(definedRecordAtIndex(0))->standardForm(context), m_variables[0], start, step, m_intervalApproximateSolutions[1], context); + m_approximateSolutions[i] = PoincareHelpers::NextRoot(modelForRecord(definedRecordAtIndex(0))->standardForm(context, shouldReplaceFunctionsButNotSymbols), m_variables[0], start, step, m_intervalApproximateSolutions[1], context); if (std::isnan(m_approximateSolutions[i])) { break; } else { @@ -117,21 +120,34 @@ void EquationStore::approximateSolve(Poincare::Context * context) { } } -EquationStore::Error EquationStore::exactSolve(Poincare::Context * context) { +EquationStore::Error EquationStore::exactSolve(Poincare::Context * context, bool * replaceFunctionsButNotSymbols) { + assert(replaceFunctionsButNotSymbols != nullptr); + *replaceFunctionsButNotSymbols = false; + Error e = privateExactSolve(context, false); + if (e == Error::NoError && numberOfSolutions() == 0 && m_numberOfUserVariables > 0) { + *replaceFunctionsButNotSymbols = true; + e = privateExactSolve(context, true); + } + return e; +} + +EquationStore::Error EquationStore::privateExactSolve(Poincare::Context * context, bool replaceFunctionsButNotSymbols) { tidySolution(); + m_userVariablesUsed = !replaceFunctionsButNotSymbols; + // Step 0. Get unknown variables m_variables[0][0] = 0; int numberOfVariables = 0; for (int i = 0; i < numberOfDefinedModels(); i++) { - const Expression e = modelForRecord(definedRecordAtIndex(i))->standardForm(context); + const Expression e = modelForRecord(definedRecordAtIndex(i))->standardForm(context, replaceFunctionsButNotSymbols); if (e.isUninitialized() || e.type() == ExpressionNode::Type::Undefined) { return Error::EquationUndefined; } if (e.type() == ExpressionNode::Type::Unreal) { return Error::EquationUnreal; } - numberOfVariables = e.getVariables(context, [](const char * symbol) { return true; }, (char *)m_variables, Poincare::SymbolAbstract::k_maxNameSize); + numberOfVariables = e.getVariables(context, [](const char * symbol, Poincare::Context * context) { return true; }, (char *)m_variables, Poincare::SymbolAbstract::k_maxNameSize, numberOfVariables); if (numberOfVariables == -1) { return Error::TooManyVariables; } @@ -140,7 +156,22 @@ EquationStore::Error EquationStore::exactSolve(Poincare::Context * context) { assert(numberOfVariables >= 0); } - // Step 1. Linear System? + // 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: * coefficients * (x y z ...) = constants */ @@ -149,7 +180,7 @@ EquationStore::Error EquationStore::exactSolve(Poincare::Context * context) { bool isLinear = true; // Invalid the linear system if one equation is non-linear Preferences * preferences = Preferences::sharedPreferences(); for (int i = 0; i < numberOfDefinedModels(); i++) { - isLinear = isLinear && modelForRecord(definedRecordAtIndex(i))->standardForm(context).getLinearCoefficients((char *)m_variables, Poincare::SymbolAbstract::k_maxNameSize, coefficients[i], &constants[i], context, updatedComplexFormat(context), preferences->angleUnit()); + isLinear = isLinear && modelForRecord(definedRecordAtIndex(i))->standardForm(context, replaceFunctionsButNotSymbols).getLinearCoefficients((char *)m_variables, Poincare::SymbolAbstract::k_maxNameSize, coefficients[i], &constants[i], context, updatedComplexFormat(context), preferences->angleUnit(), replaceFunctionsButNotSymbols ? ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions : ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition); if (!isLinear) { // TODO: should we clean pool allocated memory if the system is not linear #if 0 @@ -177,16 +208,16 @@ EquationStore::Error EquationStore::exactSolve(Poincare::Context * context) { m_type = Type::LinearSystem; error = resolveLinearSystem(exactSolutions, exactSolutionsApproximations, coefficients, constants, context); } else { - // Step 2. Polynomial & Monovariable? + // Step 3. Polynomial & Monovariable? assert(numberOfVariables == 1 && numberOfDefinedModels() == 1); Expression polynomialCoefficients[Expression::k_maxNumberOfPolynomialCoefficients]; - int degree = modelForRecord(definedRecordAtIndex(0))->standardForm(context).getPolynomialReducedCoefficients(m_variables[0], polynomialCoefficients, context, updatedComplexFormat(context), preferences->angleUnit()); + int degree = modelForRecord(definedRecordAtIndex(0))->standardForm(context, replaceFunctionsButNotSymbols).getPolynomialReducedCoefficients(m_variables[0], polynomialCoefficients, context, updatedComplexFormat(context), preferences->angleUnit(), replaceFunctionsButNotSymbols ? ExpressionNode::SymbolicComputation::ReplaceDefinedFunctionsWithDefinitions : ExpressionNode::SymbolicComputation::ReplaceAllDefinedSymbolsWithDefinition); if (degree == 2) { // Polynomial degree <= 2 m_type = Type::PolynomialMonovariable; error = oneDimensialPolynomialSolve(exactSolutions, exactSolutionsApproximations, polynomialCoefficients, degree, context); } else { - // Step 3. Monovariable non-polynomial or polynomial with degree > 2 + // Step 4. Monovariable non-polynomial or polynomial with degree > 2 m_type = Type::Monovariable; m_intervalApproximateSolutions[0] = -10; m_intervalApproximateSolutions[1] = 10; @@ -194,8 +225,8 @@ EquationStore::Error EquationStore::exactSolve(Poincare::Context * context) { } } // Create the results' layouts - // In Dutch exam mode, display only approximate solutions - bool forbidExactSolutions = GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::Dutch; + // Some exam mode configuration requires to display only approximate solutions + bool forbidExactSolutions = ExamModeConfiguration::exactExpressionsAreForbidden(GlobalPreferences::sharedGlobalPreferences()->examMode()); int solutionIndex = 0; int initialNumberOfSolutions = m_numberOfSolutions <= k_maxNumberOfExactSolutions ? m_numberOfSolutions : -1; // We iterate through the solutions and the potential delta @@ -214,12 +245,12 @@ EquationStore::Error EquationStore::exactSolve(Poincare::Context * context) { char approximateBuffer[::Constant::MaxSerializedExpressionSize]; m_exactSolutionExactLayouts[solutionIndex].serializeForParsing(exactBuffer, ::Constant::MaxSerializedExpressionSize); m_exactSolutionApproximateLayouts[solutionIndex].serializeForParsing(approximateBuffer, ::Constant::MaxSerializedExpressionSize); - /* Cheat: declare exact and approximate solutions to be identical in - * Dutch exam mode to display only the approximate solutions. */ + /* Cheat: declare exact and approximate solutions to be identical in when + * 'forbidExactSolutions' is true to display only the approximate + * solutions. */ m_exactSolutionIdentity[solutionIndex] = forbidExactSolutions || strcmp(exactBuffer, approximateBuffer) == 0; if (!m_exactSolutionIdentity[solutionIndex]) { - char buffer[::Constant::MaxSerializedExpressionSize]; - m_exactSolutionEquality[solutionIndex] = exactSolutions[i].isEqualToItsApproximationLayout(exactSolutionsApproximations[i], buffer, ::Constant::MaxSerializedExpressionSize, preferences->complexFormat(), preferences->angleUnit(), preferences->displayMode(), preferences->numberOfSignificantDigits(), context); + m_exactSolutionEquality[solutionIndex] = Expression::ParsedExpressionsAreEqual(exactBuffer, approximateBuffer, context, updatedComplexFormat(context), preferences->angleUnit()); } solutionIndex++; } @@ -281,7 +312,7 @@ EquationStore::Error EquationStore::oneDimensialPolynomialSolve(Expression exact assert(degree == 2); // Compute delta = b*b-4ac Expression delta = Subtraction::Builder(Power::Builder(coefficients[1].clone(), Rational::Builder(2)), Multiplication::Builder(Rational::Builder(4), coefficients[0].clone(), coefficients[2].clone())); - delta = delta.simplify(context, updatedComplexFormat(context), Poincare::Preferences::sharedPreferences()->angleUnit(), ExpressionNode::ReductionTarget::SystemForApproximation); + delta = delta.simplify(ExpressionNode::ReductionContext(context, updatedComplexFormat(context), Poincare::Preferences::sharedPreferences()->angleUnit(), ExpressionNode::ReductionTarget::SystemForApproximation)); if (delta.isUninitialized()) { delta = Poincare::Undefined::Builder(); } diff --git a/apps/solver/equation_store.h b/apps/solver/equation_store.h index b1a1bf717..5cc28c6b1 100644 --- a/apps/solver/equation_store.h +++ b/apps/solver/equation_store.h @@ -38,11 +38,21 @@ public: assert(i < Poincare::Expression::k_maxNumberOfVariables && m_variables[i][0] != 0); return m_variables[i]; } + const char * userVariableAtIndex(size_t i) { + assert(i < Poincare::Expression::k_maxNumberOfVariables && m_userVariables[i][0] != 0); + return m_userVariables[i]; + } + int numberOfUserVariables() const { + return m_numberOfUserVariables; + } + bool userVariablesUsed() const { + return m_userVariablesUsed; + } int numberOfSolutions() const { return m_numberOfSolutions; } /* Exact resolution */ - Error exactSolve(Poincare::Context * context); + Error exactSolve(Poincare::Context * context, bool * replaceFunctionsButNotSymbols); /* The exact solutions are displayed in a table with 2 layouts: an exact * Layout and an approximate layout. For example, 'sqrt(2)' and '1.414213'. * The boolean exactLayout indicates if we want the exact layout or the @@ -65,8 +75,8 @@ public: double intervalBound(int index) const; void setIntervalBound(int index, double value); double approximateSolutionAtIndex(int i); - void approximateSolve(Poincare::Context * context); - bool haveMoreApproximationSolutions(Poincare::Context * context); + void approximateSolve(Poincare::Context * context, bool shouldReplaceFuncionsButNotSymbols); + bool haveMoreApproximationSolutions(Poincare::Context * context, bool solveWithoutContext); void tidy() override; @@ -84,6 +94,7 @@ private: Shared::ExpressionModelHandle * setMemoizedModelAtIndex(int cacheIndex, Ion::Storage::Record record) const override; Shared::ExpressionModelHandle * memoizedModelAtIndex(int cacheIndex) const override; + Error privateExactSolve(Poincare::Context * context, bool replaceFunctionsButNotSymbols); Error resolveLinearSystem(Poincare::Expression solutions[k_maxNumberOfExactSolutions], Poincare::Expression solutionApproximations[k_maxNumberOfExactSolutions], Poincare::Expression coefficients[k_maxNumberOfEquations][Poincare::Expression::k_maxNumberOfVariables], Poincare::Expression constants[k_maxNumberOfEquations], Poincare::Context * context); Error oneDimensialPolynomialSolve(Poincare::Expression solutions[k_maxNumberOfExactSolutions], Poincare::Expression solutionApproximations[k_maxNumberOfExactSolutions], Poincare::Expression polynomialCoefficients[Poincare::Expression::k_maxNumberOfPolynomialCoefficients], int degree, Poincare::Context * context); void tidySolution(); @@ -93,6 +104,7 @@ private: mutable Equation m_equations[k_maxNumberOfEquations]; Type m_type; char m_variables[Poincare::Expression::k_maxNumberOfVariables][Poincare::SymbolAbstract::k_maxNameSize]; + char m_userVariables[Poincare::Expression::k_maxNumberOfVariables][Poincare::SymbolAbstract::k_maxNameSize]; int m_numberOfSolutions; Poincare::Layout m_exactSolutionExactLayouts[k_maxNumberOfApproximateSolutions]; Poincare::Layout m_exactSolutionApproximateLayouts[k_maxNumberOfExactSolutions]; @@ -100,6 +112,8 @@ private: bool m_exactSolutionEquality[k_maxNumberOfExactSolutions]; double m_intervalApproximateSolutions[2]; double m_approximateSolutions[k_maxNumberOfApproximateSolutions]; + int m_numberOfUserVariables; + bool m_userVariablesUsed; }; } diff --git a/apps/solver/interval_controller.cpp b/apps/solver/interval_controller.cpp index d92a358fd..a46051dfd 100644 --- a/apps/solver/interval_controller.cpp +++ b/apps/solver/interval_controller.cpp @@ -32,11 +32,11 @@ View * IntervalController::ContentView::subviewAtIndex(int index) { return m_selectableTableView; } -void IntervalController::ContentView::layoutSubviews() { +void IntervalController::ContentView::layoutSubviews(bool force) { KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height(); - m_instructions0.setFrame(KDRect(0, k_topMargin/2-textHeight, bounds().width(), textHeight)); - m_instructions1.setFrame(KDRect(0, k_topMargin/2, bounds().width(), textHeight)); - m_selectableTableView->setFrame(KDRect(0, k_topMargin, bounds().width(), bounds().height()-k_topMargin)); + m_instructions0.setFrame(KDRect(0, k_topMargin/2-textHeight, bounds().width(), textHeight), force); + m_instructions1.setFrame(KDRect(0, k_topMargin/2, bounds().width(), textHeight), force); + m_selectableTableView->setFrame(KDRect(0, k_topMargin, bounds().width(), bounds().height()-k_topMargin), force); } /* IntervalController Controller */ @@ -102,7 +102,7 @@ bool IntervalController::textFieldDidFinishEditing(TextField * textField, const void IntervalController::buttonAction() { StackViewController * stack = stackController(); - m_equationStore->approximateSolve(textFieldDelegateApp()->localContext()); + m_equationStore->approximateSolve(textFieldDelegateApp()->localContext(), App::app()->solutionsController()->shouldReplaceFuncionsButNotSymbols()); stack->push(App::app()->solutionsControllerStack(), Palette::BannerSecondText, Palette::BannerSecondBackground, Palette::BannerSecondBorder); } diff --git a/apps/solver/interval_controller.h b/apps/solver/interval_controller.h index 206f86fd1..ba4f98d21 100644 --- a/apps/solver/interval_controller.h +++ b/apps/solver/interval_controller.h @@ -29,7 +29,7 @@ private: constexpr static KDCoordinate k_topMargin = 50; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; MessageTextView m_instructions0; MessageTextView m_instructions1; SelectableTableView * m_selectableTableView; diff --git a/apps/solver/list_controller.cpp b/apps/solver/list_controller.cpp index 22064c015..edbe539b8 100644 --- a/apps/solver/list_controller.cpp +++ b/apps/solver/list_controller.cpp @@ -1,6 +1,7 @@ #include "list_controller.h" #include "app.h" #include +#include #include using namespace Shared; @@ -124,10 +125,11 @@ bool layoutRepresentsAnEquality(Poincare::Layout l) { bool ListController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) { if (textField->isEditing() && textField->shouldFinishEditing(event)) { - if (!textRepresentsAnEquality(textField->text())) { - textField->handleEvent(Ion::Events::ShiftRight); + const char * text = textField->text(); + if (!textRepresentsAnEquality(text)) { + textField->setCursorLocation(text + strlen(text)); textField->handleEventWithText("=0"); - if (!textRepresentsAnEquality(textField->text())) { + if (!textRepresentsAnEquality(text)) { Container::activeApp()->displayWarning(I18n::Message::RequireEquation); return true; } @@ -142,7 +144,7 @@ bool ListController::textFieldDidReceiveEvent(TextField * textField, Ion::Events bool ListController::layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) { if (layoutField->isEditing() && layoutField->shouldFinishEditing(event)) { if (!layoutRepresentsAnEquality(layoutField->layout())) { - layoutField->handleEvent(Ion::Events::ShiftRight); + layoutField->putCursorRightOfLayout(); layoutField->handleEventWithText("=0"); if (!layoutRepresentsAnEquality(layoutField->layout())) { Container::activeApp()->displayWarning(I18n::Message::RequireEquation); @@ -171,7 +173,8 @@ void ListController::resolveEquations() { Container::activeApp()->displayWarning(I18n::Message::EnterEquation); return; } - EquationStore::Error e = modelStore()->exactSolve(textFieldDelegateApp()->localContext()); + bool resultWithoutUserDefinedSymbols = false; + EquationStore::Error e = modelStore()->exactSolve(textFieldDelegateApp()->localContext(), &resultWithoutUserDefinedSymbols); switch (e) { case EquationStore::Error::EquationUndefined: Container::activeApp()->displayWarning(I18n::Message::UndefinedEquation); @@ -187,17 +190,18 @@ void ListController::resolveEquations() { return; case EquationStore::Error::RequireApproximateSolution: { - StackViewController * stack = stackController(); - stack->push(App::app()->intervalController(), Palette::BannerFirstText, Palette::BannerFirstBackground, Palette::BannerFirstBorder); + App::app()->solutionsController()->setShouldReplaceFuncionsButNotSymbols(resultWithoutUserDefinedSymbols); + stackController()->push(App::app()->intervalController(), Palette::BannerFirstText, Palette::BannerFirstBackground, Palette::BannerFirstBorder); return; } default: { assert(e == EquationStore::Error::NoError); StackViewController * stack = stackController(); + App::app()->solutionsController()->setShouldReplaceFuncionsButNotSymbols(resultWithoutUserDefinedSymbols); stack->push(App::app()->solutionsControllerStack(), Palette::BannerFirstText, Palette::BannerFirstBackground, Palette::BannerFirstBorder); } - } + } } void ListController::reloadButtonMessage() { diff --git a/apps/solver/solutions_controller.cpp b/apps/solver/solutions_controller.cpp index 1b69b5d18..1afa6220e 100644 --- a/apps/solver/solutions_controller.cpp +++ b/apps/solver/solutions_controller.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include using namespace Poincare; @@ -17,28 +18,30 @@ namespace Solver { static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { return x > y ? x : y; } +constexpr KDColor SolutionsController::ContentView::k_backgroundColor; + SolutionsController::ContentView::ContentView(SolutionsController * controller) : - m_warningMessageView0(KDFont::SmallFont, I18n::Message::Default, 0.5f, 0.5f, Palette::PrimaryText, Palette::BackgroundAppsSecondary), - m_warningMessageView1(KDFont::SmallFont, I18n::Message::Default, 0.5f, 0.5f, Palette::PrimaryText, Palette::BackgroundAppsSecondary), - m_selectableTableView(controller), + m_warningMessageView0(KDFont::SmallFont, I18n::Message::Default, 0.5f, 0.5f, KDColorBlack, k_backgroundColor), + m_warningMessageView1(KDFont::SmallFont, I18n::Message::Default, 0.5f, 0.5f, KDColorBlack, k_backgroundColor), + m_selectableTableView(controller, controller, controller, controller), m_displayWarningMoreSolutions(false) { - m_selectableTableView.setBackgroundColor(Palette::BackgroundAppsSecondary - ); + m_selectableTableView.setBackgroundColor(k_backgroundColor); m_selectableTableView.setVerticalCellOverlap(0); } void SolutionsController::ContentView::drawRect(KDContext * ctx, KDRect rect) const { if (m_displayWarningMoreSolutions) { - ctx->fillRect(KDRect(0, 0, bounds().width(), k_topMargin), Palette::BackgroundAppsSecondary - ); + ctx->fillRect(KDRect(0, 0, bounds().width(), k_topMargin), k_backgroundColor); } } void SolutionsController::ContentView::setWarning(bool warning) { - m_displayWarningMoreSolutions = warning; - m_selectableTableView.setTopMargin(m_displayWarningMoreSolutions ? 0 : Metric::CommonTopMargin); - layoutSubviews(); + if (m_displayWarningMoreSolutions != warning) { + m_displayWarningMoreSolutions = warning; + m_selectableTableView.setTopMargin(m_displayWarningMoreSolutions ? 0 : Metric::CommonTopMargin); + layoutSubviews(); + } } void SolutionsController::ContentView::setWarningMessages(I18n::Message message0, I18n::Message message1) { @@ -47,28 +50,30 @@ void SolutionsController::ContentView::setWarningMessages(I18n::Message message0 } int SolutionsController::ContentView::numberOfSubviews() const { - return 1+2*m_displayWarningMoreSolutions; + return 1 + 2*m_displayWarningMoreSolutions; } View * SolutionsController::ContentView::subviewAtIndex(int index) { - assert(index >= 0 && index < 1+2*m_displayWarningMoreSolutions); - if (index == 0 && m_displayWarningMoreSolutions) { - return &m_warningMessageView0; - } - if (index == 1 && m_displayWarningMoreSolutions) { - return &m_warningMessageView1; + assert(index >= 0 && index < numberOfSubviews()); + if (m_displayWarningMoreSolutions) { + if (index == 0) { + return &m_warningMessageView0; + } + if (index == 1) { + return &m_warningMessageView1; + } } return &m_selectableTableView; } -void SolutionsController::ContentView::layoutSubviews() { +void SolutionsController::ContentView::layoutSubviews(bool force) { if (m_displayWarningMoreSolutions) { KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height(); - m_warningMessageView0.setFrame(KDRect(0, k_topMargin/2-textHeight, bounds().width(), textHeight)); - m_warningMessageView1.setFrame(KDRect(0, k_topMargin/2, bounds().width(), textHeight)); - m_selectableTableView.setFrame(KDRect(0, k_topMargin, bounds().width(), bounds().height()-k_topMargin)); + m_warningMessageView0.setFrame(KDRect(0, k_topMargin/2-textHeight, bounds().width(), textHeight), force); + m_warningMessageView1.setFrame(KDRect(0, k_topMargin/2, bounds().width(), textHeight), force); + m_selectableTableView.setFrame(KDRect(0, k_topMargin, bounds().width(), bounds().height()-k_topMargin), force); } else { - m_selectableTableView.setFrame(bounds()); + m_selectableTableView.setFrame(bounds(), force); } } @@ -77,19 +82,21 @@ SolutionsController::SolutionsController(Responder * parentResponder, EquationSt m_equationStore(equationStore), m_deltaCell(0.5f, 0.5f), m_delta2Layout(), - m_contentView(this) + m_contentView(this), + m_shouldReplaceFunctionsButNotSymbols(false) { m_delta2Layout = HorizontalLayout::Builder(VerticalOffsetLayout::Builder(CodePointLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Position::Superscript), LayoutHelper::String("-4ac", 4, KDFont::SmallFont)); const char * deltaB = "Δ=b"; static_cast(m_delta2Layout).addOrMergeChildAtIndex(LayoutHelper::String(deltaB, strlen(deltaB), KDFont::SmallFont), 0, false); - for (int i = 0; i < EquationStore::k_maxNumberOfExactSolutions; i++) { + for (int i = 0; i < k_numberOfExactValueCells; i++) { m_exactValueCells[i].setParentResponder(m_contentView.selectableTableView()); } - for (int i = 0; i < EquationStore::k_maxNumberOfApproximateSolutions; i++) { + for (int i = 0; i < k_numberOfApproximateValueCells; i++) { m_approximateValueCells[i].setFont(KDFont::LargeFont); } - for (int i = 0; i < EquationStore::k_maxNumberOfSolutions; i++) { + for (int i = 0; i < k_numberOfSymbolCells; i++) { m_symbolCells[i].setAlignment(0.5f, 0.5f); + m_symbolCells[i].setFont(KDFont::LargeFont); } } @@ -101,16 +108,12 @@ const char * SolutionsController::title() { return I18n::translate(I18n::Message::Solution); } -View * SolutionsController::view() { - return &m_contentView; -} - void SolutionsController::viewWillAppear() { ViewController::viewWillAppear(); bool requireWarning = false; if (m_equationStore->type() == EquationStore::Type::Monovariable) { m_contentView.setWarningMessages(I18n::Message::OnlyFirstSolutionsDisplayed0, I18n::Message::OnlyFirstSolutionsDisplayed1); - requireWarning = m_equationStore->haveMoreApproximationSolutions(App::app()->localContext()); + requireWarning = m_equationStore->haveMoreApproximationSolutions(App::app()->localContext(), m_shouldReplaceFunctionsButNotSymbols); } else if (m_equationStore->type() == EquationStore::Type::PolynomialMonovariable && m_equationStore->numberOfSolutions() == 1) { assert(Preferences::sharedPreferences()->complexFormat() == Preferences::ComplexFormat::Real); m_contentView.setWarningMessages(I18n::Message::PolynomeHasNoRealSolution0, I18n::Message::PolynomeHasNoRealSolution1); @@ -118,8 +121,17 @@ void SolutionsController::viewWillAppear() { } m_contentView.setWarning(requireWarning); m_contentView.selectableTableView()->reloadData(); - if (selectedRow() < 0) { - selectCellAtLocation(0, 0); + selectCellAtLocation(0, 0); +} + +void SolutionsController::viewDidDisappear() { + selectCellAtLocation(-1, -1); +} + +void SolutionsController::didEnterResponderChain(Responder * previousFirstResponder) { + // Select the most left present subview on all cells and reinitialize scroll + for (int i = 0; i < EquationStore::k_maxNumberOfExactSolutions; i++) { + m_exactValueCells[i].reinitSelection(); } } @@ -152,56 +164,82 @@ Responder * SolutionsController::defaultController() { /* TableViewDataSource */ int SolutionsController::numberOfRows() const { - return m_equationStore->numberOfSolutions(); -} - -int SolutionsController::numberOfColumns() const { - return 2; + return m_equationStore->numberOfSolutions() + (m_equationStore->numberOfUserVariables() > 0 ? 1 + m_equationStore->numberOfUserVariables() : 0); } void SolutionsController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { + const int rowOfUserVariablesMessage = userVariablesMessageRow(); + if (j == rowOfUserVariablesMessage) { + // Predefined varaible used/ignored message + assert(i >= 0); + MessageCell * messageCell = static_cast(cell); + messageCell->setHorizontalAlignment(i == 0 ? 1.0f : 0.0f); + if (usedUserVariables()) { + messageCell->setMessage(i == 0 ? I18n::Message::PredefinedVariablesUsedLeft : I18n::Message::PredefinedVariablesUsedRight); + } else { + messageCell->setMessage(i == 0 ? I18n::Message::PredefinedVariablesIgnoredLeft : I18n::Message::PredefinedVariablesIgnoredRight); + } + return; + } if (i == 0) { - // Name of the variable or discriminant if (m_equationStore->type() == EquationStore::Type::PolynomialMonovariable && j == m_equationStore->numberOfSolutions()-1) { - // Discriminant + // Formula of the discriminant EvenOddExpressionCell * deltaCell = static_cast(cell); deltaCell->setLayout(m_delta2Layout); } else { EvenOddBufferTextCell * symbolCell = static_cast(cell); - symbolCell->setFont(KDFont::LargeFont); - char bufferSymbol[Poincare::SymbolAbstract::k_maxNameSize+1]; // Hold at maximum Delta = b^2-4ac or the variable name + a digit - switch (m_equationStore->type()) { - case EquationStore::Type::LinearSystem: + char bufferSymbol[Poincare::SymbolAbstract::k_maxNameSize+2]; // Holds at maximum the variable name + 2 digits (for 10) + if (rowOfUserVariablesMessage < 0 || j < rowOfUserVariablesMessage) { + // Solution symbol name + if (m_equationStore->type() == EquationStore::Type::LinearSystem) { /* The system has more than one variable: the cell text is the * variable name */ strlcpy(bufferSymbol, m_equationStore->variableAtIndex(j), Poincare::SymbolAbstract::k_maxNameSize); - break; - default: + } else { /* The system has one variable but might have many solutions: the cell * text is variableX, with X the row index + 1 (e.g. x1, x2,...) */ int length = strlcpy(bufferSymbol, m_equationStore->variableAtIndex(0), Poincare::SymbolAbstract::k_maxNameSize); - bufferSymbol[length++] = j+'1'; + if (j < 9) { + bufferSymbol[length++] = j+'1'; + } else { + assert(j == 9); + bufferSymbol[length++] = '1'; + bufferSymbol[length++] = '0'; + } bufferSymbol[length] = 0; - break; + } + } else { + // User variable name + assert(rowOfUserVariablesMessage > 0); + strlcpy(bufferSymbol, m_equationStore->userVariableAtIndex(j - rowOfUserVariablesMessage - 1), Poincare::SymbolAbstract::k_maxNameSize); } symbolCell->setText(bufferSymbol); } } else { - // Value of the variable or discriminant - if (m_equationStore->type() == EquationStore::Type::Monovariable) { - EvenOddBufferTextCell * valueCell = static_cast(cell); - constexpr int precision = Preferences::LargeNumberOfSignificantDigits; - constexpr int bufferSize = PrintFloat::charSizeForFloatsWithPrecision(precision); - char bufferValue[bufferSize]; - PoincareHelpers::ConvertFloatToText(m_equationStore->approximateSolutionAtIndex(j), bufferValue, bufferSize, precision); - valueCell->setText(bufferValue); - } else { - Shared::ScrollableExactApproximateExpressionsCell * valueCell = static_cast(cell); - Poincare::Layout exactLayout = m_equationStore->exactSolutionLayoutsAtIndexAreIdentical(j) ? Poincare::Layout() : m_equationStore->exactSolutionLayoutAtIndex(j, true); - valueCell->setLayouts(m_equationStore->exactSolutionLayoutAtIndex(j, false), exactLayout); - if (!exactLayout.isUninitialized()) { - valueCell->setEqualMessage(m_equationStore->exactSolutionLayoutsAtIndexAreEqual(j) ? I18n::Message::Equal : I18n::Message::AlmostEqual); + if (rowOfUserVariablesMessage < 0 || j < rowOfUserVariablesMessage) { + if (m_equationStore->type() == EquationStore::Type::Monovariable) { + // Values of the solutions + EvenOddBufferTextCell * valueCell = static_cast(cell); + constexpr int precision = Preferences::LargeNumberOfSignificantDigits; + constexpr int bufferSize = PrintFloat::charSizeForFloatsWithPrecision(precision); + char bufferValue[bufferSize]; + PoincareHelpers::ConvertFloatToText(m_equationStore->approximateSolutionAtIndex(j), bufferValue, bufferSize, precision); + valueCell->setText(bufferValue); + } else { + // Values of the solutions or discriminant + ScrollableTwoExpressionsCell * valueCell = static_cast(cell); + Poincare::Layout exactLayout = m_equationStore->exactSolutionLayoutsAtIndexAreIdentical(j) ? Poincare::Layout() : m_equationStore->exactSolutionLayoutAtIndex(j, true); + valueCell->setLayouts(exactLayout, m_equationStore->exactSolutionLayoutAtIndex(j, false)); + if (!exactLayout.isUninitialized()) { + valueCell->setEqualMessage(m_equationStore->exactSolutionLayoutsAtIndexAreEqual(j) ? I18n::Message::Equal : I18n::Message::AlmostEqual); + } } + } else { + // Values of the solutions or discriminant + ScrollableTwoExpressionsCell * valueCell = static_cast(cell); + const char * symbol = m_equationStore->userVariableAtIndex(j - rowOfUserVariablesMessage - 1); + Poincare::Layout layout = PoincareHelpers::CreateLayout(App::app()->localContext()->expressionForSymbolAbstract(Poincare::Symbol::Builder(symbol, strlen(symbol)), false)); + valueCell->setLayouts(Poincare::Layout(), layout); } } EvenOddCell * evenOddCell = static_cast(cell); @@ -209,22 +247,29 @@ void SolutionsController::willDisplayCellAtLocation(HighlightCell * cell, int i, } KDCoordinate SolutionsController::columnWidth(int i) { - if (i == 0) { - return k_symbolCellWidth; - } - return k_valueCellWidth; + return i == 0 ? k_symbolCellWidth : k_valueCellWidth; } KDCoordinate SolutionsController::rowHeight(int j) { - if (m_equationStore->type() == EquationStore::Type::Monovariable) { - return k_defaultCellHeight; + const int rowOfUserVariablesMessage = userVariablesMessageRow(); + if (rowOfUserVariablesMessage < 0 || j < rowOfUserVariablesMessage) { + if (m_equationStore->type() == EquationStore::Type::Monovariable) { + return k_defaultCellHeight; + } + Poincare::Layout exactLayout = m_equationStore->exactSolutionLayoutAtIndex(j, true); + Poincare::Layout approximateLayout = m_equationStore->exactSolutionLayoutAtIndex(j, false); + KDCoordinate exactLayoutHeight = exactLayout.layoutSize().height(); + KDCoordinate approximateLayoutHeight = approximateLayout.layoutSize().height(); + KDCoordinate layoutHeight = maxCoordinate(exactLayout.baseline(), approximateLayout.baseline()) + maxCoordinate(exactLayoutHeight-exactLayout.baseline(), approximateLayoutHeight-approximateLayout.baseline()); + return layoutHeight + 2 * Metric::CommonSmallMargin; } - Poincare::Layout exactLayout = m_equationStore->exactSolutionLayoutAtIndex(j, true); - Poincare::Layout approximateLayout = m_equationStore->exactSolutionLayoutAtIndex(j, false); - KDCoordinate exactLayoutHeight = exactLayout.layoutSize().height(); - KDCoordinate approximateLayoutHeight = approximateLayout.layoutSize().height(); - KDCoordinate layoutHeight = maxCoordinate(exactLayout.baseline(), approximateLayout.baseline()) + maxCoordinate(exactLayoutHeight-exactLayout.baseline(), approximateLayoutHeight-approximateLayout.baseline()); - return layoutHeight + 2 * Metric::CommonSmallMargin; + if (j == rowOfUserVariablesMessage) { + return Metric::CommonTopMargin + k_defaultCellHeight + Metric::CommonBottomMargin; + } + // TODO: memoize user symbols if too slow + const char * symbol = m_equationStore->userVariableAtIndex(j - rowOfUserVariablesMessage - 1); + Poincare::Layout layout = PoincareHelpers::CreateLayout(App::app()->localContext()->expressionForSymbolAbstract(Poincare::Symbol::Builder(symbol, strlen(symbol)), false)); + return layout.layoutSize().height() + 2 * Metric::CommonSmallMargin; } KDCoordinate SolutionsController::cumulatedWidthFromIndex(int i) { @@ -233,11 +278,9 @@ KDCoordinate SolutionsController::cumulatedWidthFromIndex(int i) { return 0; case 1: return k_symbolCellWidth; - case 2: - return k_symbolCellWidth+k_valueCellWidth; default: - assert(false); - return 0; + assert(i == 2); + return k_symbolCellWidth+k_valueCellWidth; } } @@ -254,41 +297,73 @@ int SolutionsController::indexFromCumulatedWidth(KDCoordinate offsetX) { } HighlightCell * SolutionsController::reusableCell(int index, int type) { - if (type == 0) { - return &m_symbolCells[index]; - } else if (type == 1) { - return &m_deltaCell; - } else if (type == 2) { - return &m_exactValueCells[index]; + switch (type) { + case k_symbolCellType: + return &m_symbolCells[index]; + case k_deltaCellType: + return &m_deltaCell; + case k_exactValueCellType: + return &m_exactValueCells[index]; + case k_messageCellType: + return &m_messageCells[index]; + default: + assert(type == k_approximateValueCellType); + return &m_approximateValueCells[index]; } - return &m_approximateValueCells[index]; } int SolutionsController::reusableCellCount(int type) { switch (type) { - case 0: - return EquationStore::k_maxNumberOfSolutions; - case 1: + case k_symbolCellType: + return k_numberOfSymbolCells; + case k_deltaCellType: return 1; - case 2: - return EquationStore::k_maxNumberOfExactSolutions; + case k_exactValueCellType: + return k_numberOfExactValueCells; + case k_messageCellType: + return k_numberOfMessageCells; default: - return EquationStore::k_maxNumberOfApproximateSolutions; + assert(type == k_approximateValueCellType); + return k_numberOfApproximateValueCells; } } int SolutionsController::typeAtLocation(int i, int j) { + const int rowOfUserVariableMessage = userVariablesMessageRow(); + if (j == rowOfUserVariableMessage) { + return k_messageCellType; + } if (i == 0) { if (m_equationStore->type() == EquationStore::Type::PolynomialMonovariable && j == m_equationStore->numberOfSolutions()-1) { - return 1; + return k_deltaCellType; } - return 0; + return k_symbolCellType; } - return m_equationStore->type() == EquationStore::Type::Monovariable ? 3 : 2; + if ((rowOfUserVariableMessage < 0 || j < rowOfUserVariableMessage) && m_equationStore->type() == EquationStore::Type::Monovariable) { + return k_approximateValueCellType; + } + return k_exactValueCellType; } void SolutionsController::didBecomeFirstResponder() { Container::activeApp()->setFirstResponder(m_contentView.selectableTableView()); } +void SolutionsController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { + const int rowOfUserVariablesMessage = userVariablesMessageRow(); + if (rowOfUserVariablesMessage < 0) { + return; + } + assert(rowOfUserVariablesMessage > 0); + // Forbid the selection of the messages row + if (t->selectedRow() == rowOfUserVariablesMessage) { + t->selectCellAtLocation(t->selectedColumn(), rowOfUserVariablesMessage + (rowOfUserVariablesMessage < previousSelectedCellY ? -1 : 1)); + } +} + +int SolutionsController::userVariablesMessageRow() const { + assert(m_equationStore->numberOfUserVariables() >= 0); + return m_equationStore->numberOfUserVariables() == 0 ? -1 : m_equationStore->numberOfSolutions(); +} + } diff --git a/apps/solver/solutions_controller.h b/apps/solver/solutions_controller.h index 33208d7c2..99247ac03 100644 --- a/apps/solver/solutions_controller.h +++ b/apps/solver/solutions_controller.h @@ -3,25 +3,29 @@ #include #include "equation_store.h" -#include "../shared/scrollable_exact_approximate_expressions_cell.h" +#include "../shared/scrollable_two_expressions_cell.h" #include namespace Solver { -class SolutionsController : public ViewController, public AlternateEmptyViewDefaultDelegate, public SelectableTableViewDataSource, public TableViewDataSource { +class SolutionsController : public ViewController, public AlternateEmptyViewDefaultDelegate, public SelectableTableViewDataSource, public TableViewDataSource, public SelectableTableViewDelegate { public: SolutionsController(Responder * parentResponder, EquationStore * equationStore); + void setShouldReplaceFuncionsButNotSymbols(bool shouldReplaceFuncionsButNotSymbols) { m_shouldReplaceFunctionsButNotSymbols = shouldReplaceFuncionsButNotSymbols; } + bool shouldReplaceFuncionsButNotSymbols() const { return m_shouldReplaceFunctionsButNotSymbols; } /* ViewController */ const char * title() override; - View * view() override; + View * view() override { return &m_contentView; } void viewWillAppear() override; + void viewDidDisappear() override; + void didEnterResponderChain(Responder * previousFirstResponder) override; /* AlternateEmptyViewDefaultDelegate */ bool isEmpty() const override; virtual I18n::Message emptyMessage() override; virtual Responder * defaultController() override; /* TableViewDataSource */ int numberOfRows() const override; - int numberOfColumns() const override; + int numberOfColumns() const override { return 2; } void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; KDCoordinate columnWidth(int i) override; KDCoordinate rowHeight(int j) override; @@ -32,37 +36,80 @@ public: int typeAtLocation(int i, int j) override; /* Responder */ void didBecomeFirstResponder() override; - + /* SelectableTableViewDelegate */ + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false) override; private: class ContentView : public View { public: + constexpr static KDCoordinate k_topMargin = 50; + constexpr static KDColor k_backgroundColor = Palette::BackgroundAppsSecondary; ContentView(SolutionsController * controller); void drawRect(KDContext * ctx, KDRect rect) const override; void setWarning(bool warning); void setWarningMessages(I18n::Message message0, I18n::Message message1); - SelectableTableView * selectableTableView() { - return &m_selectableTableView; - } + SelectableTableView * selectableTableView() { return &m_selectableTableView; } private: - constexpr static KDCoordinate k_topMargin = 50; + constexpr static KDCoordinate k_middleMargin = 50; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; MessageTextView m_warningMessageView0; MessageTextView m_warningMessageView1; SelectableTableView m_selectableTableView; bool m_displayWarningMoreSolutions; }; + + class MessageCell : public HighlightCell { + public: + MessageCell() : m_messageView(KDFont::SmallFont, (I18n::Message)0, 0.0f, k_verticalAlignment, KDColorBlack, SolutionsController::ContentView::k_backgroundColor) {} + void setBackgroundColor(KDColor color) { m_messageView.setBackgroundColor(color); } + void setHorizontalAlignment(float alignment) { m_messageView.setAlignment(alignment, k_verticalAlignment); } + void setMessage(I18n::Message message) { m_messageView.setMessage(message); } + private: + constexpr static float k_verticalAlignment = 0.8f; + int numberOfSubviews() const override { return 1; } + View * subviewAtIndex(int index) override { assert(index == 0); return &m_messageView; } + void layoutSubviews(bool force = false) override { m_messageView.setFrame(bounds(), force); } + MessageTextView m_messageView; + }; + + // Cell types + constexpr static int k_symbolCellType = 0; + constexpr static int k_deltaCellType = 1; + constexpr static int k_exactValueCellType = 2; + constexpr static int k_approximateValueCellType = 3; + constexpr static int k_messageCellType = 4; + + // Heights and widths + constexpr static KDCoordinate k_defaultCellHeight = 20; constexpr static int k_symbolCellWidth = 90; constexpr static int k_valueCellWidth = 190; - constexpr static KDCoordinate k_defaultCellHeight = 20; + + // Number of cells + constexpr static int k_maxNumberOfVisibleCells = (Ion::Display::Height - 3 * Metric::TitleBarHeight) / k_defaultCellHeight + 1; // When displaying approximate solutions for cos(x) = 0 between 0 and 1800 and scrolling down + static_assert(k_maxNumberOfVisibleCells == 10, "k_maxNumberOfVisibleCells has changed"); //This assert is just for information purposes + static_assert(k_maxNumberOfVisibleCells <= EquationStore::k_maxNumberOfSolutions + Poincare::Expression::k_maxNumberOfVariables, "We can reduce the number of cells in Solver:SolutionsController."); + constexpr static int k_maxNumberOfSymbols = EquationStore::k_maxNumberOfSolutions + Poincare::Expression::k_maxNumberOfVariables; + constexpr static int k_numberOfSymbolCells = k_maxNumberOfVisibleCells < k_maxNumberOfSymbols ? k_maxNumberOfVisibleCells : k_maxNumberOfSymbols; + constexpr static int k_maxNumberOfExactValues = EquationStore::k_maxNumberOfExactSolutions + Poincare::Expression::k_maxNumberOfVariables; + constexpr static int k_numberOfExactValueCells = k_maxNumberOfVisibleCells < k_maxNumberOfExactValues ? k_maxNumberOfVisibleCells : k_maxNumberOfExactValues; + constexpr static int k_numberOfApproximateValueCells = 1 + (k_maxNumberOfVisibleCells < EquationStore::k_maxNumberOfApproximateSolutions ? k_maxNumberOfVisibleCells : EquationStore::k_maxNumberOfApproximateSolutions); + constexpr static int k_numberOfMessageCells = 2; + + bool usedUserVariables() const { + return m_equationStore->userVariablesUsed(); + } + int userVariablesMessageRow() const; + EquationStore * m_equationStore; - EvenOddBufferTextCell m_symbolCells[EquationStore::k_maxNumberOfSolutions]; + EvenOddBufferTextCell m_symbolCells[k_numberOfSymbolCells]; EvenOddExpressionCell m_deltaCell; Poincare::Layout m_delta2Layout; - Shared::ScrollableExactApproximateExpressionsCell m_exactValueCells[EquationStore::k_maxNumberOfExactSolutions]; - EvenOddBufferTextCell m_approximateValueCells[EquationStore::k_maxNumberOfApproximateSolutions]; + Shared::ScrollableTwoExpressionsCell m_exactValueCells[k_numberOfExactValueCells]; + EvenOddBufferTextCell m_approximateValueCells[k_numberOfApproximateValueCells]; + MessageCell m_messageCells[k_numberOfMessageCells]; ContentView m_contentView; + bool m_shouldReplaceFunctionsButNotSymbols; }; } diff --git a/apps/solver/test/equation_store.cpp b/apps/solver/test/equation_store.cpp index 396c6a9ed..c36817d6c 100644 --- a/apps/solver/test/equation_store.cpp +++ b/apps/solver/test/equation_store.cpp @@ -11,24 +11,26 @@ using namespace Poincare; namespace Solver { -void addEquationWithText(EquationStore * equationStore, const char * text) { +void addEquationWithText(EquationStore * equationStore, const char * text, Context * context) { Ion::Storage::Record::ErrorStatus err = equationStore->addEmptyModel(); quiz_assert_print_if_failure(err == Ion::Storage::Record::ErrorStatus::None, text); (void) err; // Silence warning in DEBUG=0 Ion::Storage::Record record = equationStore->recordAtIndex(equationStore->numberOfModels()-1); Shared::ExpiringPointer model = equationStore->modelForRecord(record); - model->setContent(text); + model->setContent(text, context); } -void assert_equation_system_exact_solve_to(const char * equations[], EquationStore::Error error, EquationStore::Type type, const char * variables[], const char * solutions[], int numberOfSolutions) { +void assert_equation_system_exact_solve_to(const char * equations[], EquationStore::Error error, EquationStore::Type type, const char * variables[], const char * solutions[], int numberOfSolutions, bool didReplaceFunctionsButNotSymbols = false) { Shared::GlobalContext globalContext; EquationStore equationStore; int index = 0; while (equations[index] != 0) { - addEquationWithText(&equationStore, equations[index++]); + addEquationWithText(&equationStore, equations[index++], &globalContext); } - EquationStore::Error err = equationStore.exactSolve(&globalContext); + bool replaceFunctionsButNotSymbols = false; + EquationStore::Error err = equationStore.exactSolve(&globalContext, &replaceFunctionsButNotSymbols); quiz_assert_print_if_failure(err == error, equations[0]); + quiz_assert_print_if_failure(replaceFunctionsButNotSymbols == didReplaceFunctionsButNotSymbols, equations[0]); if (err != EquationStore::Error::NoError) { equationStore.removeAll(); return; @@ -58,18 +60,19 @@ void assert_equation_system_exact_solve_to(const char * equations[], EquationSto void assert_equation_approximate_solve_to(const char * equations, double xMin, double xMax, const char * variable, double solutions[], int numberOfSolutions, bool hasMoreSolutions) { Shared::GlobalContext globalContext; EquationStore equationStore; - addEquationWithText(&equationStore, equations); - EquationStore::Error err = equationStore.exactSolve(&globalContext); + addEquationWithText(&equationStore, equations, &globalContext); + bool replaceFunctionsButNotSymbols = false; + EquationStore::Error err = equationStore.exactSolve(&globalContext, &replaceFunctionsButNotSymbols); quiz_assert(err == EquationStore::Error::RequireApproximateSolution); equationStore.setIntervalBound(0, xMin); equationStore.setIntervalBound(1, xMax); - equationStore.approximateSolve(&globalContext); + equationStore.approximateSolve(&globalContext, replaceFunctionsButNotSymbols); quiz_assert(equationStore.numberOfSolutions() == numberOfSolutions); quiz_assert(strcmp(equationStore.variableAtIndex(0), variable)== 0); for (int i = 0; i < numberOfSolutions; i++) { quiz_assert(std::fabs(equationStore.approximateSolutionAtIndex(i) - solutions[i]) < 1E-5); } - quiz_assert(equationStore.haveMoreApproximationSolutions(&globalContext) == hasMoreSolutions); + quiz_assert(equationStore.haveMoreApproximationSolutions(&globalContext, replaceFunctionsButNotSymbols) == hasMoreSolutions); equationStore.removeAll(); } @@ -157,6 +160,11 @@ QUIZ_CASE(equation_solve) { const char * solutions14[] = {"\u0012\u0012-π-20\u0013/\u00128\u0013\u0013", "\u0012\u0012π+20\u0013/\u00128\u0013\u0013", "\u0012\u0012π\u0013/\u00124\u0013\u0013"}; // (-π-20)/8, (π+20)/8, π/4 assert_equation_system_exact_solve_to(equations14, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variablesxyz, solutions14, 3); + const char * variablesxyzabc[] = {"x", "y", "z", "a", "b", "c"}; + const char * equations22[] = {"x+y=0", "3x+y+z=-5", "4z-π=0", "a+b+c=0", "a = 3", "c = a+2", 0}; + const char * solutions22[] = {"\u0012\u0012-π-20\u0013/\u00128\u0013\u0013", "\u0012\u0012π+20\u0013/\u00128\u0013\u0013", "\u0012\u0012π\u0013/\u00124\u0013\u0013", "3", "-8", "5"}; // (-π-20)/8, (π+20)/8, π/4, 3, 5, -8 + assert_equation_system_exact_solve_to(equations22, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variablesxyzabc, solutions22, 6); + // Monovariable non-polynomial equation double solutions15[] = {-90.0, 90.0}; assert_equation_approximate_solve_to("cos(x)=0", -100.0, 100.0, "x", solutions15, 2, false); @@ -256,25 +264,116 @@ QUIZ_CASE(equation_solve_complex_format) { const char * solutions5Polar[] = {"\u0012\u00123\u0013/\u00122\u0013\u0013ℯ^\u0012\u0012\u00122π\u0013/\u00123\u0013\u0013𝐢\u0013"}; //3/2ℯ^\u0012\u00122π\u0012/3\u0013𝐢"}; assert_equation_system_exact_solve_to(equations5, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variablesx, solutions5Polar, 1); + // Put back the complex format + Poincare::Preferences::sharedPreferences()->setComplexFormat(Poincare::Preferences::ComplexFormat::Real); } QUIZ_CASE(equation_and_symbolic_computation) { - // x+a=0 : non linear system - const char * equation[] = {"x+a=0", 0}; - assert_equation_system_exact_solve_to(equation, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, nullptr, nullptr, INT_MAX); + const char * equation1[] = {"x+a=0", 0}; + assert_equation_system_exact_solve_to(equation1, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, nullptr, nullptr, INT_MAX); // -3->a Shared::GlobalContext globalContext; Expression::ParseAndSimplify("-3→a", &globalContext, Preferences::ComplexFormat::Polar, Preferences::AngleUnit::Degree); // x+a = 0 : x = 3 - const char * variables[] = {"x", ""}; - const char * solutions[] = {"3"}; - assert_equation_system_exact_solve_to(equation, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variables, solutions, 1); + const char * variables1[] = {"x", ""}; + const char * solutions1[] = {"3"}; + assert_equation_system_exact_solve_to(equation1, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variables1, solutions1, 1); + + /* a = 0 : the equation has no solution as the user defined a = -3, so a is + * not replaced with its context value and the result is a = 0. */ + const char * equation2[] = {"a=0", 0}; + const char * variables2[] = {"a", ""}; + const char * solutions2[] = {"0"}; + assert_equation_system_exact_solve_to(equation2, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variables2, solutions2, 1, true); + + // 4->b + Expression::ParseAndSimplify("-4→b", &globalContext, Preferences::ComplexFormat::Polar, Preferences::AngleUnit::Degree); + + /* a + b = 0 : the equation has no solution as the user defined a = -3, and + * b = -4 so a and b are not replaced with their context values and the result + * is an infinity of solutions. */ + const char * equation3[] = {"a+b=0", 0}; + const char * variables3[] = {"a", "b", ""}; + assert_equation_system_exact_solve_to(equation3, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variables3, nullptr, INT_MAX, true); + + // a + b + c = 0 : the equation has the solution c = -7 + const char * equation4[] = {"a+b+c=0", 0}; + const char * variables4[] = {"c", ""}; + const char * solutions4[] = {"7"}; + assert_equation_system_exact_solve_to(equation4, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variables4, solutions4, 1); + + /* a + c = 0 and a = 3: the system has no solution as the user defined a = -3, + * so a is not replaced with its context value and the result is a = 3 and + * c = -3. */ + const char * equation5[] = {"a+c=0", "a=3", 0}; + const char * variables5[] = {"a", "c", ""}; + const char * solutions5[] = {"3", "-3"}; + assert_equation_system_exact_solve_to(equation5, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variables5, solutions5, 2, true); + + // x+1->f(x) + Expression::ParseAndSimplify("x+1→f(x)", &globalContext, Preferences::ComplexFormat::Polar, Preferences::AngleUnit::Degree); + + // f(x) = 0 : x = -1 + const char * equation6[] = {"f(x)=0", 0}; + const char * variables6[] = {"x", ""}; + const char * solutions6[] = {"-1",}; + assert_equation_system_exact_solve_to(equation6, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variables6, solutions6, 1); + + /* f(a) = 0 : the equation has no solution as the user defined a = -3, so a is + * not replaced with its context value and the result is a = -1. */ + const char * equation7[] = {"f(a)=0", 0}; + const char * variables7[] = {"a", ""}; + const char * solutions7[] = {"-1",}; + assert_equation_system_exact_solve_to(equation7, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variables7, solutions7, 1, true); + + // a+x+1->g(x) + Expression::ParseAndSimplify("a+x+2→g(x)", &globalContext, Preferences::ComplexFormat::Polar, Preferences::AngleUnit::Degree); + + // g(x) = 0 : x = 2 + const char * equation8[] = {"g(x)=0", 0}; + const char * variables8[] = {"x", ""}; + const char * solutions8[] = {"1",}; + assert_equation_system_exact_solve_to(equation8, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variables8, solutions8, 1); + + /* g(a) = 0 : the equation has no solution as the user defined a = -3, so a is + * not replaced with its context value and the equation becomes a+a+2=0. The + * solution is a = -1. */ + const char * equation9[] = {"g(a)=0", 0}; + const char * variables9[] = {"a", ""}; + const char * solutions9[] = {"-1",}; + assert_equation_system_exact_solve_to(equation9, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variables9, solutions9, 1, true); + + /* c = d + * d = 5 + * h(x) = c + d + 3 + * /c = -3 + * \h(x) = 0 + * c and d context values should not be used, and the solution is c = -3, d = 0 */ + Expression::ParseAndSimplify("5→d", &globalContext, Preferences::ComplexFormat::Polar, Preferences::AngleUnit::Degree); + Expression::ParseAndSimplify("d→c", &globalContext, Preferences::ComplexFormat::Polar, Preferences::AngleUnit::Degree); + Expression::ParseAndSimplify("c+d+3→h(x)", &globalContext, Preferences::ComplexFormat::Polar, Preferences::AngleUnit::Degree); + const char * equation10[] = {"h(x)=0", "c = -3", 0}; + const char * variables10[] = {"c", "d", ""}; + const char * solutions10[] = {"-3", "0"}; + assert_equation_system_exact_solve_to(equation10, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variables10, solutions10, 2, true); + + const char * equation11[] = {"c+d=5", "c-d=1", 0}; + const char * variables11[] = {"c", "d", ""}; + const char * solutions11[] = {"3", "2"}; + assert_equation_system_exact_solve_to(equation11, EquationStore::Error::NoError, EquationStore::Type::LinearSystem, (const char **)variables11, solutions11, 2, true); + // Clean the storage Ion::Storage::sharedStorage()->recordNamed("a.exp").destroy(); + Ion::Storage::sharedStorage()->recordNamed("b.exp").destroy(); + Ion::Storage::sharedStorage()->recordNamed("c.exp").destroy(); + Ion::Storage::sharedStorage()->recordNamed("d.exp").destroy(); + Ion::Storage::sharedStorage()->recordNamed("f.func").destroy(); + Ion::Storage::sharedStorage()->recordNamed("g.func").destroy(); + Ion::Storage::sharedStorage()->recordNamed("h.func").destroy(); } } diff --git a/apps/statistics/app.cpp b/apps/statistics/app.cpp index 120edaaa3..3d40b76ba 100644 --- a/apps/statistics/app.cpp +++ b/apps/statistics/app.cpp @@ -1,5 +1,6 @@ #include "app.h" #include "stat_icon.h" +#include "../apps_container.h" #include using namespace Shared; @@ -35,7 +36,7 @@ App::Snapshot::Snapshot() : } App * App::Snapshot::unpack(Container * container) { - return new (container->currentAppBuffer()) App(this); + return new (container->currentAppBuffer()) App(this, static_cast(container)->globalContext()); } void App::Snapshot::reset() { @@ -53,7 +54,7 @@ App::Descriptor * App::Snapshot::descriptor() { return &descriptor; } -App::App(Snapshot * snapshot) : +App::App(Snapshot * snapshot, Poincare::Context * parentContext) : TextFieldDelegateApp(snapshot, &m_tabViewController), m_calculationController(&m_calculationAlternateEmptyViewController, &m_calculationHeader, snapshot->store()), m_calculationAlternateEmptyViewController(&m_calculationHeader, &m_calculationController, &m_calculationController), @@ -65,7 +66,7 @@ App::App(Snapshot * snapshot) : m_histogramAlternateEmptyViewController(&m_histogramHeader, &m_histogramController, &m_histogramController), m_histogramHeader(&m_histogramStackViewController, &m_histogramAlternateEmptyViewController, &m_histogramController), m_histogramStackViewController(&m_tabViewController, &m_histogramHeader), - m_storeController(&m_storeHeader, this, snapshot->store(), &m_storeHeader), + m_storeController(&m_storeHeader, this, snapshot->store(), &m_storeHeader, parentContext), m_storeHeader(&m_storeStackViewController, &m_storeController, &m_storeController), m_storeStackViewController(&m_tabViewController, &m_storeHeader), m_tabViewController(&m_modalViewController, snapshot, &m_storeStackViewController, &m_histogramStackViewController, &m_boxHeader, &m_calculationHeader) diff --git a/apps/statistics/app.h b/apps/statistics/app.h index 6deed5259..8492df65a 100644 --- a/apps/statistics/app.h +++ b/apps/statistics/app.h @@ -45,7 +45,7 @@ public: BoxView::Quantile m_selectedBoxQuantile; }; private: - App(Snapshot * snapshot); + App(Snapshot * snapshot, Poincare::Context * parentContext); CalculationController m_calculationController; AlternateEmptyViewController m_calculationAlternateEmptyViewController; ButtonRowController m_calculationHeader; diff --git a/apps/statistics/box_axis_view.cpp b/apps/statistics/box_axis_view.cpp index 504e6b453..5057fcdd5 100644 --- a/apps/statistics/box_axis_view.cpp +++ b/apps/statistics/box_axis_view.cpp @@ -9,7 +9,7 @@ void BoxAxisView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(rect, Palette::BackgroundHard); KDRect lineRect = KDRect(0, k_axisMargin, bounds().width(), 1); ctx->fillRect(lineRect, Palette::PrimaryText); - drawLabels(ctx, rect, Axis::Horizontal, false, false, true, k_axisMargin); + drawLabelsAndGraduations(ctx, rect, Axis::Horizontal, false, false, true, k_axisMargin); } char * BoxAxisView::label(Axis axis, int index) const { diff --git a/apps/statistics/histogram_controller.cpp b/apps/statistics/histogram_controller.cpp index d92671801..1f153855a 100644 --- a/apps/statistics/histogram_controller.cpp +++ b/apps/statistics/histogram_controller.cpp @@ -2,6 +2,7 @@ #include "../shared/poincare_helpers.h" #include "../shared/text_helpers.h" #include "app.h" +#include #include #include #include @@ -70,7 +71,8 @@ void HistogramController::viewWillAppear() { } void HistogramController::willExitResponderChain(Responder * nextFirstResponder) { - if (nextFirstResponder == nullptr || nextFirstResponder == tabController()) { + if (nextFirstResponder == tabController()) { + assert(tabController() != nullptr); if (selectedSeriesIndex() >= 0) { m_view.dataViewAtIndex(selectedSeriesIndex())->setForceOkDisplay(false); } @@ -246,9 +248,14 @@ void HistogramController::initBarParameters() { assert(selectedSeriesIndex() >= 0 && m_store->sumOfOccurrences(selectedSeriesIndex()) > 0); preinitXRangeParameters(); m_store->setFirstDrawnBarAbscissa(m_store->xMin()); - float barWidth = m_store->xGridUnit(); - if (barWidth <= 0.0f) { - barWidth = 1.0f; + double barWidth = m_store->xGridUnit(); + if (barWidth <= 0.0) { + barWidth = 1.0; + } else { + // Truncate the bar width, as we convert from float to double + const double precision = 7; // TODO factorize? This is an experimental value, the same as in Expression;;Epsilon() + const double logBarWidth = IEEE754::exponentBase10(barWidth); + barWidth = ((int)(barWidth * std::pow(10.0, precision - logBarWidth))) * std::pow(10.0, -precision + logBarWidth); } m_store->setBarWidth(barWidth); } diff --git a/apps/statistics/histogram_view.cpp b/apps/statistics/histogram_view.cpp index 7dca2421a..e6bdc2a1b 100644 --- a/apps/statistics/histogram_view.cpp +++ b/apps/statistics/histogram_view.cpp @@ -42,7 +42,7 @@ void HistogramView::drawRect(KDContext * ctx, KDRect rect) const { m_controller->setCurrentDrawnSeries(m_series); ctx->fillRect(rect, Palette::BackgroundHard); drawAxis(ctx, rect, Axis::Horizontal); - drawLabels(ctx, rect, Axis::Horizontal, false, !m_displayLabels); + drawLabelsAndGraduations(ctx, rect, Axis::Horizontal, false, !m_displayLabels); /* We memoize the total size to avoid recomputing it in double precision at * every call to EvaluateHistogramAtAbscissa() */ float totalSize = m_store->sumOfOccurrences(m_series); diff --git a/apps/statistics/multiple_boxes_view.cpp b/apps/statistics/multiple_boxes_view.cpp index 5fdb64c60..a19fc8bfc 100644 --- a/apps/statistics/multiple_boxes_view.cpp +++ b/apps/statistics/multiple_boxes_view.cpp @@ -26,7 +26,7 @@ int MultipleBoxesView::seriesOfSubviewAtIndex(int index) { return static_cast(subviewAtIndex(index))->series(); } -void MultipleBoxesView::layoutDataSubviews() { +void MultipleBoxesView::layoutDataSubviews(bool force) { int numberOfDataSubviews = m_store->numberOfNonEmptySeries(); assert(numberOfDataSubviews > 0); KDCoordinate bannerHeight = bannerFrame().height(); @@ -35,11 +35,11 @@ void MultipleBoxesView::layoutDataSubviews() { for (int i = 0; i < Store::k_numberOfSeries; i++) { if (!m_store->seriesIsEmpty(i)) { KDRect frame = KDRect(0, displayedSubviewIndex*subviewHeight, bounds().width(), subviewHeight); - dataViewAtIndex(i)->setFrame(frame); + dataViewAtIndex(i)->setFrame(frame, force); displayedSubviewIndex++; } } - m_axisView.setFrame(KDRect(0, displayedSubviewIndex*subviewHeight, bounds().width(), bounds().height() - bannerHeight - displayedSubviewIndex*subviewHeight)); + m_axisView.setFrame(KDRect(0, displayedSubviewIndex*subviewHeight, bounds().width(), bounds().height() - bannerHeight - displayedSubviewIndex*subviewHeight), force); } void MultipleBoxesView::reload() { diff --git a/apps/statistics/multiple_boxes_view.h b/apps/statistics/multiple_boxes_view.h index 3333907a3..3eb6d32c8 100644 --- a/apps/statistics/multiple_boxes_view.h +++ b/apps/statistics/multiple_boxes_view.h @@ -19,7 +19,7 @@ public: int seriesOfSubviewAtIndex(int index) override; BoxBannerView * bannerView() override { return &m_bannerView; } BoxView * dataViewAtIndex(int index) override; - void layoutDataSubviews() override; + void layoutDataSubviews(bool force) override; void reload() override; // View diff --git a/apps/statistics/multiple_data_view.cpp b/apps/statistics/multiple_data_view.cpp index 37ea55931..569d7827d 100644 --- a/apps/statistics/multiple_data_view.cpp +++ b/apps/statistics/multiple_data_view.cpp @@ -7,7 +7,7 @@ namespace Statistics { void MultipleDataView::setDisplayBanner(bool display) { m_displayBanner = display; - layoutBanner(); + layoutBanner(false); } void MultipleDataView::reload() { @@ -66,14 +66,14 @@ View * MultipleDataView::subviewAtIndex(int index) { return nullptr; } -void MultipleDataView::layoutSubviews() { +void MultipleDataView::layoutSubviews(bool force) { // We need to set the banner width first, so its height can be computed - bannerView()->setFrame(KDRect(0, 0, bounds().width(), 0)); - layoutDataSubviews(); - layoutBanner(); + bannerView()->setFrame(KDRect(0, 0, bounds().width(), 0), force); + layoutDataSubviews(force); + layoutBanner(force); } -void MultipleDataView::layoutDataSubviews() { +void MultipleDataView::layoutDataSubviews(bool force) { int numberDataSubviews = m_store->numberOfNonEmptySeries(); assert(numberDataSubviews > 0); KDCoordinate bannerHeight = bannerView()->minimalSizeForOptimalDisplay().height(); @@ -83,7 +83,7 @@ void MultipleDataView::layoutDataSubviews() { if (!m_store->seriesIsEmpty(i)) { CurveView * dataView = dataViewAtIndex(i); KDRect frame = KDRect(0, displayedSubviewIndex*subviewHeight, bounds().width(), subviewHeight); - dataView->setFrame(frame); + dataView->setFrame(frame, force); displayedSubviewIndex++; } } @@ -100,13 +100,13 @@ KDRect MultipleDataView::bannerFrame() const { return frame; } -void MultipleDataView::layoutBanner() { +void MultipleDataView::layoutBanner(bool force) { KDCoordinate bannerHeight = bannerView()->minimalSizeForOptimalDisplay().height(); if (m_displayBanner) { - bannerView()->setFrame(bannerFrame()); + bannerView()->setFrame(bannerFrame(), force); } else { KDRect frame = KDRect(0, bounds().height() - bannerHeight, bounds().width(), 0); - bannerView()->setFrame(frame); + bannerView()->setFrame(frame, force); } } diff --git a/apps/statistics/multiple_data_view.h b/apps/statistics/multiple_data_view.h index e2b9eae0b..40c3dcf72 100644 --- a/apps/statistics/multiple_data_view.h +++ b/apps/statistics/multiple_data_view.h @@ -32,14 +32,14 @@ public: int numberOfSubviews() const override; protected: virtual Shared::BannerView * bannerView() = 0; - void layoutSubviews() override; - virtual void layoutDataSubviews(); + void layoutSubviews(bool force = false) override; + virtual void layoutDataSubviews(bool force); View * subviewAtIndex(int index) override; virtual void changeDataViewSelection(int index, bool select); KDRect bannerFrame() const; Store * m_store; private: - void layoutBanner(); + void layoutBanner(bool force); void drawRect(KDContext * ctx, KDRect rect) const override; bool m_displayBanner; }; diff --git a/apps/statistics/multiple_data_view_controller.cpp b/apps/statistics/multiple_data_view_controller.cpp index c3c9f7d5c..d21807be5 100644 --- a/apps/statistics/multiple_data_view_controller.cpp +++ b/apps/statistics/multiple_data_view_controller.cpp @@ -79,7 +79,8 @@ void MultipleDataViewController::didEnterResponderChain(Responder * firstRespond } void MultipleDataViewController::willExitResponderChain(Responder * nextFirstResponder) { - if (nextFirstResponder == nullptr || nextFirstResponder == tabController()) { + if (nextFirstResponder == tabController()) { + assert(tabController() != nullptr); assert(*m_selectedSeriesIndex >= 0); multipleDataView()->deselectDataView(*m_selectedSeriesIndex); multipleDataView()->setDisplayBanner(false); diff --git a/apps/statistics/multiple_histograms_view.cpp b/apps/statistics/multiple_histograms_view.cpp index b96c2ade1..d5c18899f 100644 --- a/apps/statistics/multiple_histograms_view.cpp +++ b/apps/statistics/multiple_histograms_view.cpp @@ -31,7 +31,7 @@ int MultipleHistogramsView::seriesOfSubviewAtIndex(int index) { return static_cast(subviewAtIndex(index))->series(); } -void MultipleHistogramsView::layoutSubviews() { +void MultipleHistogramsView::layoutSubviews(bool force) { MultipleDataView::layoutSubviews(); int numberHistogramSubviews = m_store->numberOfNonEmptySeries(); assert(numberHistogramSubviews > 0); diff --git a/apps/statistics/multiple_histograms_view.h b/apps/statistics/multiple_histograms_view.h index de41f0d3b..d5cdbb36b 100644 --- a/apps/statistics/multiple_histograms_view.h +++ b/apps/statistics/multiple_histograms_view.h @@ -19,7 +19,7 @@ public: HistogramBannerView * bannerView() override { return &m_bannerView; } HistogramView * dataViewAtIndex(int index) override; private: - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; void changeDataViewSelection(int index, bool select) override; HistogramView m_histogramView1; HistogramView m_histogramView2; diff --git a/apps/statistics/statistics_context.cpp b/apps/statistics/statistics_context.cpp index 744a06b4d..96a891d43 100644 --- a/apps/statistics/statistics_context.cpp +++ b/apps/statistics/statistics_context.cpp @@ -10,7 +10,7 @@ using namespace Shared; namespace Statistics { const Expression StatisticsContext::expressionForSymbolAbstract(const SymbolAbstract & symbol, bool clone) { - if (symbol.type() == ExpressionNode::Type::Symbol && Symbol::isSeriesSymbol(symbol.name())) { + if (symbol.type() == ExpressionNode::Type::Symbol && Symbol::isSeriesSymbol(symbol.name(), nullptr)) { const char * seriesName = symbol.name(); assert(strlen(seriesName) == 2); @@ -24,7 +24,7 @@ const Expression StatisticsContext::expressionForSymbolAbstract(const SymbolAbst assert(m_seriesPairIndex < m_store->numberOfPairsOfSeries(series)); return Float::Builder(m_store->get(series, storeI, m_seriesPairIndex)); } else { - return m_parentContext->expressionForSymbolAbstract(symbol, clone); + return ContextWithParent::expressionForSymbolAbstract(symbol, clone); } } diff --git a/apps/statistics/store_controller.cpp b/apps/statistics/store_controller.cpp index b5dfe39e2..84c263855 100644 --- a/apps/statistics/store_controller.cpp +++ b/apps/statistics/store_controller.cpp @@ -12,20 +12,15 @@ using namespace Shared; namespace Statistics { -StoreController::StoreController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, Store * store, ButtonRowController * header) : +StoreController::StoreController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, Store * store, ButtonRowController * header, Context * parentContext) : Shared::StoreController(parentResponder, inputEventHandlerDelegate, store, header), m_titleCells{}, m_store(store), - m_statisticsContext(m_store), + m_statisticsContext(m_store, parentContext), m_storeParameterController(this, store, this) { } -StoreContext * StoreController::storeContext() { - m_statisticsContext.setParentContext(AppsContainer::sharedAppsContainer()->globalContext()); - return &m_statisticsContext; -} - void StoreController::setFormulaLabel() { int series = selectedColumn() / Store::k_numberOfColumnsPerSeries; int isValueColumn = selectedColumn() % Store::k_numberOfColumnsPerSeries == 0; diff --git a/apps/statistics/store_controller.h b/apps/statistics/store_controller.h index c3e2350e6..e6d17a807 100644 --- a/apps/statistics/store_controller.h +++ b/apps/statistics/store_controller.h @@ -11,8 +11,8 @@ namespace Statistics { class StoreController : public Shared::StoreController { public: - StoreController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, Store * store, ButtonRowController * header); - Shared::StoreContext * storeContext() override; + StoreController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, Store * store, ButtonRowController * header, Poincare::Context * parentContext); + Shared::StoreContext * storeContext() override { return &m_statisticsContext; } void setFormulaLabel() override; bool fillColumnWithFormula(Poincare::Expression formula) override; void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; diff --git a/apps/title_bar_view.cpp b/apps/title_bar_view.cpp index 3df120c07..7b2437338 100644 --- a/apps/title_bar_view.cpp +++ b/apps/title_bar_view.cpp @@ -61,28 +61,36 @@ View * TitleBarView::subviewAtIndex(int index) { return &m_batteryView; } -void TitleBarView::layoutSubviews() { +void TitleBarView::layoutSubviews(bool force) { /* We here cheat to layout the main title. The application title is written * with upper cases. But, as upper letters are on the same baseline as lower * letters, they seem to be slightly above when they are perferctly centered * (because their glyph never cross the baseline). To avoid this effect, we * translate the frame of the title downwards.*/ - m_titleView.setFrame(KDRect(0, 2, bounds().width(), bounds().height()-2)); - m_preferenceView.setFrame(KDRect(Metric::TitleBarExternHorizontalMargin, 0, m_preferenceView.minimalSizeForOptimalDisplay().width(), bounds().height())); + m_titleView.setFrame(KDRect(0, 2, bounds().width(), bounds().height()-2), force); + m_preferenceView.setFrame(KDRect(Metric::TitleBarExternHorizontalMargin, 0, m_preferenceView.minimalSizeForOptimalDisplay().width(), bounds().height()), force); KDSize batterySize = m_batteryView.minimalSizeForOptimalDisplay(); - m_batteryView.setFrame(KDRect(bounds().width() - batterySize.width() - Metric::TitleBarExternHorizontalMargin, (bounds().height()- batterySize.height())/2, batterySize)); + m_batteryView.setFrame(KDRect(bounds().width() - batterySize.width() - Metric::TitleBarExternHorizontalMargin, (bounds().height()- batterySize.height())/2, batterySize), force); if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { +<<<<<<< HEAD m_examModeIconView.setFrame(KDRect(bounds().width() - batterySize.width() - k_examIconWidth - k_alphaRightMargin - Metric::TitleBarExternHorizontalMargin, (bounds().height() - k_examIconHeight)/2, k_examIconWidth, k_examIconHeight)); +======= + m_examModeIconView.setFrame(KDRect(k_examIconMargin, (bounds().height() - k_examIconHeight)/2, k_examIconWidth, k_examIconHeight), force); +>>>>>>> upstream/master } else { - m_examModeIconView.setFrame(KDRectZero); + m_examModeIconView.setFrame(KDRectZero, force); } KDSize shiftAlphaLockSize = m_shiftAlphaLockView.minimalSizeForOptimalDisplay(); +<<<<<<< HEAD if (GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) { // The Shift/Alpha frame is shifted when examination mode is active m_shiftAlphaLockView.setFrame(KDRect(bounds().width()-batterySize.width()-k_examIconWidth-Metric::TitleBarExternHorizontalMargin-2*k_alphaRightMargin-shiftAlphaLockSize.width(), (bounds().height()- shiftAlphaLockSize.height())/2, shiftAlphaLockSize)); } else { m_shiftAlphaLockView.setFrame(KDRect(bounds().width()-batterySize.width()-Metric::TitleBarExternHorizontalMargin-k_alphaRightMargin-shiftAlphaLockSize.width(), (bounds().height()- shiftAlphaLockSize.height())/2, shiftAlphaLockSize)); } +======= + m_shiftAlphaLockView.setFrame(KDRect(bounds().width()-batterySize.width()-Metric::TitleBarExternHorizontalMargin-k_alphaRightMargin-shiftAlphaLockSize.width(), (bounds().height()- shiftAlphaLockSize.height())/2, shiftAlphaLockSize), force); +>>>>>>> upstream/master } void TitleBarView::refreshPreferences() { diff --git a/apps/title_bar_view.h b/apps/title_bar_view.h index 646b34415..c0b6517f6 100644 --- a/apps/title_bar_view.h +++ b/apps/title_bar_view.h @@ -23,7 +23,7 @@ private: constexpr static KDCoordinate k_examIconHeight = 9; constexpr static KDCoordinate k_examIconMargin = 93; int numberOfSubviews() const override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; View * subviewAtIndex(int index) override; MessageTextView m_titleView; BatteryView m_batteryView; diff --git a/apps/toolbox.de.i18n b/apps/toolbox.de.i18n index 8704dd9ad..61999f5f7 100644 --- a/apps/toolbox.de.i18n +++ b/apps/toolbox.de.i18n @@ -1,3 +1,105 @@ +Unit = "Units" +UnitTimeMenu = "Time" +UnitTimeSecondMenu = "Second" +UnitTimeSecond = "Second" +UnitTimeSecondMilli = "Millisecond" +UnitTimeSecondMicro = "Microsecond" +UnitTimeSecondNano = "Nanosecond" +UnitTimeMinute = "Minute" +UnitTimeHour = "Hour" +UnitTimeDay = "Day" +UnitTimeWeek = "Week" +UnitTimeMonth = "Month" +UnitTimeYear = "Year" +UnitDistanceMenu = "Distance" +UnitDistanceMeterMenu = "Meter" +UnitDistanceMeterKilo = "Kilometer" +UnitDistanceMeter = "Meter" +UnitDistanceMeterMilli = "Millimeter" +UnitDistanceMeterMicro = "Micrometer" +UnitDistanceMeterNano = "Nanometer" +UnitDistanceMeterPico = "Picometer" +UnitDistanceAstronomicalUnit = "Astronomical unit" +UnitDistanceLightYear = "Light year" +UnitDistanceParsec = "Parsec" +UnitMassMenu = "Mass" +UnitMassGramKilo = "Kilogram" +UnitMassGram = "Gram" +UnitMassGramMilli = "Milligram" +UnitMassGramMicro = "Microgram" +UnitMassGramNano = "Nanogram" +UnitMassTonne = "Tonne" +UnitCurrentMenu = "Electric current" +UnitCurrentAmpere = "Ampere" +UnitCurrentAmpereMilli = "Milliampere" +UnitCurrentAmpereMicro = "Microampere" +UnitTemperatureMenu = "Temperature" +UnitTemperatureKelvin = "Kelvin" +UnitAmountMenu = "Amount of substance" +UnitAmountMole = "Mole" +UnitAmountMoleMilli = "Millimole" +UnitAmountMoleMicro = "Micromole" +UnitLuminousIntensityMenu = "Luminous intensity" +UnitLuminousIntensityCandela = "Candela" +UnitFrequencyMenu = "Frequency" +UnitFrequencyHertzGiga = "Gigahertz" +UnitFrequencyHertzMega = "Megahertz" +UnitFrequencyHertzKilo = "Kilohertz" +UnitFrequencyHertz = "Hertz" +UnitForceMenu = "Force" +UnitForceNewtonKilo = "Kilonewton" +UnitForceNewton = "Newton" +UnitForceNewtonMilli = "Millinewton" +UnitPressureMenu = "Pressure" +UnitPressurePascal = "Pascal" +UnitPressurePascalHecto = "Hectopascal" +UnitPressureBar = "Bar" +UnitPressureAtm = "Atmosphere" +UnitEnergyMenu = "Energy" +UnitEnergyJouleMenu = "Joule" +UnitEnergyJouleKilo = "Kilojoule" +UnitEnergyJoule = "Joule" +UnitEnergyJouleMilli = "Millijoule" +UnitEnergyEletronVoltMenu = "Electronvolt" +UnitEnergyElectronVoltMega = "Megaelectronvolt" +UnitEnergyElectronVoltKilo = "Kiloelectronvolt" +UnitEnergyElectronVolt = "Electronvolt" +UnitEnergyElectronVoltMilli = "Millielectronvolt" +UnitPowerMenu = "Power" +UnitPowerWattGiga = "Gigawatt" +UnitPowerWattMega = "Megawatt" +UnitPowerWattKilo = "Kilowatt" +UnitPowerWatt = "Watt" +UnitPowerWattMilli = "Milliwatt" +UnitPowerWattMicro = "Microwatt" +UnitElectricChargeMenu = "Electric charge" +UnitChargeCoulomb = "Coulomb" +UnitPotentialMenu = "Electric potential" +UnitPotentialVoltKilo = "Kilovolt" +UnitPotentialVolt = "Volt" +UnitPotentialVoltMilli = "Millivolt" +UnitPotentialVoltMicro = "Microvolt" +UnitCapacitanceMenu = "Electrical capacitance" +UnitCapacitanceFarad = "Farad" +UnitCapacitanceFaradMilli = "Millifarad" +UnitCapacitanceFaradMicro = "Microfarad" +UnitResistanceMenu = "Electrical resistance" +UnitResistanceOhmKilo = "Kiloohm" +UnitResistanceOhm = "Ohm" +UnitConductanceMenu = "Electrical conductance" +UnitConductanceSiemens = "Siemens" +UnitConductanceSiemensMilli = "Millisiemens" +UnitMagneticFieldMenu = "Magnetic field" +UnitMagneticFieldTesla = "Tesla" +InductanceMenu = "Electrical inductance" +UnitInductanceHenry = "Henry" +UnitSurfaceMenu = "Area" +UnitSurfaceHectar = "Hectare" +UnitVolumeMenu = "Volume" +UnitVolumeLiter = "Liter" +UnitVolumeLiterDeci = "Deciliter" +UnitVolumeLiterCenti = "Centiliter" +UnitVolumeLiterMilli = "Milliliter" Toolbox = "Werkzeugkasten" AbsoluteValue = "Betragsfunktion" NthRoot = "n-te Wurzel" diff --git a/apps/toolbox.en.i18n b/apps/toolbox.en.i18n index 00c0f8ac4..eec7d5bfd 100644 --- a/apps/toolbox.en.i18n +++ b/apps/toolbox.en.i18n @@ -1,3 +1,105 @@ +Unit = "Units" +UnitTimeMenu = "Time" +UnitTimeSecondMenu = "Second" +UnitTimeSecond = "Second" +UnitTimeSecondMilli = "Millisecond" +UnitTimeSecondMicro = "Microsecond" +UnitTimeSecondNano = "Nanosecond" +UnitTimeMinute = "Minute" +UnitTimeHour = "Hour" +UnitTimeDay = "Day" +UnitTimeWeek = "Week" +UnitTimeMonth = "Month" +UnitTimeYear = "Year" +UnitDistanceMenu = "Distance" +UnitDistanceMeterMenu = "Meter" +UnitDistanceMeterKilo = "Kilometer" +UnitDistanceMeter = "Meter" +UnitDistanceMeterMilli = "Millimeter" +UnitDistanceMeterMicro = "Micrometer" +UnitDistanceMeterNano = "Nanometer" +UnitDistanceMeterPico = "Picometer" +UnitDistanceAstronomicalUnit = "Astronomical unit" +UnitDistanceLightYear = "Light year" +UnitDistanceParsec = "Parsec" +UnitMassMenu = "Mass" +UnitMassGramKilo = "Kilogram" +UnitMassGram = "Gram" +UnitMassGramMilli = "Milligram" +UnitMassGramMicro = "Microgram" +UnitMassGramNano = "Nanogram" +UnitMassTonne = "Tonne" +UnitCurrentMenu = "Electric current" +UnitCurrentAmpere = "Ampere" +UnitCurrentAmpereMilli = "Milliampere" +UnitCurrentAmpereMicro = "Microampere" +UnitTemperatureMenu = "Temperature" +UnitTemperatureKelvin = "Kelvin" +UnitAmountMenu = "Amount of substance" +UnitAmountMole = "Mole" +UnitAmountMoleMilli = "Millimole" +UnitAmountMoleMicro = "Micromole" +UnitLuminousIntensityMenu = "Luminous intensity" +UnitLuminousIntensityCandela = "Candela" +UnitFrequencyMenu = "Frequency" +UnitFrequencyHertzGiga = "Gigahertz" +UnitFrequencyHertzMega = "Megahertz" +UnitFrequencyHertzKilo = "Kilohertz" +UnitFrequencyHertz = "Hertz" +UnitForceMenu = "Force" +UnitForceNewtonKilo = "Kilonewton" +UnitForceNewton = "Newton" +UnitForceNewtonMilli = "Millinewton" +UnitPressureMenu = "Pressure" +UnitPressurePascal = "Pascal" +UnitPressurePascalHecto = "Hectopascal" +UnitPressureBar = "Bar" +UnitPressureAtm = "Atmosphere" +UnitEnergyMenu = "Energy" +UnitEnergyJouleMenu = "Joule" +UnitEnergyJouleKilo = "Kilojoule" +UnitEnergyJoule = "Joule" +UnitEnergyJouleMilli = "Millijoule" +UnitEnergyEletronVoltMenu = "Electronvolt" +UnitEnergyElectronVoltMega = "Megaelectronvolt" +UnitEnergyElectronVoltKilo = "Kiloelectronvolt" +UnitEnergyElectronVolt = "Electronvolt" +UnitEnergyElectronVoltMilli = "Millielectronvolt" +UnitPowerMenu = "Power" +UnitPowerWattGiga = "Gigawatt" +UnitPowerWattMega = "Megawatt" +UnitPowerWattKilo = "Kilowatt" +UnitPowerWatt = "Watt" +UnitPowerWattMilli = "Milliwatt" +UnitPowerWattMicro = "Microwatt" +UnitElectricChargeMenu = "Electric charge" +UnitChargeCoulomb = "Coulomb" +UnitPotentialMenu = "Electric potential" +UnitPotentialVoltKilo = "Kilovolt" +UnitPotentialVolt = "Volt" +UnitPotentialVoltMilli = "Millivolt" +UnitPotentialVoltMicro = "Microvolt" +UnitCapacitanceMenu = "Electrical capacitance" +UnitCapacitanceFarad = "Farad" +UnitCapacitanceFaradMilli = "Millifarad" +UnitCapacitanceFaradMicro = "Microfarad" +UnitResistanceMenu = "Electrical resistance" +UnitResistanceOhmKilo = "Kiloohm" +UnitResistanceOhm = "Ohm" +UnitConductanceMenu = "Electrical conductance" +UnitConductanceSiemens = "Siemens" +UnitConductanceSiemensMilli = "Millisiemens" +UnitMagneticFieldMenu = "Magnetic field" +UnitMagneticFieldTesla = "Tesla" +InductanceMenu = "Electrical inductance" +UnitInductanceHenry = "Henry" +UnitSurfaceMenu = "Area" +UnitSurfaceHectar = "Hectare" +UnitVolumeMenu = "Volume" +UnitVolumeLiter = "Liter" +UnitVolumeLiterDeci = "Deciliter" +UnitVolumeLiterCenti = "Centiliter" +UnitVolumeLiterMilli = "Milliliter" Toolbox = "Toolbox" AbsoluteValue = "Absolute value" NthRoot = "nth-root" diff --git a/apps/toolbox.es.i18n b/apps/toolbox.es.i18n index 0ba807a5f..52a63922a 100644 --- a/apps/toolbox.es.i18n +++ b/apps/toolbox.es.i18n @@ -1,3 +1,105 @@ +Unit = "Units" +UnitTimeMenu = "Time" +UnitTimeSecondMenu = "Second" +UnitTimeSecond = "Second" +UnitTimeSecondMilli = "Millisecond" +UnitTimeSecondMicro = "Microsecond" +UnitTimeSecondNano = "Nanosecond" +UnitTimeMinute = "Minute" +UnitTimeHour = "Hour" +UnitTimeDay = "Day" +UnitTimeWeek = "Week" +UnitTimeMonth = "Month" +UnitTimeYear = "Year" +UnitDistanceMenu = "Distance" +UnitDistanceMeterMenu = "Meter" +UnitDistanceMeterKilo = "Kilometer" +UnitDistanceMeter = "Meter" +UnitDistanceMeterMilli = "Millimeter" +UnitDistanceMeterMicro = "Micrometer" +UnitDistanceMeterNano = "Nanometer" +UnitDistanceMeterPico = "Picometer" +UnitDistanceAstronomicalUnit = "Astronomical unit" +UnitDistanceLightYear = "Light year" +UnitDistanceParsec = "Parsec" +UnitMassMenu = "Mass" +UnitMassGramKilo = "Kilogram" +UnitMassGram = "Gram" +UnitMassGramMilli = "Milligram" +UnitMassGramMicro = "Microgram" +UnitMassGramNano = "Nanogram" +UnitMassTonne = "Tonne" +UnitCurrentMenu = "Electric current" +UnitCurrentAmpere = "Ampere" +UnitCurrentAmpereMilli = "Milliampere" +UnitCurrentAmpereMicro = "Microampere" +UnitTemperatureMenu = "Temperature" +UnitTemperatureKelvin = "Kelvin" +UnitAmountMenu = "Amount of substance" +UnitAmountMole = "Mole" +UnitAmountMoleMilli = "Millimole" +UnitAmountMoleMicro = "Micromole" +UnitLuminousIntensityMenu = "Luminous intensity" +UnitLuminousIntensityCandela = "Candela" +UnitFrequencyMenu = "Frequency" +UnitFrequencyHertzGiga = "Gigahertz" +UnitFrequencyHertzMega = "Megahertz" +UnitFrequencyHertzKilo = "Kilohertz" +UnitFrequencyHertz = "Hertz" +UnitForceMenu = "Force" +UnitForceNewtonKilo = "Kilonewton" +UnitForceNewton = "Newton" +UnitForceNewtonMilli = "Millinewton" +UnitPressureMenu = "Pressure" +UnitPressurePascal = "Pascal" +UnitPressurePascalHecto = "Hectopascal" +UnitPressureBar = "Bar" +UnitPressureAtm = "Atmosphere" +UnitEnergyMenu = "Energy" +UnitEnergyJouleMenu = "Joule" +UnitEnergyJouleKilo = "Kilojoule" +UnitEnergyJoule = "Joule" +UnitEnergyJouleMilli = "Millijoule" +UnitEnergyEletronVoltMenu = "Electronvolt" +UnitEnergyElectronVoltMega = "Megaelectronvolt" +UnitEnergyElectronVoltKilo = "Kiloelectronvolt" +UnitEnergyElectronVolt = "Electronvolt" +UnitEnergyElectronVoltMilli = "Millielectronvolt" +UnitPowerMenu = "Power" +UnitPowerWattGiga = "Gigawatt" +UnitPowerWattMega = "Megawatt" +UnitPowerWattKilo = "Kilowatt" +UnitPowerWatt = "Watt" +UnitPowerWattMilli = "Milliwatt" +UnitPowerWattMicro = "Microwatt" +UnitElectricChargeMenu = "Electric charge" +UnitChargeCoulomb = "Coulomb" +UnitPotentialMenu = "Electric potential" +UnitPotentialVoltKilo = "Kilovolt" +UnitPotentialVolt = "Volt" +UnitPotentialVoltMilli = "Millivolt" +UnitPotentialVoltMicro = "Microvolt" +UnitCapacitanceMenu = "Electrical capacitance" +UnitCapacitanceFarad = "Farad" +UnitCapacitanceFaradMilli = "Millifarad" +UnitCapacitanceFaradMicro = "Microfarad" +UnitResistanceMenu = "Electrical resistance" +UnitResistanceOhmKilo = "Kiloohm" +UnitResistanceOhm = "Ohm" +UnitConductanceMenu = "Electrical conductance" +UnitConductanceSiemens = "Siemens" +UnitConductanceSiemensMilli = "Millisiemens" +UnitMagneticFieldMenu = "Magnetic field" +UnitMagneticFieldTesla = "Tesla" +InductanceMenu = "Electrical inductance" +UnitInductanceHenry = "Henry" +UnitSurfaceMenu = "Area" +UnitSurfaceHectar = "Hectare" +UnitVolumeMenu = "Volume" +UnitVolumeLiter = "Liter" +UnitVolumeLiterDeci = "Deciliter" +UnitVolumeLiterCenti = "Centiliter" +UnitVolumeLiterMilli = "Milliliter" Toolbox = "Caja de herramientas" AbsoluteValue = "Valor absoluto" NthRoot = "Raíz enesima" diff --git a/apps/toolbox.fr.i18n b/apps/toolbox.fr.i18n index 113bd4a10..bb9978e74 100644 --- a/apps/toolbox.fr.i18n +++ b/apps/toolbox.fr.i18n @@ -1,3 +1,105 @@ +Unit = "Unités" +UnitTimeMenu = "Temps" +UnitTimeSecondMenu = "Seconde" +UnitTimeSecond = "Seconde" +UnitTimeSecondMilli = "Milliseconde" +UnitTimeSecondMicro = "Microseconde" +UnitTimeSecondNano = "Nanoseconde" +UnitTimeMinute = "Minute" +UnitTimeHour = "Heure" +UnitTimeDay = "Jour" +UnitTimeWeek = "Semaine" +UnitTimeMonth = "Mois" +UnitTimeYear = "Année" +UnitDistanceMenu = "Distance" +UnitDistanceMeterMenu = "Mètre" +UnitDistanceMeterKilo = "Kilomètre" +UnitDistanceMeter = "Mètre" +UnitDistanceMeterMilli = "Millimètre" +UnitDistanceMeterMicro = "Micromètre" +UnitDistanceMeterNano = "Nanomètre" +UnitDistanceMeterPico = "Picomètre" +UnitDistanceAstronomicalUnit = "Unité astronomique" +UnitDistanceLightYear = "Année lumière" +UnitDistanceParsec = "Parsec" +UnitMassMenu = "Masse" +UnitMassGramKilo = "Kilogramme" +UnitMassGram = "Gramme" +UnitMassGramMilli = "Milligramme" +UnitMassGramMicro = "Microgramme" +UnitMassGramNano = "Nanogramme" +UnitMassTonne = "Tonne" +UnitCurrentMenu = "Intensité du courant électrique" +UnitCurrentAmpere = "Ampère" +UnitCurrentAmpereMilli = "Milliampère" +UnitCurrentAmpereMicro = "Microampère" +UnitTemperatureMenu = "Température" +UnitTemperatureKelvin = "Kelvin" +UnitAmountMenu = "Quantité de matière" +UnitAmountMole = "Mole" +UnitAmountMoleMilli = "Millimole" +UnitAmountMoleMicro = "Micromole" +UnitLuminousIntensityMenu = "Intensité lumineuse" +UnitLuminousIntensityCandela = "Candela" +UnitFrequencyMenu = "Fréquence" +UnitFrequencyHertzGiga = "Gigahertz" +UnitFrequencyHertzMega = "Megahertz" +UnitFrequencyHertzKilo = "Kilohertz" +UnitFrequencyHertz = "Hertz" +UnitForceMenu = "Force" +UnitForceNewtonKilo = "Kilonewton" +UnitForceNewton = "Newton" +UnitForceNewtonMilli = "Millinewton" +UnitPressureMenu = "Pression" +UnitPressurePascal = "Pascal" +UnitPressurePascalHecto = "Hectopascal" +UnitPressureBar = "Bar" +UnitPressureAtm = "Atmosphère" +UnitEnergyMenu = "Énergie" +UnitEnergyJouleMenu = "Joule" +UnitEnergyJouleKilo = "Kilojoule" +UnitEnergyJoule = "Joule" +UnitEnergyJouleMilli = "Millijoule" +UnitEnergyEletronVoltMenu = "Electronvolt" +UnitEnergyElectronVoltMega = "Megaelectronvolt" +UnitEnergyElectronVoltKilo = "Kiloelectronvolt" +UnitEnergyElectronVolt = "Electronvolt" +UnitEnergyElectronVoltMilli = "Millielectronvolt" +UnitPowerMenu = "Puissance" +UnitPowerWattGiga = "Gigawatt" +UnitPowerWattMega = "Megawatt" +UnitPowerWattKilo = "Kilowatt" +UnitPowerWatt = "Watt" +UnitPowerWattMilli = "Milliwatt" +UnitPowerWattMicro = "Microwatt" +UnitElectricChargeMenu = "Charge électrique" +UnitChargeCoulomb = "Coulomb" +UnitPotentialMenu = "Tension électrique" +UnitPotentialVoltKilo = "Kilovolt" +UnitPotentialVolt = "Volt" +UnitPotentialVoltMilli = "Millivolt" +UnitPotentialVoltMicro = "Microvolt" +UnitCapacitanceMenu = "Capacité électrique" +UnitCapacitanceFarad = "Farad" +UnitCapacitanceFaradMilli = "Millifarad" +UnitCapacitanceFaradMicro = "Microfarad" +UnitResistanceMenu = "Résistance électrique" +UnitResistanceOhmKilo = "Kiloohm" +UnitResistanceOhm = "Ohm" +UnitConductanceMenu = "Conductance électrique" +UnitConductanceSiemens = "Siemens" +UnitConductanceSiemensMilli = "Millisiemens" +UnitMagneticFieldMenu = "Induction électromagnétique" +UnitMagneticFieldTesla = "Tesla" +InductanceMenu = "Inductance" +UnitInductanceHenry = "Henry" +UnitSurfaceMenu = "Superficie" +UnitSurfaceHectar = "Hectare" +UnitVolumeMenu = "Volume" +UnitVolumeLiter = "Litre" +UnitVolumeLiterDeci = "Decilitre" +UnitVolumeLiterCenti = "Centilitre" +UnitVolumeLiterMilli = "Millilitre" Toolbox = "Boîte à outils" AbsoluteValue = "Valeur absolue" NthRoot = "Racine n-ième" diff --git a/apps/toolbox.pt.i18n b/apps/toolbox.pt.i18n index c36ebcc06..187e789f6 100644 --- a/apps/toolbox.pt.i18n +++ b/apps/toolbox.pt.i18n @@ -1,3 +1,105 @@ +Unit = "Units" +UnitTimeMenu = "Time" +UnitTimeSecondMenu = "Second" +UnitTimeSecond = "Second" +UnitTimeSecondMilli = "Millisecond" +UnitTimeSecondMicro = "Microsecond" +UnitTimeSecondNano = "Nanosecond" +UnitTimeMinute = "Minute" +UnitTimeHour = "Hour" +UnitTimeDay = "Day" +UnitTimeWeek = "Week" +UnitTimeMonth = "Month" +UnitTimeYear = "Year" +UnitDistanceMenu = "Distance" +UnitDistanceMeterMenu = "Meter" +UnitDistanceMeterKilo = "Kilometer" +UnitDistanceMeter = "Meter" +UnitDistanceMeterMilli = "Millimeter" +UnitDistanceMeterMicro = "Micrometer" +UnitDistanceMeterNano = "Nanometer" +UnitDistanceMeterPico = "Picometer" +UnitDistanceAstronomicalUnit = "Astronomical unit" +UnitDistanceLightYear = "Light year" +UnitDistanceParsec = "Parsec" +UnitMassMenu = "Mass" +UnitMassGramKilo = "Kilogram" +UnitMassGram = "Gram" +UnitMassGramMilli = "Milligram" +UnitMassGramMicro = "Microgram" +UnitMassGramNano = "Nanogram" +UnitMassTonne = "Tonne" +UnitCurrentMenu = "Electric current" +UnitCurrentAmpere = "Ampere" +UnitCurrentAmpereMilli = "Milliampere" +UnitCurrentAmpereMicro = "Microampere" +UnitTemperatureMenu = "Temperature" +UnitTemperatureKelvin = "Kelvin" +UnitAmountMenu = "Amount of substance" +UnitAmountMole = "Mole" +UnitAmountMoleMilli = "Millimole" +UnitAmountMoleMicro = "Micromole" +UnitLuminousIntensityMenu = "Luminous intensity" +UnitLuminousIntensityCandela = "Candela" +UnitFrequencyMenu = "Frequency" +UnitFrequencyHertzGiga = "Gigahertz" +UnitFrequencyHertzMega = "Megahertz" +UnitFrequencyHertzKilo = "Kilohertz" +UnitFrequencyHertz = "Hertz" +UnitForceMenu = "Force" +UnitForceNewtonKilo = "Kilonewton" +UnitForceNewton = "Newton" +UnitForceNewtonMilli = "Millinewton" +UnitPressureMenu = "Pressure" +UnitPressurePascal = "Pascal" +UnitPressurePascalHecto = "Hectopascal" +UnitPressureBar = "Bar" +UnitPressureAtm = "Atmosphere" +UnitEnergyMenu = "Energy" +UnitEnergyJouleMenu = "Joule" +UnitEnergyJouleKilo = "Kilojoule" +UnitEnergyJoule = "Joule" +UnitEnergyJouleMilli = "Millijoule" +UnitEnergyEletronVoltMenu = "Electronvolt" +UnitEnergyElectronVoltMega = "Megaelectronvolt" +UnitEnergyElectronVoltKilo = "Kiloelectronvolt" +UnitEnergyElectronVolt = "Electronvolt" +UnitEnergyElectronVoltMilli = "Millielectronvolt" +UnitPowerMenu = "Power" +UnitPowerWattGiga = "Gigawatt" +UnitPowerWattMega = "Megawatt" +UnitPowerWattKilo = "Kilowatt" +UnitPowerWatt = "Watt" +UnitPowerWattMilli = "Milliwatt" +UnitPowerWattMicro = "Microwatt" +UnitElectricChargeMenu = "Electric charge" +UnitChargeCoulomb = "Coulomb" +UnitPotentialMenu = "Electric potential" +UnitPotentialVoltKilo = "Kilovolt" +UnitPotentialVolt = "Volt" +UnitPotentialVoltMilli = "Millivolt" +UnitPotentialVoltMicro = "Microvolt" +UnitCapacitanceMenu = "Electrical capacitance" +UnitCapacitanceFarad = "Farad" +UnitCapacitanceFaradMilli = "Millifarad" +UnitCapacitanceFaradMicro = "Microfarad" +UnitResistanceMenu = "Electrical resistance" +UnitResistanceOhmKilo = "Kiloohm" +UnitResistanceOhm = "Ohm" +UnitConductanceMenu = "Electrical conductance" +UnitConductanceSiemens = "Siemens" +UnitConductanceSiemensMilli = "Millisiemens" +UnitMagneticFieldMenu = "Magnetic field" +UnitMagneticFieldTesla = "Tesla" +InductanceMenu = "Electrical inductance" +UnitInductanceHenry = "Henry" +UnitSurfaceMenu = "Area" +UnitSurfaceHectar = "Hectare" +UnitVolumeMenu = "Volume" +UnitVolumeLiter = "Liter" +UnitVolumeLiterDeci = "Deciliter" +UnitVolumeLiterCenti = "Centiliter" +UnitVolumeLiterMilli = "Milliliter" Toolbox = "Caixa de ferramentas" AbsoluteValue = "Valor absoluto" NthRoot = "Radiciacao" diff --git a/apps/variable_box_controller.cpp b/apps/variable_box_controller.cpp index ea54020a7..7d5943674 100644 --- a/apps/variable_box_controller.cpp +++ b/apps/variable_box_controller.cpp @@ -21,6 +21,9 @@ VariableBoxController::VariableBoxController() : m_lockPageDelete(Page::RootMenu), m_firstMemoizedLayoutIndex(0) { + for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) { + m_leafCells[i].setParentResponder(&m_selectableTableView); + } } void VariableBoxController::viewWillAppear() { @@ -117,6 +120,7 @@ void VariableBoxController::willDisplayCellForIndex(HighlightCell * cell, int in Layout symbolLayout = LayoutHelper::String(symbolName, symbolLength); myCell->setLayout(symbolLayout); myCell->setAccessoryLayout(expressionLayoutForRecord(record, index)); + myCell->reloadScroll(); myCell->reloadCell(); } diff --git a/apps/variable_box_empty_controller.cpp b/apps/variable_box_empty_controller.cpp index 0f4c22aba..758865a88 100644 --- a/apps/variable_box_empty_controller.cpp +++ b/apps/variable_box_empty_controller.cpp @@ -28,6 +28,10 @@ void VariableBoxEmptyController::VariableBoxEmptyView::setLayout(Poincare::Layou m_layoutExample.setLayout(layout); } +void VariableBoxEmptyController::VariableBoxEmptyView::drawRect(KDContext * ctx, KDRect rect) const { + drawBorderOfRect(ctx, bounds(), Palette::GreyBright); +} + int VariableBoxEmptyController::VariableBoxEmptyView::numberOfSubviews() const { return k_numberOfMessages+1; } @@ -42,20 +46,20 @@ View * VariableBoxEmptyController::VariableBoxEmptyView::subviewAtIndex(int inde return &m_messages[index-1]; } -void VariableBoxEmptyController::VariableBoxEmptyView::layoutSubviews() { - KDCoordinate width = bounds().width(); - KDCoordinate height = bounds().height(); +void VariableBoxEmptyController::VariableBoxEmptyView::layoutSubviews(bool force) { + KDCoordinate width = bounds().width() - 2*k_separatorThickness; + KDCoordinate height = bounds().height() - 2*k_separatorThickness; KDCoordinate textHeight = k_font->glyphSize().height(); KDCoordinate layoutHeight = m_layoutExample.minimalSizeForOptimalDisplay().height(); KDCoordinate margin = (height - k_numberOfMessages*textHeight-layoutHeight)/2; - m_layoutExample.setFrame(KDRect(0, margin+k_layoutRowIndex*textHeight, width, layoutHeight)); - KDCoordinate currentHeight = 0; + m_layoutExample.setFrame(KDRect(k_separatorThickness, k_separatorThickness+margin+k_layoutRowIndex*textHeight, width, layoutHeight), force); + KDCoordinate currentHeight = k_separatorThickness; for (uint8_t i = 0; i < k_numberOfMessages; i++) { if (i == k_layoutRowIndex) { currentHeight += layoutHeight; } KDCoordinate h = i == 0 || i == k_numberOfMessages - 1 ? textHeight+margin : textHeight; - m_messages[i].setFrame(KDRect(0, currentHeight, width, h)); + m_messages[i].setFrame(KDRect(k_separatorThickness, currentHeight, width, h), force); currentHeight += h; } } diff --git a/apps/variable_box_empty_controller.h b/apps/variable_box_empty_controller.h index b799c6ca9..683c63a9b 100644 --- a/apps/variable_box_empty_controller.h +++ b/apps/variable_box_empty_controller.h @@ -1,7 +1,11 @@ #ifndef APPS_VARIABLE_BOX_EMPTY_CONTROLLER_H #define APPS_VARIABLE_BOX_EMPTY_CONTROLLER_H -#include +#include +#include +#include +#include +#include class VariableBoxEmptyController : public ViewController { public: @@ -18,17 +22,18 @@ public: void setType(Type type); private: - class VariableBoxEmptyView : public View { + 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() override; + void layoutSubviews(bool force = false) override; constexpr static int k_layoutRowIndex = 2; MessageTextView m_messages[k_numberOfMessages]; ExpressionView m_layoutExample; diff --git a/apps/variables.es.i18n b/apps/variables.es.i18n index b0e00b9ba..86124ebe3 100644 --- a/apps/variables.es.i18n +++ b/apps/variables.es.i18n @@ -5,6 +5,6 @@ EmptyExpressionBox0 = "Ninguna variable definida." EmptyFunctionBox0 = "Ninguna función definida." EmptyExpressionBox1 = "Para definir una, teclear :" EmptyFunctionBox1 = "Para definir una, teclear :" -EmptyExpressionBox2 = "El nombre de variable debe contener :" -EmptyFunctionBox2 = "El nombre de función debe contener : " -EnableCharacters = "A..Z, a..z, 0..9 y _" +EmptyExpressionBox2 = "El nombre de variable debe" +EmptyFunctionBox2 = "El nombre de función debe" +EnableCharacters = "contener : A..Z, a..z, 0..9 y _" diff --git a/apps/variables.fr.i18n b/apps/variables.fr.i18n index a94262cb5..da626f514 100644 --- a/apps/variables.fr.i18n +++ b/apps/variables.fr.i18n @@ -5,6 +5,6 @@ EmptyExpressionBox0 = "Vous n'avez défini aucune variable." EmptyFunctionBox0 = "Vous n'avez défini aucune fonction." EmptyExpressionBox1 = "Pour définir une variable, tapez :" EmptyFunctionBox1 = "Pour définir une fonction, tapez :" -EmptyExpressionBox2 = "Le nom de la variable peut contenir :" -EmptyFunctionBox2 = "Le nom de la fonction peut contenir :" -EnableCharacters = "A..Z, a..z, 0..9 et _" +EmptyExpressionBox2 = "Le nom de la variable peut" +EmptyFunctionBox2 = "Le nom de la fonction peut" +EnableCharacters = "contenir : A..Z, a..z, 0..9 et _" diff --git a/build/config.mak b/build/config.mak index a8f499fbc..d95ff1d9f 100644 --- a/build/config.mak +++ b/build/config.mak @@ -3,7 +3,7 @@ PLATFORM ?= device DEBUG ?= 0 -EPSILON_VERSION ?= 12.0.0 +EPSILON_VERSION ?= 13.0.0 EPSILON_CUSTOM_VERSION ?= 1.19.0-0 # USERNAME ?= N/A # Valid values are "none", "update", "beta" diff --git a/build/defaults.mak b/build/defaults.mak index e4f889b20..d8a0c8c0d 100644 --- a/build/defaults.mak +++ b/build/defaults.mak @@ -42,6 +42,10 @@ ifeq ("$(origin V)", "command line") endif endif +ifeq ("$(PLATFORM)", "device") + SFLAGS += -DPLATFORM_DEVICE +endif + # Host detection ifeq ($(OS),Windows_NT) HOST = windows diff --git a/build/rules.mk b/build/rules.mk index 2092a831e..242dc13ea 100644 --- a/build/rules.mk +++ b/build/rules.mk @@ -1,5 +1,9 @@ # Define standard compilation rules +.PHONY: official_authorization +official_authorization: + @echo "CAUTION: You are about to build an official NumWorks firmware. Distribution of such firmware by a third party is prohibited. Are you sure you want to proceed? Please type "yes" to confirm." && read ans && [ $${ans:-no} = yes ] + $(eval $(call rule_for, \ AS, %.o, %.s, \ $$(CC) $$(SFLAGS) -c $$< -o $$@ \ diff --git a/build/targets.mak b/build/targets.mak index b821b4448..68ce3de04 100644 --- a/build/targets.mak +++ b/build/targets.mak @@ -2,14 +2,22 @@ base_src = $(liba_src) $(kandinsky_src) $(escher_src) $(libaxx_src) $(poincare_src) $(python_src) epsilon_src = $(base_src) $(ion_default_src) $(apps_default_src) +epsilon_official_src = $(base_src) $(ion_default_src) $(apps_official_default_src) epsilon_onboarding_src = $(base_src) $(ion_default_src) $(apps_onboarding_src) +epsilon_official_onboarding_src = $(base_src) $(ion_default_src) $(apps_official_onboarding_src) epsilon_onboarding_update_src = $(base_src) $(ion_default_src) $(apps_onboarding_update_src) +epsilon_official_onboarding_update_src = $(base_src) $(ion_default_src) $(apps_official_onboarding_update_src) epsilon_onboarding_beta_src = $(base_src) $(ion_default_src) $(apps_onboarding_beta_src) +epsilon_official_onboarding_beta_src = $(base_src) $(ion_default_src) $(apps_official_onboarding_beta_src) $(BUILD_DIR)/epsilon.$(EXE): $(call object_for,$(epsilon_src)) +$(BUILD_DIR)/epsilon.official.$(EXE): $(call object_for,$(epsilon_official_src)) $(BUILD_DIR)/epsilon.onboarding.$(EXE): $(call object_for,$(epsilon_onboarding_src)) +$(BUILD_DIR)/epsilon.official.onboarding.$(EXE): $(call object_for,$(epsilon_official_onboarding_src)) $(BUILD_DIR)/epsilon.onboarding.update.$(EXE): $(call object_for,$(epsilon_onboarding_update_src)) +$(BUILD_DIR)/epsilon.official.onboarding.update.$(EXE): $(call object_for,$(epsilon_official_onboarding_update_src)) $(BUILD_DIR)/epsilon.onboarding.beta.$(EXE): $(call object_for,$(epsilon_onboarding_beta_src)) +$(BUILD_DIR)/epsilon.official.onboarding.beta.$(EXE): $(call object_for,$(epsilon_official_onboarding_beta_src)) test_base_src = $(base_src) $(apps_tests_src) $(runner_src) $(tests_src) @@ -20,8 +28,8 @@ $(BUILD_DIR)/test.$(EXE): $(call object_for,$(test_runner_src)) # Those can be built easily by simply invoking "make target.ext". The named file # will be built in $(BUILD_DIR). -HANDY_TARGETS = epsilon epsilon.onboarding epsilon.onboarding.update epsilon.onboarding.beta test -HANDY_TARGETS_EXTENSIONS = $(EXE) +HANDY_TARGETS += epsilon epsilon.official epsilon.onboarding epsilon.official.onboarding epsilon.onboarding.update epsilon.official.onboarding.update epsilon.onboarding.beta epsilon.official.onboarding.beta test +HANDY_TARGETS_EXTENSIONS += $(EXE) define handy_target_rule .PHONY: $(1).$(2) diff --git a/build/toolchain.emscripten.mak b/build/toolchain.emscripten.mak index b582851bb..f8ec0beaa 100644 --- a/build/toolchain.emscripten.mak +++ b/build/toolchain.emscripten.mak @@ -5,6 +5,7 @@ LD = emcc EMSCRIPTEN_ASYNC_SYMBOLS = \ SAFE_HEAP_LOAD \ SAFE_HEAP_STORE \ +_IonDisplayForceRefresh\ _IonEventsEmscriptenKeyDown \ _IonEventsEmscriptenKeyUp \ _IonEventsEmscriptenPushEvent \ @@ -82,6 +83,8 @@ _main \ _micropython_port_interruptible_msleep \ _micropython_port_interrupt_if_needed \ _micropython_port_vm_hook_loop \ +_modion_keyboard_keydown \ +_modtime_sleep \ _modturtle_backward \ _modturtle_circle \ _modturtle_forward \ @@ -117,6 +120,15 @@ endif # Configure EMFLAGS EMFLAGS += -s WASM=0 +# Since emcc 1.39.5, DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR is defautly +# to 1 which discards old looks up to find DOM elements. However SDL relies on +# the presence of a target "#canvas" that used to return the Module['canvas'] +# target but not anymore. It also expects the existence of Module['canvas']. +# Until we fix the DOM of the html calling epsilon module, we use the deprecated +# 'find_event_target'. +# TODO: fix DOM of htmls files that uses epsilon js module +EMFLAGS += -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=0 + # Configure LDFLAGS EMSCRIPTEN_MODULARIZE ?= 1 LDFLAGS += -s MODULARIZE=$(EMSCRIPTEN_MODULARIZE) -s 'EXPORT_NAME="Epsilon"' @@ -124,4 +136,4 @@ EMSCRIPTEN_INIT_FILE ?= 0 LDFLAGS += --memory-init-file $(EMSCRIPTEN_INIT_FILE) SFLAGS += $(EMFLAGS) -LDFLAGS += $(EMFLAGS) -Oz -s EXPORTED_FUNCTIONS='["_main", "_IonSimulatorKeyboardKeyDown", "_IonSimulatorKeyboardKeyUp", "_IonSimulatorEventsPushEvent", "_IonSoftwareVersion", "_IonPatchLevel"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["UTF8ToString", "Pointer_stringify"]' +LDFLAGS += $(EMFLAGS) -Oz -s EXPORTED_FUNCTIONS='["_main", "_IonSimulatorKeyboardKeyDown", "_IonSimulatorKeyboardKeyUp", "_IonSimulatorEventsPushEvent", "_IonSoftwareVersion", "_IonPatchLevel", "_IonDisplayForceRefresh"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["UTF8ToString"]' diff --git a/escher/Makefile b/escher/Makefile index 6263321ba..0f095c9be 100644 --- a/escher/Makefile +++ b/escher/Makefile @@ -4,6 +4,7 @@ escher_src += $(addprefix escher/src/,\ alternate_empty_view_controller.cpp \ app.cpp \ bank_view_controller.cpp \ + bordered.cpp \ buffer_text_view.cpp \ button.cpp \ button_row_controller.cpp \ @@ -44,7 +45,6 @@ escher_src += $(addprefix escher/src/,\ message_table_cell_with_message.cpp \ message_table_cell_with_switch.cpp \ message_text_view.cpp \ - message_tree.cpp \ modal_view_controller.cpp \ nested_menu_controller.cpp \ palette.cpp \ @@ -54,13 +54,12 @@ escher_src += $(addprefix escher/src/,\ scroll_view.cpp \ scroll_view_data_source.cpp \ scroll_view_indicator.cpp \ + scrollable_expression_view.cpp \ scrollable_view.cpp \ selectable_table_view.cpp \ - selectable_table_view_delegate.cpp \ simple_list_view_data_source.cpp \ simple_table_view_data_source.cpp \ solid_color_view.cpp \ - solid_text_area.cpp \ stack_view.cpp \ stack_view_controller.cpp \ switch_view.cpp \ @@ -87,6 +86,9 @@ escher_src += $(addprefix escher/src/,\ window.cpp \ ) +tests_src += $(addprefix escher/test/,\ + layout_field.cpp\ +) $(eval $(call rule_for, \ HOSTCC, \ diff --git a/escher/include/escher.h b/escher/include/escher.h index 334325de2..e07ce1abb 100644 --- a/escher/include/escher.h +++ b/escher/include/escher.h @@ -58,6 +58,7 @@ #include #include #include +#include #include #include #include diff --git a/escher/include/escher/alternate_empty_view_controller.h b/escher/include/escher/alternate_empty_view_controller.h index 1e16d58fc..734cee60c 100644 --- a/escher/include/escher/alternate_empty_view_controller.h +++ b/escher/include/escher/alternate_empty_view_controller.h @@ -21,7 +21,7 @@ private: ContentView(ViewController * mainViewController, AlternateEmptyViewDelegate * delegate); ViewController * mainViewController() const; AlternateEmptyViewDelegate * alternateEmptyViewDelegate() const; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; private: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; diff --git a/escher/include/escher/app.h b/escher/include/escher/app.h index 6934235e6..b3812a4d8 100644 --- a/escher/include/escher/app.h +++ b/escher/include/escher/app.h @@ -9,6 +9,7 @@ #include #include #include +#include /* An app is fed events and outputs drawing calls. * @@ -25,10 +26,10 @@ class App : public Responder { public: class Descriptor { public: - virtual I18n::Message name(); - virtual I18n::Message upperName(); + virtual I18n::Message name() { return (I18n::Message)0; } + virtual I18n::Message upperName() { return (I18n::Message)0; } virtual int examinationLevel(); - virtual const Image * icon(); + virtual const Image * icon() { return nullptr; } const int NoExaminationLevel = 0; const int BasicExaminationLevel = 1; @@ -39,37 +40,44 @@ public: virtual App * unpack(Container * container) = 0; void pack(App * app); /* reset all instances to their initial values */ - virtual void reset(); + virtual void reset() {} virtual void storageDidChangeForRecord(Ion::Storage::Record) {} virtual Descriptor * descriptor() = 0; #if EPSILON_GETOPT virtual void setOpt(const char * name, const char * value) {} #endif /* tidy clean all dynamically-allocated data */ - virtual void tidy(); + virtual void tidy() {} }; /* The destructor has to be virtual. Otherwise calling a destructor on an * App * pointing to a Derived App would have undefined behaviour. */ virtual ~App() = default; Snapshot * snapshot() const { return m_snapshot; } void setFirstResponder(Responder * responder); - Responder * firstResponder(); + Responder * firstResponder() { return m_firstResponder; } virtual bool processEvent(Ion::Events::Event event); /* prepareForExit returns true if the app can be switched off in the current * runloop step, else it prepares for a switch off and returns false. */ virtual bool prepareForExit() { return true; } void displayModalViewController(ViewController * vc, float verticalAlignment, float horizontalAlignment, KDCoordinate topMargin = 0, KDCoordinate leftMargin = 0, KDCoordinate bottomMargin = 0, KDCoordinate rightMargin = 0); - void dismissModalViewController(); + void dismissModalViewController(bool willExitApp = false); void displayWarning(I18n::Message warningMessage1, I18n::Message warningMessage2 = (I18n::Message) 0, bool specialExitKeys = false); virtual void didBecomeActive(Window * window); virtual void willBecomeInactive(); View * modalView(); - virtual int numberOfTimers(); - virtual Timer * timerAtIndex(int i); + virtual int numberOfTimers() { return 0; } + virtual Timer * timerAtIndex(int i) { assert(false); return nullptr; } + virtual Poincare::Context * localContext() { return nullptr; } protected: - App(Snapshot * snapshot, ViewController * rootViewController, I18n::Message warningMessage = (I18n::Message)0); + App(Snapshot * snapshot, ViewController * rootViewController, I18n::Message warningMessage = (I18n::Message)0) : + Responder(nullptr), + m_modalViewController(this, rootViewController), + m_firstResponder(nullptr), + m_snapshot(snapshot), + m_warningController(this, warningMessage) + {} ModalViewController m_modalViewController; private: Responder * m_firstResponder; diff --git a/escher/include/escher/bank_view_controller.h b/escher/include/escher/bank_view_controller.h index 90f7270da..d68b9207b 100644 --- a/escher/include/escher/bank_view_controller.h +++ b/escher/include/escher/bank_view_controller.h @@ -31,8 +31,8 @@ private: assert(index == 0); return m_subview; } - void layoutSubviews() override { - m_subview->setFrame(bounds()); + void layoutSubviews(bool force = false) override { + m_subview->setFrame(bounds(), force); } View * m_subview; }; diff --git a/escher/include/escher/bordered.h b/escher/include/escher/bordered.h new file mode 100644 index 000000000..84a80f235 --- /dev/null +++ b/escher/include/escher/bordered.h @@ -0,0 +1,16 @@ +#ifndef ESCHER_BORDERED_H +#define ESCHER_BORDERED_H + +#include +#include + +class Bordered { +public: + void drawBorderOfRect(KDContext * ctx, KDRect rect, KDColor borderColor) const; + void drawInnerRect(KDContext * ctx, KDRect rect, KDColor backgroundColor) const; +protected: + constexpr static KDCoordinate k_separatorThickness = Metric::CellSeparatorThickness; +}; + +#endif + diff --git a/escher/include/escher/button.h b/escher/include/escher/button.h index c46b3d600..5e3d9e8a0 100644 --- a/escher/include/escher/button.h +++ b/escher/include/escher/button.h @@ -27,7 +27,7 @@ private: constexpr static KDCoordinate k_horizontalMarginLarge = 20; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; Invocation m_invocation; const KDFont * m_font; }; diff --git a/escher/include/escher/button_row_controller.h b/escher/include/escher/button_row_controller.h index dcba3c98f..120603339 100644 --- a/escher/include/escher/button_row_controller.h +++ b/escher/include/escher/button_row_controller.h @@ -45,7 +45,7 @@ private: void reload(); int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; void drawRect(KDContext * ctx, KDRect rect) const override; bool setSelectedButton(int selectedButton); int selectedButton() const { return m_selectedButton; } @@ -57,8 +57,6 @@ private: constexpr static KDCoordinate k_embossedStyleHeightLarge = 52; constexpr static KDCoordinate k_embossedStyleHeightMarginSmall = 6; constexpr static KDCoordinate k_embossedStyleHeightMarginLarge = 8; - constexpr static KDColor k_separatorHeaderColor = KDColor::RGB24(0xDEE0E2); - constexpr static KDColor k_selectedBackgroundColor = KDColor::RGB24(0x426DA7); ViewController * m_mainViewController; int m_selectedButton; ButtonRowDelegate * m_delegate; diff --git a/escher/include/escher/clipboard.h b/escher/include/escher/clipboard.h index 674e5e5f4..46a105e9a 100644 --- a/escher/include/escher/clipboard.h +++ b/escher/include/escher/clipboard.h @@ -7,7 +7,7 @@ class Clipboard { public: static Clipboard * sharedClipboard(); - void store(const char * storedText); + void store(const char * storedText, int length = -1); const char * storedText() const { return m_textBuffer; } void reset(); private: diff --git a/escher/include/escher/context_provider.h b/escher/include/escher/context_provider.h new file mode 100644 index 000000000..3f7458847 --- /dev/null +++ b/escher/include/escher/context_provider.h @@ -0,0 +1,11 @@ +#ifndef ESCHER_CONTEXT_PROVIDER_H +#define ESCHER_CONTEXT_PROVIDER_H + +#include + +class ContextProvider { +public: + virtual Poincare::Context * context() const { return nullptr; } +}; + +#endif diff --git a/escher/include/escher/editable_field.h b/escher/include/escher/editable_field.h index bdee14acc..008b2a167 100644 --- a/escher/include/escher/editable_field.h +++ b/escher/include/escher/editable_field.h @@ -5,6 +5,11 @@ #include #include +/* TODO: improve classes hierarchy to share selection handling (and some other + * features) between EditableField and TextInput. Refactor the following classes: + * InputEventHandler, TextInput, TextArea, EditableField, LayoutField, + * TextField, and their delegates! */ + class EditableField : public InputEventHandler { public: using InputEventHandler::InputEventHandler; diff --git a/escher/include/escher/editable_text_cell.h b/escher/include/escher/editable_text_cell.h index 8bf3a3348..3369f3460 100644 --- a/escher/include/escher/editable_text_cell.h +++ b/escher/include/escher/editable_text_cell.h @@ -26,7 +26,7 @@ public: } int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; void didBecomeFirstResponder() override; KDSize minimalSizeForOptimalDisplay() const override; private: diff --git a/escher/include/escher/even_odd_buffer_text_cell.h b/escher/include/escher/even_odd_buffer_text_cell.h index eed6f6b2a..917ce71d7 100644 --- a/escher/include/escher/even_odd_buffer_text_cell.h +++ b/escher/include/escher/even_odd_buffer_text_cell.h @@ -26,7 +26,7 @@ protected: static constexpr KDCoordinate k_horizontalMargin = Metric::CellMargin; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; BufferTextView m_bufferTextView; }; diff --git a/escher/include/escher/even_odd_cell_with_ellipsis.h b/escher/include/escher/even_odd_cell_with_ellipsis.h index 971950159..858130760 100644 --- a/escher/include/escher/even_odd_cell_with_ellipsis.h +++ b/escher/include/escher/even_odd_cell_with_ellipsis.h @@ -14,7 +14,7 @@ private: assert(index==0); return &m_ellipsisView; } - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; EllipsisView m_ellipsisView; }; diff --git a/escher/include/escher/even_odd_editable_text_cell.h b/escher/include/escher/even_odd_editable_text_cell.h index f19f9e7d3..b462144c6 100644 --- a/escher/include/escher/even_odd_editable_text_cell.h +++ b/escher/include/escher/even_odd_editable_text_cell.h @@ -21,7 +21,7 @@ public: private: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; EditableTextCell m_editableCell; }; diff --git a/escher/include/escher/even_odd_expression_cell.h b/escher/include/escher/even_odd_expression_cell.h index 6286141f6..00f884ac3 100644 --- a/escher/include/escher/even_odd_expression_cell.h +++ b/escher/include/escher/even_odd_expression_cell.h @@ -23,7 +23,7 @@ public: protected: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; ExpressionView m_expressionView; KDCoordinate m_leftMargin; KDCoordinate m_rightMargin; diff --git a/escher/include/escher/even_odd_message_text_cell.h b/escher/include/escher/even_odd_message_text_cell.h index 6572d8f4e..5bb9fceec 100644 --- a/escher/include/escher/even_odd_message_text_cell.h +++ b/escher/include/escher/even_odd_message_text_cell.h @@ -18,7 +18,7 @@ protected: constexpr static KDCoordinate k_horizontalMargin = Metric::CellMargin; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; MessageTextView m_messageTextView; }; diff --git a/escher/include/escher/expression_field.h b/escher/include/escher/expression_field.h index 0c4c1dc43..bdbec1afa 100644 --- a/escher/include/escher/expression_field.h +++ b/escher/include/escher/expression_field.h @@ -21,30 +21,31 @@ public: * use text() there... TODO: change text() for fillTextInBuffer?*/ const char * text(); void setText(const char * text); - void reload(); bool editionIsInTextField() const; bool isEmpty() const; - bool heightIsMaximal() const; + bool inputViewHeightDidChange(); bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false); /* View */ int numberOfSubviews() const override { return 1; } View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; void drawRect(KDContext * ctx, KDRect rect) const override; KDSize minimalSizeForOptimalDisplay() const override; /* Responder */ bool handleEvent(Ion::Events::Event event) override; + void didBecomeFirstResponder() override; private: static constexpr int k_textFieldBufferSize = TextField::maxBufferSize(); - static constexpr KDCoordinate k_textFieldHeight = 37; + static constexpr KDCoordinate k_minimalHeight = 37; + static constexpr KDCoordinate k_maximalHeight = 0.6*Ion::Display::Height; static constexpr KDCoordinate k_horizontalMargin = 5; static constexpr KDCoordinate k_verticalMargin = 5; constexpr static KDCoordinate k_separatorThickness = Metric::CellSeparatorThickness; KDCoordinate inputViewHeight() const; - KDCoordinate maximalHeight() const; + KDCoordinate m_inputViewMemoizedHeight; TextField m_textField; LayoutField m_layoutField; }; diff --git a/escher/include/escher/expression_table_cell.h b/escher/include/escher/expression_table_cell.h index 1c927d8ac..6027a70b8 100644 --- a/escher/include/escher/expression_table_cell.h +++ b/escher/include/escher/expression_table_cell.h @@ -1,18 +1,26 @@ #ifndef ESCHER_EXPRESSION_TABLE_CELL_H #define ESCHER_EXPRESSION_TABLE_CELL_H -#include +#include #include -class ExpressionTableCell : public TableCell { +class ExpressionTableCell : public Responder, public TableCell { public: - ExpressionTableCell(Layout layout = Layout::Horizontal); + ExpressionTableCell(Responder * responder = nullptr, Layout layout = Layout::HorizontalRightOverlap); View * labelView() const override; void setHighlighted(bool highlight) override; void setLayout(Poincare::Layout layout); Poincare::Layout layout() const override { return m_labelExpressionView.layout(); } + + Responder * responder() override { + return this; + } + void didBecomeFirstResponder() override; + virtual void reloadScroll() { m_labelExpressionView.reloadScroll(); } private: - ExpressionView m_labelExpressionView; + // Remove margins added by TableCell because they're already handled by ScrollableInputExactApproximateExpressionsView + KDCoordinate labelMargin() const override { return 0; } + ScrollableExpressionView m_labelExpressionView; }; #endif diff --git a/escher/include/escher/expression_table_cell_with_expression.h b/escher/include/escher/expression_table_cell_with_expression.h index bdabef00c..4942beeba 100644 --- a/escher/include/escher/expression_table_cell_with_expression.h +++ b/escher/include/escher/expression_table_cell_with_expression.h @@ -6,12 +6,16 @@ class ExpressionTableCellWithExpression : public ExpressionTableCell { public: - ExpressionTableCellWithExpression(); + ExpressionTableCellWithExpression(Responder * parentResponder = nullptr); View * accessoryView() const override; void setHighlighted(bool highlight) override; void setAccessoryLayout(Poincare::Layout l); + void didBecomeFirstResponder() override; + void reloadScroll() override { m_accessoryExpressionView.reloadScroll(); } private: - ExpressionView m_accessoryExpressionView; + // Accessory margin is already handled in ScrollableExpressionView + KDCoordinate accessoryMargin() const override { return 0; } + ScrollableExpressionView m_accessoryExpressionView; }; #endif diff --git a/escher/include/escher/expression_table_cell_with_pointer.h b/escher/include/escher/expression_table_cell_with_pointer.h index fc382125d..69a74ac37 100644 --- a/escher/include/escher/expression_table_cell_with_pointer.h +++ b/escher/include/escher/expression_table_cell_with_pointer.h @@ -7,7 +7,7 @@ class ExpressionTableCellWithPointer : public ExpressionTableCell { public: - ExpressionTableCellWithPointer(I18n::Message accessoryMessage = (I18n::Message)0, Layout layout = Layout::Horizontal); + ExpressionTableCellWithPointer(Responder * responder = nullptr, I18n::Message accessoryMessage = (I18n::Message)0, Layout layout = Layout::HorizontalRightOverlap); View * accessoryView() const override; void setHighlighted(bool highlight) override; void setAccessoryMessage(I18n::Message messageBody); diff --git a/escher/include/escher/expression_view.h b/escher/include/escher/expression_view.h index fda1f8472..a835eb273 100644 --- a/escher/include/escher/expression_view.h +++ b/escher/include/escher/expression_view.h @@ -14,7 +14,7 @@ class ExpressionView : public View { public: ExpressionView(float horizontalAlignment = 0.0f, float verticalAlignment = 0.5f, - KDColor textColor = Palette::PrimaryText, KDColor backgroundColor = Palette::ListCellBackground); + KDColor textColor = Palette::PrimaryText, KDColor backgroundColor = Palette::ListCellBackground, Poincare::Layout * selectionStart = nullptr, Poincare::Layout * selectionEnd = nullptr); Poincare::Layout layout() const { return m_layout; } bool setLayout(Poincare::Layout layout); void drawRect(KDContext * ctx, KDRect rect) const override; @@ -26,17 +26,20 @@ public: KDSize minimalSizeForOptimalDisplay() const override; KDPoint drawingOrigin() const; KDPoint absoluteDrawingOrigin() const; -private: +protected: /* Warning: we do not need to delete the previous expression layout when * deleting object or setting a new expression layout. Indeed, the expression * layout is always possessed by a controller which only gives a pointer to * the expression view (without cloning it). The named controller is then * responsible for freeing the expression layout when required. */ mutable Poincare::Layout m_layout; // TODO find better way to have minimalSizeForOptimalDisplay const - float m_horizontalAlignment; - float m_verticalAlignment; KDColor m_textColor; KDColor m_backgroundColor; + Poincare::Layout * m_selectionStart; + Poincare::Layout * m_selectionEnd; +private: + float m_horizontalAlignment; + float m_verticalAlignment; KDCoordinate m_horizontalMargin; }; diff --git a/escher/include/escher/input_event_handler.h b/escher/include/escher/input_event_handler.h index c033e1055..b5fed665f 100644 --- a/escher/include/escher/input_event_handler.h +++ b/escher/include/escher/input_event_handler.h @@ -3,6 +3,8 @@ #include +// See TODO in EditableField + class InputEventHandlerDelegate; class InputEventHandler { diff --git a/escher/include/escher/input_view_controller.h b/escher/include/escher/input_view_controller.h index 2867c6959..5d0bd9197 100644 --- a/escher/include/escher/input_view_controller.h +++ b/escher/include/escher/input_view_controller.h @@ -64,7 +64,6 @@ private: InputEventHandlerDelegate * m_inputEventHandlerDelegate; TextFieldDelegate * m_textFieldDelegate; LayoutFieldDelegate * m_layoutFieldDelegate; - bool m_inputViewHeightIsMaximal; }; #endif diff --git a/escher/include/escher/layout_field.h b/escher/include/escher/layout_field.h index 1fefd2688..663458c20 100644 --- a/escher/include/escher/layout_field.h +++ b/escher/include/escher/layout_field.h @@ -11,6 +11,8 @@ #include #include +// See TODO in EditableField + class LayoutField : public ScrollableView, public ScrollViewDataSource, public EditableField { public: LayoutField(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, LayoutFieldDelegate * delegate = nullptr) : @@ -20,15 +22,17 @@ public: m_delegate(delegate) {} void setDelegates(InputEventHandlerDelegate * inputEventHandlerDelegate, LayoutFieldDelegate * delegate) { m_inputEventHandlerDelegate = inputEventHandlerDelegate; m_delegate = delegate; } + Poincare::Context * context() const; bool isEditing() const override { return m_contentView.isEditing(); } void setEditing(bool isEditing) override; void clearLayout() { m_contentView.clearLayout(); } void scrollToCursor() { - scrollToBaselinedRect(m_contentView.cursorRect(), m_contentView.cursor()->baseline()); + scrollToBaselinedRect(m_contentView.cursorRect(), m_contentView.cursor()->baselineWithoutSelection()); } bool hasText() const { return layout().hasText(); } Poincare::Layout layout() const { return m_contentView.expressionView()->layout(); } CodePoint XNTCodePoint(CodePoint defaultXNTCodePoint) override; + void putCursorRightOfLayout(); // ScrollableView void setBackgroundColor(KDColor c) override { @@ -39,18 +43,20 @@ public: /* Responder */ bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; bool handleEvent(Ion::Events::Event event) override; - bool shouldFinishEditing(Ion::Events::Event event) override { // TODO REMOVE ? - return m_delegate->layoutFieldShouldFinishEditing(this, event); - } + // TODO: factorize with TextField (see TODO of EditableField) + bool shouldFinishEditing(Ion::Events::Event event) override; -protected: - void reload(KDSize previousSize); - virtual bool privateHandleEvent(Ion::Events::Event event); - bool privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout); + // Selection + bool resetSelection() { return m_contentView.resetSelection(); } + void deleteSelection(); private: constexpr static int k_maxNumberOfLayouts = 220; static_assert(k_maxNumberOfLayouts == TextField::maxBufferSize(), "Maximal number of layouts in a layout field should be equal to max number of char in text field"); + void reload(KDSize previousSize); + virtual bool privateHandleEvent(Ion::Events::Event event); + bool privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout); + bool privateHandleSelectionEvent(Ion::Events::Event event, bool * shouldRecomputeLayout); void scrollRightOfLayout(Poincare::Layout layoutR); void scrollToBaselinedRect(KDRect rect, KDCoordinate baseline); void insertLayoutAtCursor(Poincare::Layout layoutR, Poincare::Expression correspondingExpression, bool forceCursorRightOfLayout = false); @@ -62,26 +68,35 @@ private: bool setEditing(bool isEditing); // returns True if LayoutField should reload void setBackgroundColor(KDColor c) { m_expressionView.setBackgroundColor(c); } void setCursor(Poincare::LayoutCursor cursor) { m_cursor = cursor; } - void cursorPositionChanged() { layoutCursorSubview(); } + void cursorPositionChanged() { layoutCursorSubview(false); } KDRect cursorRect() { return m_cursorView.frame(); } Poincare::LayoutCursor * cursor() { return &m_cursor; } const ExpressionView * expressionView() const { return &m_expressionView; } - ExpressionView * editableExpressionView() { return &m_expressionView; } void clearLayout(); - /* View */ + // View KDSize minimalSizeForOptimalDisplay() const override; + // Selection + Poincare::Layout * selectionStart() { return &m_selectionStart; } + Poincare::Layout * selectionEnd() { return &m_selectionEnd; } + void addSelection(Poincare::Layout addedLayout); + bool resetSelection(); // returns true if the selection was indeed reset + void copySelection(Poincare::Context * context); + bool selectionIsEmpty() const; + void deleteSelection(); + private: - enum class Position { - Top, - Bottom - }; int numberOfSubviews() const override { return 2; } View * subviewAtIndex(int index) override; - void layoutSubviews() override; - void layoutCursorSubview(); + void layoutSubviews(bool force = false) override; + void layoutCursorSubview(bool force); + KDRect selectionRect() const; Poincare::LayoutCursor m_cursor; ExpressionView m_expressionView; TextCursorView m_cursorView; + /* The selection starts on the left of m_selectionStart, and ends on the + * right of m_selectionEnd. */ + Poincare::Layout m_selectionStart; + Poincare::Layout m_selectionEnd; bool m_isEditing; }; ContentView m_contentView; diff --git a/escher/include/escher/layout_field_delegate.h b/escher/include/escher/layout_field_delegate.h index 3d81702be..ce9dfaf02 100644 --- a/escher/include/escher/layout_field_delegate.h +++ b/escher/include/escher/layout_field_delegate.h @@ -1,12 +1,13 @@ #ifndef ESCHER_LAYOUT_FIELD_DELEGATE_H #define ESCHER_LAYOUT_FIELD_DELEGATE_H +#include #include #include class LayoutField; -class LayoutFieldDelegate { +class LayoutFieldDelegate : public ContextProvider{ public: virtual bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) = 0; virtual bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) = 0; diff --git a/escher/include/escher/message_table_cell.h b/escher/include/escher/message_table_cell.h index b87480201..788bfb1b1 100644 --- a/escher/include/escher/message_table_cell.h +++ b/escher/include/escher/message_table_cell.h @@ -7,7 +7,7 @@ class MessageTableCell : public TableCell { public: - MessageTableCell(I18n::Message label = (I18n::Message)0, const KDFont * font = KDFont::SmallFont, Layout layout = Layout::Horizontal); + MessageTableCell(I18n::Message label = (I18n::Message)0, const KDFont * font = KDFont::SmallFont, Layout layout = Layout::HorizontalLeftOverlap); View * labelView() const override; virtual void setHighlighted(bool highlight) override; void setMessage(I18n::Message message); diff --git a/escher/include/escher/message_table_cell_with_editable_text.h b/escher/include/escher/message_table_cell_with_editable_text.h index 089d165fc..54e0e19c6 100644 --- a/escher/include/escher/message_table_cell_with_editable_text.h +++ b/escher/include/escher/message_table_cell_with_editable_text.h @@ -28,7 +28,7 @@ public: void setAccessoryText(const char * text); void setTextColor(KDColor color) override; private: - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; TextField m_textField; char m_textBody[Poincare::PrintFloat::k_maxFloatCharSize]; }; diff --git a/escher/include/escher/message_tree.h b/escher/include/escher/message_tree.h index 7ddd88973..f6625cb3a 100644 --- a/escher/include/escher/message_tree.h +++ b/escher/include/escher/message_tree.h @@ -5,18 +5,18 @@ class MessageTree { public: - constexpr MessageTree(I18n::Message label = (I18n::Message)0, int numberOfChildren = 0) : + constexpr MessageTree(I18n::Message label = (I18n::Message)0, const int numberOfChildren = 0) : m_label(label), m_numberOfChildren(numberOfChildren) { }; virtual const MessageTree * children(int index) const = 0; - I18n::Message label() const; - int numberOfChildren() const; - bool isNull() const; + I18n::Message label() const { return m_label; } + int numberOfChildren() const { return m_numberOfChildren; } + bool isNull() const { return (m_label == (I18n::Message)0); } protected: I18n::Message m_label; - int m_numberOfChildren; + const int m_numberOfChildren; }; #endif diff --git a/escher/include/escher/metric.h b/escher/include/escher/metric.h index dfd388772..f514cb68c 100644 --- a/escher/include/escher/metric.h +++ b/escher/include/escher/metric.h @@ -17,11 +17,12 @@ public: constexpr static KDCoordinate ParameterCellHeight = 35; constexpr static KDCoordinate ModalTopMargin = 5; constexpr static KDCoordinate ModalBottomMargin = 18; - constexpr static KDCoordinate TableCellLabelTopMargin = 3; + constexpr static KDCoordinate TableCellVerticalMargin = 3; + constexpr static KDCoordinate TableCellHorizontalMargin = 10; constexpr static KDCoordinate TabHeight = 27; constexpr static KDCoordinate ScrollStep = 10; - constexpr static KDCoordinate PopUpLeftMargin = 32; - constexpr static KDCoordinate PopUpRightMargin = 32; + constexpr static KDCoordinate PopUpLeftMargin = 27; + constexpr static KDCoordinate PopUpRightMargin = 27; constexpr static KDCoordinate PopUpTopMargin = 50; constexpr static KDCoordinate ExamPopUpTopMargin = 27; constexpr static KDCoordinate ExamPopUpBottomMargin = 55; @@ -34,6 +35,7 @@ public: constexpr static KDCoordinate CellSeparatorThickness = 1; constexpr static KDCoordinate TableSeparatorThickness = 5; constexpr static KDCoordinate ExpressionViewHorizontalMargin = 5; + constexpr static KDCoordinate EllipsisCellWidth = 37; }; #endif diff --git a/escher/include/escher/modal_view_controller.h b/escher/include/escher/modal_view_controller.h index 1bce427ad..f0d2e7758 100644 --- a/escher/include/escher/modal_view_controller.h +++ b/escher/include/escher/modal_view_controller.h @@ -14,7 +14,7 @@ public: void displayModalViewController(ViewController * vc, float verticalAlignment, float horizontalAlignment, KDCoordinate topMargin = 0, KDCoordinate leftMargin = 0, KDCoordinate bottomMargin = 0, KDCoordinate rightMargin = 0); void reloadModalViewController(); - void dismissModalViewController(); + void dismissModalViewController(bool willExitApp = false); bool isDisplayingModal(); void initView() override; void viewWillAppear() override; @@ -28,10 +28,10 @@ private: void setMainView(View * regularView); int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; void presentModalView(View * modalView, float verticalAlignment, float horizontalAlignment, KDCoordinate topMargin, KDCoordinate leftMargin, KDCoordinate bottomMargin, KDCoordinate rightMargin); - void dismissModalView(); + void dismissModalView(bool willExitApp = false); bool isDisplayingModal() const; void reload(); private: diff --git a/escher/include/escher/scroll_view.h b/escher/include/escher/scroll_view.h index 920860fc7..a5790af32 100644 --- a/escher/include/escher/scroll_view.h +++ b/escher/include/escher/scroll_view.h @@ -6,6 +6,13 @@ #include class ScrollView : public View { + +/* TODO: Should we add a reload method that forces the relayouting of the + * subviews? Or should ScrollView::setFrame always force the layouting of the + * subviews ? Because the scroll view frame might not change but its content + * might need to be relayouted. + * cf TableView, InputViewController, EditExpressionController. */ + public: ScrollView(View * contentView, ScrollViewDataSource * dataSource); ScrollView(ScrollView&& other); @@ -35,7 +42,7 @@ public: virtual ~Decorator() = default; virtual int numberOfIndicators() const { return 0; } virtual View * indicatorAtIndex(int index) { assert(false); return nullptr; } - virtual KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2) { return frame; } + virtual KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) { return frame; } virtual void setBackgroundColor(KDColor c) {} }; @@ -44,7 +51,7 @@ public: BarDecorator() : m_verticalBar(), m_horizontalBar() {} int numberOfIndicators() const override { return 2; } View * indicatorAtIndex(int index) override; - KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2) override; + KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) override; ScrollViewVerticalBar * verticalBar() { return &m_verticalBar; } ScrollViewHorizontalBar * horizontalBar() { return &m_horizontalBar; } private: @@ -63,7 +70,7 @@ public: {} int numberOfIndicators() const override { return 4; } View * indicatorAtIndex(int index) override; - KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2) override; + KDRect layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) override; void setBackgroundColor(KDColor c) override; private: ScrollViewArrow m_topArrow; @@ -96,7 +103,7 @@ protected: return m_frame.height() - m_topMargin - m_bottomMargin; } KDRect visibleContentRect(); - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; virtual KDSize contentSize() const { return m_contentView->minimalSizeForOptimalDisplay(); } #if ESCHER_VIEW_LOGGING virtual const char * className() const override; diff --git a/escher/include/escher/scrollable_expression_view.h b/escher/include/escher/scrollable_expression_view.h new file mode 100644 index 000000000..a932757b0 --- /dev/null +++ b/escher/include/escher/scrollable_expression_view.h @@ -0,0 +1,19 @@ +#ifndef ESCHER_SCROLLABLE_EXPRESSION_VIEW_H +#define ESCHER_SCROLLABLE_EXPRESSION_VIEW_H + +#include +#include +#include + +class ScrollableExpressionView : public ScrollableView, public ScrollViewDataSource { +public: + ScrollableExpressionView(Responder * parentResponder, KDCoordinate leftRightMargin, KDCoordinate topBottomMargin, float horizontalAlignment = 0.0f, float verticalAlignment = 0.5f, KDColor textColor = KDColorBlack, KDColor backgroundColor = KDColorWhite); + Poincare::Layout layout() const; + void setLayout(Poincare::Layout layout); + void setBackgroundColor(KDColor backgroundColor) override; + void setExpressionBackgroundColor(KDColor backgroundColor); +private: + ExpressionView m_expressionView; +}; + +#endif diff --git a/escher/include/escher/selectable_table_view_delegate.h b/escher/include/escher/selectable_table_view_delegate.h index e8f11864a..c7e20ee7b 100644 --- a/escher/include/escher/selectable_table_view_delegate.h +++ b/escher/include/escher/selectable_table_view_delegate.h @@ -1,16 +1,18 @@ #ifndef ESCHER_SELECTABLE_TABLE_VIEW_DELEGATE_H #define ESCHER_SELECTABLE_TABLE_VIEW_DELEGATE_H +#include + class SelectableTableView; -class SelectableTableViewDelegate { +class SelectableTableViewDelegate : public ContextProvider { public: /* withinTemporarySelection flag indicates when the selection change happens * in a temporary deselection: indeed, when reloading the data of the table, * we deselect the table before re-layouting the entire table and re-select * 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 tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false) {} }; #endif diff --git a/escher/include/escher/stack_view.h b/escher/include/escher/stack_view.h index ec3a5d649..213518fbf 100644 --- a/escher/include/escher/stack_view.h +++ b/escher/include/escher/stack_view.h @@ -1,10 +1,11 @@ #ifndef ESCHER_STACK_VIEW_H #define ESCHER_STACK_VIEW_H +#include #include #include -class StackView : public View { +class StackView : public View, public Bordered { public: StackView(); void drawRect(KDContext * ctx, KDRect rect) const override; diff --git a/escher/include/escher/stack_view_controller.h b/escher/include/escher/stack_view_controller.h index 7fbf58db4..1ff89e7a6 100644 --- a/escher/include/escher/stack_view_controller.h +++ b/escher/include/escher/stack_view_controller.h @@ -57,7 +57,7 @@ private: private: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; StackView m_stackViews[kMaxNumberOfStacks]; View * m_contentView; diff --git a/escher/include/escher/tab_view.h b/escher/include/escher/tab_view.h index ab662d356..ebfaba6af 100644 --- a/escher/include/escher/tab_view.h +++ b/escher/include/escher/tab_view.h @@ -27,7 +27,7 @@ private: constexpr static KDCoordinate k_activeTabHeight = 5; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; constexpr static uint8_t k_maxNumberOfTabs = 4; TabViewCell m_cells[k_maxNumberOfTabs]; diff --git a/escher/include/escher/tab_view_controller.h b/escher/include/escher/tab_view_controller.h index f7bed6244..11d193b38 100644 --- a/escher/include/escher/tab_view_controller.h +++ b/escher/include/escher/tab_view_controller.h @@ -37,7 +37,7 @@ private: private: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; View * m_activeView; }; diff --git a/escher/include/escher/table_cell.h b/escher/include/escher/table_cell.h index 8058a090d..e46da7bf6 100644 --- a/escher/include/escher/table_cell.h +++ b/escher/include/escher/table_cell.h @@ -1,29 +1,36 @@ #ifndef ESCHER_TABLE_CELL_H #define ESCHER_TABLE_CELL_H +#include #include -#include -class TableCell : public HighlightCell { +class TableCell : public Bordered, public HighlightCell { public: + /* Layout enum class determines the way subviews are layouted. + * We can split the cell vertically or horizontally. + * We can choose which subviews frames are optimized (if there is not enough + * space for all subviews, which one is cropped). This case happens so far only + * for horizontally splitted cell, so we distinguish only these sub cases. + * TODO: implement VerticalTopOverlap, VerticalBottomlap? */ enum class Layout { Vertical, - Horizontal + HorizontalLeftOverlap, // Label overlaps on SubAccessory which overlaps on Accessory + HorizontalRightOverlap, // Reverse }; - TableCell(Layout layout = Layout::Horizontal); + TableCell(Layout layout = Layout::HorizontalLeftOverlap); virtual View * labelView() const; virtual View * accessoryView() const; virtual View * subAccessoryView() const; void drawRect(KDContext * ctx, KDRect rect) const override; - constexpr static KDCoordinate k_labelMargin = 10; - constexpr static KDCoordinate k_accessoryMargin = 10; protected: + virtual KDCoordinate labelMargin() const { return Metric::TableCellHorizontalMargin; } + virtual KDCoordinate accessoryMargin() const { return Metric::TableCellHorizontalMargin; } int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; - constexpr static KDCoordinate k_separatorThickness = Metric::CellSeparatorThickness; + void layoutSubviews(bool force = false) override; + constexpr static KDCoordinate k_verticalMargin = Metric::TableCellVerticalMargin; + constexpr static KDCoordinate k_horizontalMargin = Metric::TableCellHorizontalMargin; private: - constexpr static KDCoordinate k_accessoryBottomMargin = 3; Layout m_layout; }; diff --git a/escher/include/escher/table_view.h b/escher/include/escher/table_view.h index 5c16a8c30..b7eb77ae5 100644 --- a/escher/include/escher/table_view.h +++ b/escher/include/escher/table_view.h @@ -25,7 +25,7 @@ protected: const char * className() const override; #endif TableViewDataSource * dataSource(); - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; class ContentView : public View { public: ContentView(TableView * tableView, TableViewDataSource * dataSource, KDCoordinate horizontalCellOverlap, KDCoordinate verticalCellOverlap); @@ -42,7 +42,7 @@ protected: int numberOfDisplayableRows() const; int numberOfDisplayableColumns() const; KDRect cellFrame(int i, int j) const; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; protected: #if ESCHER_VIEW_LOGGING const char * className() const override; diff --git a/escher/include/escher/text_area.h b/escher/include/escher/text_area.h index f4bc36f48..640511dc1 100644 --- a/escher/include/escher/text_area.h +++ b/escher/include/escher/text_area.h @@ -7,6 +7,8 @@ #include #include +// See TODO in EditableField + class TextArea : public TextInput, public InputEventHandler { public: static constexpr int k_indentationSpaces = 2; @@ -80,6 +82,7 @@ protected: void insertSpacesAtLocation(int numberOfSpaces, char * location); CodePoint removePreviousGlyph(char * * position); + size_t removeText(const char * start, const char * end); size_t removeRemainingLine(const char * position, int direction); char operator[](size_t index) { assert(index < m_bufferSize); @@ -105,8 +108,8 @@ protected: m_cursorLocation = m_text.text(); } void drawRect(KDContext * ctx, KDRect rect) const override; - void drawStringAt(KDContext * ctx, int line, int column, const char * text, size_t length, KDColor textColor, KDColor backgroundColor) const; - virtual void drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn) const = 0; + void drawStringAt(KDContext * ctx, int line, int column, const char * text, int length, KDColor textColor, KDColor backgroundColor, const char * selectionStart, const char * selectionEnd, KDColor backgroundHighlightColor) const; + virtual void drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn, const char * selectionStart, const char * selectionEnd) const = 0; virtual void clearRect(KDContext * ctx, KDRect rect) const = 0; KDSize minimalSizeForOptimalDisplay() const override; void setText(char * textBuffer, size_t textBufferSize); @@ -114,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, const char * location) override; + bool insertTextAtLocation(const char * text, char * location) override; void moveCursorGeo(int deltaX, int deltaY); bool removePreviousGlyph() override; bool removeEndOfLine() override; bool removeStartOfLine(); + size_t deleteSelection() override; protected: KDRect glyphFrameAtPosition(const char * text, const char * position) const override; Text m_text; @@ -126,6 +130,7 @@ protected: ContentView * contentView() { return static_cast(TextInput::contentView()); } private: + void selectUpDown(bool up); TextAreaDelegate * m_delegate; }; diff --git a/escher/include/escher/text_field.h b/escher/include/escher/text_field.h index 88ca4ad4a..603c66b11 100644 --- a/escher/include/escher/text_field.h +++ b/escher/include/escher/text_field.h @@ -6,6 +6,8 @@ #include #include +// See TODO in EditableField + /* TODO: TextField currently uses using 2 buffers: * - one to keep the displayed text * - another one to edit the text while keeping the previous text in the first @@ -28,7 +30,6 @@ public: char * draftTextBuffer() const { return const_cast(m_contentView.editedText()); } size_t draftTextLength() const; void setText(const char * text); - void setAlignment(float horizontalAlignment, float verticalAlignment); void setEditing(bool isEditing) override { m_contentView.setEditing(isEditing); } CodePoint XNTCodePoint(CodePoint defaultXNTCodePoint) override; bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; @@ -37,12 +38,13 @@ public: return ContentView::k_maxBufferSize; } void scrollToCursor() override; - bool shouldFinishEditing(Ion::Events::Event event) override { return m_delegate->textFieldShouldFinishEditing(this, event); } + // TODO: factorize with TextField (see TODO of EditableField) + bool shouldFinishEditing(Ion::Events::Event event) override; const KDFont * font() const { return m_contentView.font(); } protected: class ContentView : public TextInput::ContentView { public: - ContentView(char * textBuffer, size_t textBufferSize, size_t draftTextBufferSize, const KDFont * font, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor); + ContentView(char * textBuffer, size_t textBufferSize, size_t draftTextBufferSize, const KDFont * font, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor backgroundColor); void setBackgroundColor(KDColor backgroundColor); KDColor backgroundColor() const { return m_backgroundColor; } void setTextColor(KDColor textColor); @@ -52,19 +54,19 @@ protected: const char * editedText() const override; size_t editedTextLength() const override { return m_currentDraftTextLength; } void setText(const char * text); - void setAlignment(float horizontalAlignment, float verticalAlignment); void setEditing(bool isEditing); void reinitDraftTextBuffer(); void setDraftTextBufferSize(size_t size) { m_draftTextBufferSize = size; } /* 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, const char * location) override; + bool insertTextAtLocation(const char * text, char * location) override; KDSize minimalSizeForOptimalDisplay() const override; bool removePreviousGlyph() override; bool removeEndOfLine() override; void willModifyTextBuffer(); void didModifyTextBuffer(); + size_t deleteSelection() override; /* In some app (ie Calculation), text fields record expression results whose * lengths can reach 70 (ie * [[1.234567e-123*e^(1.234567e-123*i), 1.234567e-123*e^(1.234567e-123*i)]]). @@ -75,15 +77,13 @@ protected: * = 212 characters. */ constexpr static int k_maxBufferSize = 220; private: - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; KDRect glyphFrameAtPosition(const char * buffer, const char * position) const override; bool m_isEditing; char * m_textBuffer; size_t m_textBufferSize; size_t m_draftTextBufferSize; size_t m_currentDraftTextLength; - float m_horizontalAlignment; - float m_verticalAlignment; KDColor m_textColor; KDColor m_backgroundColor; }; @@ -92,7 +92,9 @@ protected: private: bool privateHandleEvent(Ion::Events::Event event); bool privateHandleMoveEvent(Ion::Events::Event event); + bool privateHandleSelectEvent(Ion::Events::Event event); virtual void removeWholeText(); + bool storeInClipboard() const; TextFieldDelegate * m_delegate; }; diff --git a/escher/include/escher/text_input.h b/escher/include/escher/text_input.h index 633fcd790..82bb60106 100644 --- a/escher/include/escher/text_input.h +++ b/escher/include/escher/text_input.h @@ -6,6 +6,8 @@ #include #include +// See TODO in EditableField + class TextInput : public ScrollableView, public ScrollViewDataSource { public: TextInput(Responder * parentResponder, View * contentView) : ScrollableView(parentResponder, contentView, this) {} @@ -15,32 +17,68 @@ public: const char * cursorLocation() const { return nonEditableContentView()->cursorLocation(); } bool setCursorLocation(const char * location); virtual void scrollToCursor(); + // Selection + bool selectionIsEmpty() const { return nonEditableContentView()->selectionIsEmpty(); } + void resetSelection() { contentView()->resetSelection(); } + void deleteSelection(); + // Alignment + void setAlignment(float horizontalAlignment, float verticalAlignment); protected: + class ContentView : public View { public: - ContentView(const KDFont * font) : + ContentView(const KDFont * font, float horizontalAlignment = 0.0f, float verticalAlignment = 0.5f) : View(), m_cursorView(), m_font(font), - m_cursorLocation(nullptr) + m_selectionStart(nullptr), + m_selectionEnd(nullptr), + m_cursorLocation(nullptr), + m_horizontalAlignment(horizontalAlignment), + m_verticalAlignment(verticalAlignment) {} + + // Font void setFont(const KDFont * font); const KDFont * font() const { return m_font; } + + // Cursor location const char * cursorLocation() const { assert(m_cursorLocation != nullptr); return m_cursorLocation; } void setCursorLocation(const char * cursorLocation); + KDRect cursorRect(); + + // Virtual text get/add/remove virtual const char * text() const = 0; - virtual bool insertTextAtLocation(const char * text, const char * location) = 0; + virtual bool insertTextAtLocation(const char * text, char * location) = 0; virtual bool removePreviousGlyph() = 0; virtual bool removeEndOfLine() = 0; - KDRect cursorRect(); + + // Selection + const char * selectionStart() const { return m_selectionStart; } + const char * selectionEnd() const { return m_selectionEnd; } + void addSelection(const char * left, const char * right); + bool resetSelection(); // returns true if the selection was indeed reset + bool selectionIsEmpty() const; + virtual size_t deleteSelection() = 0; + + // Alignment + void setAlignment(float horizontalAlignment, float verticalAlignment); + float horizontalAlignment() const { return m_horizontalAlignment; } + + // Reload + void reloadRectFromPosition(const char * position, bool includeFollowingLines = false); protected: - virtual void layoutSubviews() override; - void reloadRectFromPosition(const char * position, bool lineBreak = false); + virtual void layoutSubviews(bool force = false) override; + void reloadRectFromAndToPositions(const char * start, const char * end); virtual KDRect glyphFrameAtPosition(const char * buffer, const char * position) const = 0; + virtual KDRect dirtyRectFromPosition(const char * position, bool includeFollowingLines) const; TextCursorView m_cursorView; const KDFont * m_font; + const char * m_selectionStart; + const char * m_selectionEnd; const char * m_cursorLocation; - virtual KDRect dirtyRectFromPosition(const char * position, bool lineBreak) const; + float m_horizontalAlignment; + float m_verticalAlignment; private: int numberOfSubviews() const override { return 1; } View * subviewAtIndex(int index) override { @@ -54,7 +92,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 * textBuffer, const char * location); + bool insertTextAtLocation(const char * textBuffer, char * location); bool removeEndOfLine(); ContentView * contentView() { return const_cast(nonEditableContentView()); @@ -62,6 +100,7 @@ protected: virtual const ContentView * nonEditableContentView() const = 0; bool moveCursorLeft(); bool moveCursorRight(); + bool selectLeftRight(bool left, bool all); // While indicates if all the text on the left/right should be selected private: virtual void willSetCursorLocation(const char * * location) {} virtual bool privateRemoveEndOfLine(); diff --git a/escher/include/escher/view.h b/escher/include/escher/view.h index 442c53b40..e0cd46d34 100644 --- a/escher/include/escher/view.h +++ b/escher/include/escher/view.h @@ -30,7 +30,7 @@ class View { friend class TransparentView; friend class Shared::RoundCursorView; public: - View(); + View() : m_frame(KDRectZero), m_superview(nullptr), m_dirtyRect(KDRectZero) {} virtual ~View() { for (int i = 0; i < numberOfSubviews(); i++) { View * subview = subviewAtIndex(i); @@ -49,16 +49,18 @@ public: /* The drawRect method should be implemented by each View subclass. In a * typical drawRect implementation, a subclass will make drawing calls to the * Kandinsky library using the provided context. */ - virtual void drawRect(KDContext * ctx, KDRect rect) const; + virtual void drawRect(KDContext * ctx, KDRect rect) const { + // By default, a view doesn't do anything, it's transparent + } void setSize(KDSize size); - void setFrame(KDRect frame); + void setFrame(KDRect frame, bool force); KDPoint pointFromPointInView(View * view, KDPoint point); KDRect bounds() const; View * subview(int index); - virtual KDSize minimalSizeForOptimalDisplay() const; + virtual KDSize minimalSizeForOptimalDisplay() const { return KDSizeZero; } #if ESCHER_VIEW_LOGGING friend std::ostream &operator<<(std::ostream &os, View &view); @@ -80,13 +82,9 @@ protected: #endif KDRect m_frame; private: - virtual int numberOfSubviews() const { - return 0; - } - virtual View * subviewAtIndex(int index) { - return nullptr; - } - virtual void layoutSubviews(); + virtual int numberOfSubviews() const { return 0; } + virtual View * subviewAtIndex(int index) { return nullptr; } + virtual void layoutSubviews(bool force = false) {} virtual const Window * window() const; KDRect redraw(KDRect rect, KDRect forceRedrawRect = KDRectZero); KDPoint absoluteOrigin() const; diff --git a/escher/include/escher/warning_controller.h b/escher/include/escher/warning_controller.h index 464e1509a..c9cbec7f8 100644 --- a/escher/include/escher/warning_controller.h +++ b/escher/include/escher/warning_controller.h @@ -20,7 +20,7 @@ private: void setLabels(I18n::Message message1, I18n::Message message2); int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - void layoutSubviews() override; + void layoutSubviews(bool force = false) override; KDSize minimalSizeForOptimalDisplay() const override; private: constexpr static KDCoordinate k_topAndBottomMargin = 20; diff --git a/escher/include/escher/window.h b/escher/include/escher/window.h index 7930d64d2..0e618083a 100644 --- a/escher/include/escher/window.h +++ b/escher/include/escher/window.h @@ -13,7 +13,7 @@ protected: const char * className() const override; #endif virtual int numberOfSubviews() const override; - virtual void layoutSubviews() override; + virtual void layoutSubviews(bool force = false) override; virtual View * subviewAtIndex(int index) override; View * m_contentView; private: diff --git a/escher/src/alternate_empty_view_controller.cpp b/escher/src/alternate_empty_view_controller.cpp index 9dc3c33d4..be61f67f2 100644 --- a/escher/src/alternate_empty_view_controller.cpp +++ b/escher/src/alternate_empty_view_controller.cpp @@ -22,11 +22,11 @@ View * AlternateEmptyViewController::ContentView::subviewAtIndex(int index) { return m_mainViewController->view(); } -void AlternateEmptyViewController::ContentView::layoutSubviews() { +void AlternateEmptyViewController::ContentView::layoutSubviews(bool force) { if (alternateEmptyViewDelegate()->isEmpty()) { - m_delegate->emptyView()->setFrame(bounds()); + m_delegate->emptyView()->setFrame(bounds(), force); } else { - m_mainViewController->view()->setFrame(bounds()); + m_mainViewController->view()->setFrame(bounds(), force); } } diff --git a/escher/src/app.cpp b/escher/src/app.cpp index 3879d8bf0..247c2f054 100644 --- a/escher/src/app.cpp +++ b/escher/src/app.cpp @@ -5,43 +5,16 @@ extern "C" { #include } -I18n::Message App::Descriptor::name() { - return (I18n::Message)0; -} - -I18n::Message App::Descriptor::upperName() { - return (I18n::Message)0; -} - int App::Descriptor::examinationLevel() { return App::Descriptor::NoExaminationLevel; } -const Image * App::Descriptor::icon() { - return nullptr; -} - void App::Snapshot::pack(App * app) { tidy(); app->~App(); assert(Poincare::TreePool::sharedPool()->numberOfNodes() == 0); } -void App::Snapshot::reset() { -} - -void App::Snapshot::tidy() { -} - -App::App(Snapshot * snapshot, ViewController * rootViewController, I18n::Message warningMessage) : - Responder(nullptr), - m_modalViewController(this, rootViewController), - m_firstResponder(nullptr), - m_snapshot(snapshot), - m_warningController(this, warningMessage) -{ -} - bool App::processEvent(Ion::Events::Event event) { Responder * responder = m_firstResponder; bool didHandleEvent = false; @@ -55,10 +28,6 @@ bool App::processEvent(Ion::Events::Event event) { return false; } -Responder * App::firstResponder() { - return m_firstResponder; -} - void App::setFirstResponder(Responder * responder) { Responder * previousResponder = m_firstResponder; m_firstResponder = responder; @@ -90,8 +59,8 @@ void App::displayModalViewController(ViewController * vc, float verticalAlignmen m_modalViewController.displayModalViewController(vc, verticalAlignment, horizontalAlignment, topMargin, leftMargin, bottomMargin, rightMargin); } -void App::dismissModalViewController() { - m_modalViewController.dismissModalViewController(); +void App::dismissModalViewController(bool willExitApp) { + m_modalViewController.dismissModalViewController(willExitApp); } void App::displayWarning(I18n::Message warningMessage1, I18n::Message warningMessage2, bool specialExitKeys) { @@ -109,7 +78,7 @@ void App::didBecomeActive(Window * window) { void App::willBecomeInactive() { if (m_modalViewController.isDisplayingModal()) { - dismissModalViewController(); + dismissModalViewController(true); } setFirstResponder(nullptr); m_modalViewController.viewDidDisappear(); @@ -118,12 +87,3 @@ void App::willBecomeInactive() { View * App::modalView() { return m_modalViewController.view(); } - -int App::numberOfTimers() { - return 0; -} - -Timer * App::timerAtIndex(int i) { - assert(false); - return nullptr; -} diff --git a/escher/src/bordered.cpp b/escher/src/bordered.cpp new file mode 100644 index 000000000..8a9d4a064 --- /dev/null +++ b/escher/src/bordered.cpp @@ -0,0 +1,15 @@ +#include + +void Bordered::drawBorderOfRect(KDContext * ctx, KDRect rect, KDColor borderColor) const { + KDCoordinate width = rect.width(); + KDCoordinate height = rect.height(); + // Draw rectangle around cell + ctx->fillRect(KDRect(0, 0, width, k_separatorThickness), borderColor); + ctx->fillRect(KDRect(0, k_separatorThickness, k_separatorThickness, height-k_separatorThickness), borderColor); + ctx->fillRect(KDRect(width-k_separatorThickness, k_separatorThickness, k_separatorThickness, height-k_separatorThickness), borderColor); + ctx->fillRect(KDRect(0, height-k_separatorThickness, width, k_separatorThickness), borderColor); + } + +void Bordered::drawInnerRect(KDContext * ctx, KDRect rect, KDColor backgroundColor) const { + ctx->fillRect(KDRect(k_separatorThickness, k_separatorThickness, rect.width()-2*k_separatorThickness, rect.height()-2*k_separatorThickness), backgroundColor); +} diff --git a/escher/src/button.cpp b/escher/src/button.cpp index 7b88d1449..d1539c27c 100644 --- a/escher/src/button.cpp +++ b/escher/src/button.cpp @@ -24,8 +24,8 @@ View * Button::subviewAtIndex(int index) { return &m_messageTextView; } -void Button::layoutSubviews() { - m_messageTextView.setFrame(bounds()); +void Button::layoutSubviews(bool force) { + m_messageTextView.setFrame(bounds(), force); } bool Button::handleEvent(Ion::Events::Event event) { diff --git a/escher/src/button_row_controller.cpp b/escher/src/button_row_controller.cpp index d71a5c3a1..0d62fd3f5 100644 --- a/escher/src/button_row_controller.cpp +++ b/escher/src/button_row_controller.cpp @@ -49,12 +49,12 @@ View * ButtonRowController::ContentView::subviewAtIndex(int index) { } } -void ButtonRowController::ContentView::layoutSubviews() { +void ButtonRowController::ContentView::layoutSubviews(bool force) { /* Position the main view */ if (numberOfButtons() == 0) { KDCoordinate margin = m_position == Position::Top ? 1 : 0; KDRect mainViewFrame(0, margin, bounds().width(), bounds().height()-margin); - m_mainViewController->view()->setFrame(mainViewFrame); + m_mainViewController->view()->setFrame(mainViewFrame, force); return; } KDCoordinate rowHeight; @@ -65,7 +65,7 @@ void ButtonRowController::ContentView::layoutSubviews() { } KDCoordinate frameOrigin = m_position == Position::Top ? rowHeight+1 : 0; KDRect mainViewFrame(0, frameOrigin, bounds().width(), bounds().height() - rowHeight - 1); - m_mainViewController->view()->setFrame(mainViewFrame); + m_mainViewController->view()->setFrame(mainViewFrame, force); /* Position buttons */ int nbOfButtons = numberOfButtons(); @@ -88,7 +88,7 @@ void ButtonRowController::ContentView::layoutSubviews() { Button * button = buttonAtIndex(i); KDCoordinate buttonWidth = button->minimalSizeForOptimalDisplay().width(); KDRect buttonFrame(currentXOrigin, yOrigin, buttonWidth, buttonHeight); - button->setFrame(buttonFrame); + button->setFrame(buttonFrame, force); currentXOrigin += buttonWidth + widthMargin; } } diff --git a/escher/src/clipboard.cpp b/escher/src/clipboard.cpp index 3996a9ffd..446211c4b 100644 --- a/escher/src/clipboard.cpp +++ b/escher/src/clipboard.cpp @@ -2,12 +2,14 @@ static Clipboard s_clipboard; +static inline int minInt(int x, int y) { return x < y ? x : y; } + Clipboard * Clipboard::sharedClipboard() { return &s_clipboard; } -void Clipboard::store(const char * storedText) { - strlcpy(m_textBuffer, storedText, TextField::maxBufferSize()); +void Clipboard::store(const char * storedText, int length) { + strlcpy(m_textBuffer, storedText, length == -1 ? TextField::maxBufferSize() : minInt(TextField::maxBufferSize(), length + 1)); } void Clipboard::reset() { diff --git a/escher/src/editable_text_cell.cpp b/escher/src/editable_text_cell.cpp index dfdf325a1..9a835cabd 100644 --- a/escher/src/editable_text_cell.cpp +++ b/escher/src/editable_text_cell.cpp @@ -43,12 +43,13 @@ View * EditableTextCell::subviewAtIndex(int index) { return &m_textField; } -void EditableTextCell::layoutSubviews() { +void EditableTextCell::layoutSubviews(bool force) { KDRect cellBounds = bounds(); m_textField.setFrame(KDRect(cellBounds.x() + m_leftMargin, cellBounds.y() + m_topMargin, cellBounds.width() - m_leftMargin - m_rightMargin, - cellBounds.height() - m_topMargin - m_bottomMargin)); + cellBounds.height() - m_topMargin - m_bottomMargin), + force); } void EditableTextCell::didBecomeFirstResponder() { diff --git a/escher/src/even_odd_buffer_text_cell.cpp b/escher/src/even_odd_buffer_text_cell.cpp index 48e3ba1ea..f5c2d7968 100644 --- a/escher/src/even_odd_buffer_text_cell.cpp +++ b/escher/src/even_odd_buffer_text_cell.cpp @@ -38,8 +38,8 @@ View * EvenOddBufferTextCell::subviewAtIndex(int index) { return &m_bufferTextView; } -void EvenOddBufferTextCell::layoutSubviews() { +void EvenOddBufferTextCell::layoutSubviews(bool force) { KDRect boundsThis = bounds(); KDRect boundsBuffer = KDRect(boundsThis.left() + k_horizontalMargin, boundsThis.top(), boundsThis.width() - 2*k_horizontalMargin, boundsThis.height()); - m_bufferTextView.setFrame(boundsBuffer); + m_bufferTextView.setFrame(boundsBuffer, force); } diff --git a/escher/src/even_odd_cell_with_ellipsis.cpp b/escher/src/even_odd_cell_with_ellipsis.cpp index 4a43cc5f9..53a85c298 100644 --- a/escher/src/even_odd_cell_with_ellipsis.cpp +++ b/escher/src/even_odd_cell_with_ellipsis.cpp @@ -5,6 +5,6 @@ EvenOddCellWithEllipsis::EvenOddCellWithEllipsis() : { } -void EvenOddCellWithEllipsis::layoutSubviews() { - m_ellipsisView.setFrame(bounds()); +void EvenOddCellWithEllipsis::layoutSubviews(bool force) { + m_ellipsisView.setFrame(bounds(), force); } diff --git a/escher/src/even_odd_editable_text_cell.cpp b/escher/src/even_odd_editable_text_cell.cpp index 583a938fb..0f42d93d3 100644 --- a/escher/src/even_odd_editable_text_cell.cpp +++ b/escher/src/even_odd_editable_text_cell.cpp @@ -32,8 +32,8 @@ View * EvenOddEditableTextCell::subviewAtIndex(int index) { return &m_editableCell; } -void EvenOddEditableTextCell::layoutSubviews() { - m_editableCell.setFrame(bounds()); +void EvenOddEditableTextCell::layoutSubviews(bool force) { + m_editableCell.setFrame(bounds(), force); } void EvenOddEditableTextCell::didBecomeFirstResponder() { diff --git a/escher/src/even_odd_expression_cell.cpp b/escher/src/even_odd_expression_cell.cpp index 8709cc829..0ceaeb51c 100644 --- a/escher/src/even_odd_expression_cell.cpp +++ b/escher/src/even_odd_expression_cell.cpp @@ -65,6 +65,6 @@ View * EvenOddExpressionCell::subviewAtIndex(int index) { return &m_expressionView; } -void EvenOddExpressionCell::layoutSubviews() { - m_expressionView.setFrame(KDRect(m_leftMargin, 0, bounds().width() - m_leftMargin - m_rightMargin, bounds().height())); +void EvenOddExpressionCell::layoutSubviews(bool force) { + m_expressionView.setFrame(KDRect(m_leftMargin, 0, bounds().width() - m_leftMargin - m_rightMargin, bounds().height()), force); } diff --git a/escher/src/even_odd_message_text_cell.cpp b/escher/src/even_odd_message_text_cell.cpp index 359a8b38b..873ad9285 100644 --- a/escher/src/even_odd_message_text_cell.cpp +++ b/escher/src/even_odd_message_text_cell.cpp @@ -35,7 +35,7 @@ View * EvenOddMessageTextCell::subviewAtIndex(int index) { return &m_messageTextView; } -void EvenOddMessageTextCell::layoutSubviews() { +void EvenOddMessageTextCell::layoutSubviews(bool force) { KDRect boundsThis = bounds(); - m_messageTextView.setFrame(KDRect(k_horizontalMargin, 0, boundsThis.width() - 2*k_horizontalMargin, boundsThis.height())); + m_messageTextView.setFrame(KDRect(k_horizontalMargin, 0, boundsThis.width() - 2*k_horizontalMargin, boundsThis.height()), force); } diff --git a/escher/src/expression_field.cpp b/escher/src/expression_field.cpp index 9c6f71d80..967ac940a 100644 --- a/escher/src/expression_field.cpp +++ b/escher/src/expression_field.cpp @@ -8,6 +8,7 @@ static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { retur ExpressionField::ExpressionField(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, TextFieldDelegate * textFieldDelegate, LayoutFieldDelegate * layoutFieldDelegate) : Responder(parentResponder), View(), + m_inputViewMemoizedHeight(0), m_textField(parentResponder, nullptr, k_textFieldBufferSize, k_textFieldBufferSize, inputEventHandlerDelegate, textFieldDelegate, KDFont::LargeFont, 0.0f, 0.5f, Palette::PrimaryText, Palette::ExpressionInputBackground), m_layoutField(parentResponder, inputEventHandlerDelegate, layoutFieldDelegate) { @@ -39,7 +40,7 @@ bool ExpressionField::isEditing() const { const char * ExpressionField::text() { if (!editionIsInTextField()) { - m_layoutField.layout().serializeParsedExpression(m_textField.draftTextBuffer(), k_textFieldBufferSize); + m_layoutField.layout().serializeParsedExpression(m_textField.draftTextBuffer(), k_textFieldBufferSize, m_layoutField.context()); } return m_textField.draftTextBuffer(); } @@ -62,20 +63,15 @@ View * ExpressionField::subviewAtIndex(int index) { return &m_layoutField; } -void ExpressionField::layoutSubviews() { +void ExpressionField::layoutSubviews(bool force) { KDRect inputViewFrame(0, k_separatorThickness, bounds().width(), bounds().height() - k_separatorThickness); if (editionIsInTextField()) { - m_textField.setFrame(inputViewFrame); - m_layoutField.setFrame(KDRectZero); + m_textField.setFrame(inputViewFrame, force); + m_layoutField.setFrame(KDRectZero, force); return; } - m_layoutField.setFrame(inputViewFrame); - m_textField.setFrame(KDRectZero); -} - -void ExpressionField::reload() { - layoutSubviews(); - markRectAsDirty(bounds()); + m_layoutField.setFrame(inputViewFrame, force); + m_textField.setFrame(KDRectZero, force); } void ExpressionField::drawRect(KDContext * ctx, KDRect rect) const { @@ -87,6 +83,10 @@ bool ExpressionField::handleEvent(Ion::Events::Event event) { return editionIsInTextField() ? m_textField.handleEvent(event) : m_layoutField.handleEvent(event); } +void ExpressionField::didBecomeFirstResponder() { + m_inputViewMemoizedHeight = inputViewHeight(); +} + KDSize ExpressionField::minimalSizeForOptimalDisplay() const { return KDSize(0, inputViewHeight()); } @@ -99,8 +99,11 @@ bool ExpressionField::isEmpty() const { return editionIsInTextField() ? (m_textField.draftTextLength() == 0) : !m_layoutField.hasText(); } -bool ExpressionField::heightIsMaximal() const { - return inputViewHeight() == k_separatorThickness + maximalHeight(); +bool ExpressionField::inputViewHeightDidChange() { + KDCoordinate newHeight = inputViewHeight(); + bool didChange = m_inputViewMemoizedHeight != newHeight; + m_inputViewMemoizedHeight = newHeight; + return didChange; } bool ExpressionField::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) { @@ -113,11 +116,7 @@ bool ExpressionField::handleEventWithText(const char * text, bool indentation, b KDCoordinate ExpressionField::inputViewHeight() const { return k_separatorThickness - + (editionIsInTextField() ? k_textFieldHeight : - minCoordinate(maximalHeight(), - maxCoordinate(k_textFieldHeight, m_layoutField.minimalSizeForOptimalDisplay().height()))); -} - -KDCoordinate ExpressionField::maximalHeight() const { - return 0.6*Ion::Display::Height; + + (editionIsInTextField() ? k_minimalHeight : + minCoordinate(k_maximalHeight, + maxCoordinate(k_minimalHeight, m_layoutField.minimalSizeForOptimalDisplay().height()))); } diff --git a/escher/src/expression_table_cell.cpp b/escher/src/expression_table_cell.cpp index 33c9c4842..83c3c4408 100644 --- a/escher/src/expression_table_cell.cpp +++ b/escher/src/expression_table_cell.cpp @@ -1,10 +1,12 @@ #include +#include #include #include -ExpressionTableCell::ExpressionTableCell(Layout layout) : +ExpressionTableCell::ExpressionTableCell(Responder * parentResponder, Layout layout) : + Responder(parentResponder), TableCell(layout), - m_labelExpressionView(0.0f, 0.5f, Palette::PrimaryText, Palette::ListCellBackground) + m_labelExpressionView(this, k_horizontalMargin, 0, 0.0f, 0.5f, Palette::PrimaryText, Palette::ListCellBackground) { } @@ -24,3 +26,7 @@ void ExpressionTableCell::setLayout(Poincare::Layout layout) { layoutSubviews(); } } + +void ExpressionTableCell::didBecomeFirstResponder() { + Container::activeApp()->setFirstResponder(&m_labelExpressionView); +} diff --git a/escher/src/expression_table_cell_with_expression.cpp b/escher/src/expression_table_cell_with_expression.cpp index cb3ac3f86..7d4b422f1 100644 --- a/escher/src/expression_table_cell_with_expression.cpp +++ b/escher/src/expression_table_cell_with_expression.cpp @@ -1,10 +1,11 @@ #include +#include #include #include -ExpressionTableCellWithExpression::ExpressionTableCellWithExpression() : - ExpressionTableCell(Layout::Horizontal), - m_accessoryExpressionView(1.0f, 0.5f, Palette::SecondaryText, Palette::ListCellBackground) +ExpressionTableCellWithExpression::ExpressionTableCellWithExpression(Responder * parentResponder) : + ExpressionTableCell(parentResponder, Layout::HorizontalLeftOverlap), + m_accessoryExpressionView(this, k_horizontalMargin, 0, 1.0f, 0.5f, Palette::SecondaryText, Palette::ListCellBackground) {} View * ExpressionTableCellWithExpression::accessoryView() const { @@ -21,3 +22,7 @@ void ExpressionTableCellWithExpression::setAccessoryLayout(Poincare::Layout l) { m_accessoryExpressionView.setLayout(l); layoutSubviews(); } + +void ExpressionTableCellWithExpression::didBecomeFirstResponder() { + Container::activeApp()->setFirstResponder(&m_accessoryExpressionView); +} diff --git a/escher/src/expression_table_cell_with_pointer.cpp b/escher/src/expression_table_cell_with_pointer.cpp index f29e9ef9d..19253a132 100644 --- a/escher/src/expression_table_cell_with_pointer.cpp +++ b/escher/src/expression_table_cell_with_pointer.cpp @@ -2,11 +2,11 @@ #include #include -ExpressionTableCellWithPointer::ExpressionTableCellWithPointer(I18n::Message accessoryMessage, Layout layout) : - ExpressionTableCell(layout), +ExpressionTableCellWithPointer::ExpressionTableCellWithPointer(Responder * parentResponder, I18n::Message accessoryMessage, Layout layout) : + ExpressionTableCell(parentResponder, layout), m_accessoryView(KDFont::SmallFont, accessoryMessage, 0.0f, 0.5f, Palette::SecondaryText, Palette::ListCellBackground) { - if (layout == Layout::Horizontal) { + if (layout != Layout::Vertical) { m_accessoryView.setAlignment(1.0f, 0.5f); } } diff --git a/escher/src/expression_view.cpp b/escher/src/expression_view.cpp index f44d9b5b9..dc713c2fa 100644 --- a/escher/src/expression_view.cpp +++ b/escher/src/expression_view.cpp @@ -1,15 +1,19 @@ #include +#include + using namespace Poincare; static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { return x > y ? x : y; } ExpressionView::ExpressionView(float horizontalAlignment, float verticalAlignment, - KDColor textColor, KDColor backgroundColor) : + KDColor textColor, KDColor backgroundColor, Poincare::Layout * selectionStart, Poincare::Layout * selectionEnd ) : m_layout(), - m_horizontalAlignment(horizontalAlignment), - m_verticalAlignment(verticalAlignment), m_textColor(textColor), m_backgroundColor(backgroundColor), + m_selectionStart(selectionStart), + m_selectionEnd(selectionEnd), + m_horizontalAlignment(horizontalAlignment), + m_verticalAlignment(verticalAlignment), m_horizontalMargin(0) { } @@ -71,6 +75,6 @@ KDPoint ExpressionView::absoluteDrawingOrigin() const { void ExpressionView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(rect, m_backgroundColor); if (!m_layout.isUninitialized()) { - m_layout.draw(ctx, drawingOrigin(), m_textColor, m_backgroundColor); + m_layout.draw(ctx, drawingOrigin(), m_textColor, m_backgroundColor, m_selectionStart, m_selectionEnd, Palette::Select); } } diff --git a/escher/src/input_view_controller.cpp b/escher/src/input_view_controller.cpp index 766ad58ae..2947054a7 100644 --- a/escher/src/input_view_controller.cpp +++ b/escher/src/input_view_controller.cpp @@ -20,8 +20,7 @@ InputViewController::InputViewController(Responder * parentResponder, ViewContro m_failureAction(Invocation(nullptr, nullptr)), m_inputEventHandlerDelegate(inputEventHandlerDelegate), m_textFieldDelegate(textFieldDelegate), - m_layoutFieldDelegate(layoutFieldDelegate), - m_inputViewHeightIsMaximal(false) + m_layoutFieldDelegate(layoutFieldDelegate) { } @@ -86,12 +85,17 @@ bool InputViewController::layoutFieldDidAbortEditing(LayoutField * layoutField) } void InputViewController::layoutFieldDidChangeSize(LayoutField * layoutField) { - /* Reload the view only if the ExpressionField height actually changes, i.e. - * not if the height is already maximal and stays maximal. */ - bool newInputViewHeightIsMaximal = m_expressionFieldController.expressionField()->heightIsMaximal(); - if (!m_inputViewHeightIsMaximal || !newInputViewHeightIsMaximal) { - m_inputViewHeightIsMaximal = newInputViewHeightIsMaximal; + if (m_expressionFieldController.expressionField()->inputViewHeightDidChange()) { + /* Reload the whole view only if the ExpressionField's height did actually + * change. */ reloadModalViewController(); + } else { + /* The input view is already at maximal size so we do not need to relayout + * the view underneath, but the view inside the input view might still need + * to be relayouted. + * We force the relayout because the frame stays the same but we need to + * propagate a relayout to the content of the field scroll view. */ + m_expressionFieldController.expressionField()->layoutSubviews(true); } } diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index bce750824..000e49808 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -12,8 +12,10 @@ static inline KDCoordinate minCoordinate(KDCoordinate x, KDCoordinate y) { retur LayoutField::ContentView::ContentView() : m_cursor(), - m_expressionView(0.0f, 0.5f, Palette::PrimaryText, Palette::BackgroundHard), + m_expressionView(0.0f, 0.5f, Palette::PrimaryText, Palette::BackgroundHard, &m_selectionStart, &m_selectionEnd), m_cursorView(), + m_selectionStart(), + m_selectionEnd(), m_isEditing(false) { clearLayout(); @@ -47,28 +49,202 @@ KDSize LayoutField::ContentView::minimalSizeForOptimalDisplay() const { return KDSize(evSize.width() + Poincare::LayoutCursor::k_cursorWidth, evSize.height()); } +bool IsBefore(Layout& l1, Layout& l2, bool strict) { + char * node1 = reinterpret_cast(l1.node()); + char * node2 = reinterpret_cast(l2.node()); + return strict ? (node1 < node2) : (node1 <= node2); +} + +void LayoutField::ContentView::addSelection(Layout addedLayout) { + KDRect rectBefore = selectionRect(); + if (selectionIsEmpty()) { + /* + * ---------- -> +++ is the previous previous selection + * ( ) -> added selection + * ---+++++-- -> next selection + * */ + m_selectionStart = addedLayout; + m_selectionEnd = addedLayout; + } else if (IsBefore(m_selectionEnd, addedLayout, true)) { + /* + * +++------- -> +++ is the previous previous selection + * ( ) -> added selection + * ++++++++++ -> next selection + * */ + if (addedLayout.parent() == m_selectionStart) { + /* The previous selected layout is an horizontal layout and we remove one + * of its children. */ + assert(m_selectionStart == m_selectionEnd + && m_selectionStart.type() == LayoutNode::Type::HorizontalLayout); + m_selectionStart = m_selectionStart.childAtIndex(0); + m_selectionEnd = m_selectionEnd.childAtIndex(m_selectionEnd.numberOfChildren() - 1); + addSelection(addedLayout); + return; + } + /* The previous selected layouts and the new added selection are all + * children of a same horizontal layout. */ + assert(m_selectionStart.parent() == m_selectionEnd.parent() + && m_selectionStart.parent() == addedLayout.parent() + && m_selectionStart.parent().type() == LayoutNode::Type::HorizontalLayout); + m_selectionEnd = addedLayout; + } else if (IsBefore(addedLayout, m_selectionStart, true)) { + /* + * -------+++ -> +++ is the previous previous selection + * ( ) -> added selection + * ++++++++++ -> next selection + * */ + if (addedLayout.type() == LayoutNode::Type::HorizontalLayout + && m_selectionStart.parent() == addedLayout) + { + /* The selection was from the first to the last child of an horizontal + * layout, we add this horizontal layout -> the selection is now empty. */ + assert(m_selectionEnd.parent() == addedLayout); + assert(addedLayout.childAtIndex(0) == m_selectionStart); + assert(addedLayout.childAtIndex(addedLayout.numberOfChildren() - 1) == m_selectionEnd); + m_selectionStart = Layout(); + m_selectionEnd = Layout(); + } else { + if (m_selectionStart.hasAncestor(addedLayout, true)) { + // We are selecting a layout containing the current selection + m_selectionEnd = addedLayout; + } + m_selectionStart = addedLayout; + } + } else { + bool sameEnd = m_selectionEnd == addedLayout; + bool sameStart = m_selectionStart == addedLayout; + if (sameStart && sameEnd) { + /* + * -----+++++ -> +++ is the previous previous selection + * ( ) -> added selection + * ---------- -> next selection + * */ + m_selectionStart = Layout(); + m_selectionEnd = Layout(); + } else { + assert(sameStart || sameEnd); + /* + * ++++++++++ -> +++ is the previous previous selection + * ( ) -> added selection if sameStart + * ( ) -> added selection if sameEnd + * +++++----- -> next selection + * The previous selected layouts and the new "added" selection are all + * children of a same horizontal layout. */ + Layout horizontalParent = m_selectionStart.parent(); + assert(!horizontalParent.isUninitialized() + && horizontalParent == m_selectionEnd.parent() + && horizontalParent == addedLayout.parent() + && horizontalParent.type() == LayoutNode::Type::HorizontalLayout + && ((sameEnd && horizontalParent.indexOfChild(m_selectionEnd) > 0) + || (sameStart && horizontalParent.indexOfChild(m_selectionStart) < horizontalParent.numberOfChildren()))); + if (sameStart) { + m_selectionStart = horizontalParent.childAtIndex(horizontalParent.indexOfChild(m_selectionStart) + 1); + } else { + m_selectionEnd = horizontalParent.childAtIndex(horizontalParent.indexOfChild(m_selectionEnd) - 1); + } + } + } + + KDRect rectAfter = selectionRect(); + // We need to update the background color for selected/unselected layouts + markRectAsDirty(rectBefore.unionedWith(rectAfter)); +} + +bool LayoutField::ContentView::resetSelection() { + if (selectionIsEmpty()) { + return false; + } + m_selectionStart = Layout(); + m_selectionEnd = Layout(); + return true; +} + +void LayoutField::ContentView::copySelection(Context * context) { + if (selectionIsEmpty()) { + return; + } + constexpr int bufferSize = TextField::maxBufferSize(); + char buffer[bufferSize]; + + if (m_selectionStart == m_selectionEnd) { + m_selectionStart.serializeParsedExpression(buffer, bufferSize, context); + if (buffer[0] == 0) { + int offset = 0; + if (m_selectionStart.type() == LayoutNode::Type::VerticalOffsetLayout) { + assert(bufferSize > 1); + buffer[offset++] = UCodePointEmpty; + } + m_selectionStart.serializeForParsing(buffer + offset, bufferSize - offset); + } + } else { + Layout selectionParent = m_selectionStart.parent(); + assert(!selectionParent.isUninitialized()); + assert(selectionParent.type() == LayoutNode::Type::HorizontalLayout); + int firstIndex = selectionParent.indexOfChild(m_selectionStart); + int lastIndex = selectionParent.indexOfChild(m_selectionEnd); + static_cast(selectionParent).serializeChildren(firstIndex, lastIndex, buffer, bufferSize); + } + if (buffer[0] != 0) { + Clipboard::sharedClipboard()->store(buffer); + } +} + +bool LayoutField::ContentView::selectionIsEmpty() const { + assert(!m_selectionStart.isUninitialized() || m_selectionEnd.isUninitialized()); + assert(!m_selectionEnd.isUninitialized() || m_selectionStart.isUninitialized()); + return m_selectionStart.isUninitialized(); +} + +void LayoutField::ContentView::deleteSelection() { + assert(!selectionIsEmpty()); + Layout selectionParent = m_selectionStart.parent(); + + /* If the selected layout is the upmost layout, it must be an horizontal + * layout. Empty it. */ + if (selectionParent.isUninitialized()) { + assert(m_selectionStart == m_selectionEnd); + assert(m_selectionStart.type() == LayoutNode::Type::HorizontalLayout); + clearLayout(); + } else { + assert(selectionParent == m_selectionEnd.parent()); + // Remove the selected children or replace it with an empty layout. + if (selectionParent.type() == LayoutNode::Type::HorizontalLayout) { + int firstIndex = selectionParent.indexOfChild(m_selectionStart); + int lastIndex = m_selectionStart == m_selectionEnd ? firstIndex : selectionParent.indexOfChild(m_selectionEnd); + for (int i = lastIndex; i >= firstIndex; i--) { + static_cast(selectionParent).removeChildAtIndex(i, &m_cursor, false); + } + } else { + // Only one child can be selected + assert(m_selectionStart == m_selectionEnd); + selectionParent.replaceChildWithEmpty(m_selectionStart, &m_cursor); + } + } + resetSelection(); +} + View * LayoutField::ContentView::subviewAtIndex(int index) { - assert(index >= 0 && index < 2); + assert(0 <= index && index < numberOfSubviews()); View * m_views[] = {&m_expressionView, &m_cursorView}; return m_views[index]; } -void LayoutField::ContentView::layoutSubviews() { - m_expressionView.setFrame(bounds()); - layoutCursorSubview(); +void LayoutField::ContentView::layoutSubviews(bool force) { + m_expressionView.setFrame(bounds(), force); + layoutCursorSubview(force); } -void LayoutField::ContentView::layoutCursorSubview() { +void LayoutField::ContentView::layoutCursorSubview(bool force) { if (!m_isEditing) { - m_cursorView.setFrame(KDRectZero); + m_cursorView.setFrame(KDRectZero, force); return; } KDPoint expressionViewOrigin = m_expressionView.absoluteDrawingOrigin(); - Layout pointedLayoutR = m_cursor.layoutReference(); + Layout pointedLayoutR = m_cursor.layout(); LayoutCursor::Position cursorPosition = m_cursor.position(); LayoutCursor eqCursor = pointedLayoutR.equivalentCursor(&m_cursor); - if (eqCursor.isDefined() && pointedLayoutR.hasChild(eqCursor.layoutReference())) { - pointedLayoutR = eqCursor.layoutReference(); + if (eqCursor.isDefined() && pointedLayoutR.hasChild(eqCursor.layout())) { + pointedLayoutR = eqCursor.layout(); cursorPosition = eqCursor.position(); } KDPoint cursoredExpressionViewOrigin = pointedLayoutR.absoluteOrigin(); @@ -76,8 +252,28 @@ void LayoutField::ContentView::layoutCursorSubview() { if (cursorPosition == LayoutCursor::Position::Right) { cursorX += pointedLayoutR.layoutSize().width(); } - KDPoint cursorTopLeftPosition(cursorX, expressionViewOrigin.y() + cursoredExpressionViewOrigin.y() + pointedLayoutR.baseline() - m_cursor.baseline()); - m_cursorView.setFrame(KDRect(cursorTopLeftPosition, LayoutCursor::k_cursorWidth, m_cursor.cursorHeight())); + if (selectionIsEmpty()) { + KDPoint cursorTopLeftPosition(cursorX, expressionViewOrigin.y() + cursoredExpressionViewOrigin.y() + pointedLayoutR.baseline() - m_cursor.baselineWithoutSelection()); + m_cursorView.setFrame(KDRect(cursorTopLeftPosition, LayoutCursor::k_cursorWidth, m_cursor.cursorHeightWithoutSelection()), force); + } else { + KDRect cursorRect = selectionRect(); + KDPoint cursorTopLeftPosition(cursorX, expressionViewOrigin.y() + cursorRect.y()); + m_cursorView.setFrame(KDRect(cursorTopLeftPosition, LayoutCursor::k_cursorWidth, cursorRect.height()), force); + } +} + +KDRect LayoutField::ContentView::selectionRect() const { + if (selectionIsEmpty()) { + return KDRectZero; + } + if (m_selectionStart == m_selectionEnd) { + return KDRect(m_selectionStart.absoluteOrigin(), m_selectionStart.layoutSize()); + } + Layout selectionParent = m_selectionStart.parent(); + assert(m_selectionEnd.parent() == selectionParent); + assert(selectionParent.type() == LayoutNode::Type::HorizontalLayout); + KDRect selectionRectInParent = static_cast(selectionParent).relativeSelectionRect(&m_selectionStart, &m_selectionEnd); + return selectionRectInParent.translatedBy(selectionParent.absoluteOrigin()); } void LayoutField::setEditing(bool isEditing) { @@ -87,14 +283,23 @@ void LayoutField::setEditing(bool isEditing) { } } +Context * LayoutField::context() const { + return (m_delegate != nullptr) ? m_delegate->context() : nullptr; +} + CodePoint LayoutField::XNTCodePoint(CodePoint defaultXNTCodePoint) { - CodePoint xnt = m_contentView.cursor()->layoutReference().XNTCodePoint(); + CodePoint xnt = m_contentView.cursor()->layout().XNTCodePoint(); if (xnt != UCodePointNull) { return xnt; } return defaultXNTCodePoint; } +void LayoutField::putCursorRightOfLayout() { + m_contentView.cursor()->layout().removeGreySquaresFromAllMatrixAncestors(); + m_contentView.setCursor(LayoutCursor(m_contentView.expressionView()->layout(), LayoutCursor::Position::Right)); +} + void LayoutField::reload(KDSize previousSize) { layout().invalidAllSizesPositionsAndBaselines(); KDSize newSize = minimalSizeForOptimalDisplay(); @@ -111,6 +316,12 @@ bool LayoutField::handleEventWithText(const char * text, bool indentation, bool * - the result of a key pressed, such as "," or "cos(•)" * - the text added after a toolbox selection * - the result of a copy-paste. */ + + // Delete the selected layouts if needed + if (!m_contentView.selectionIsEmpty()) { + deleteSelection(); + } + if (text[0] == 0) { // The text is empty return true; @@ -142,11 +353,11 @@ bool LayoutField::handleEventWithText(const char * text, bool indentation, bool } else if((strcmp(text, Ion::Events::Multiplication.text())) == 0){ m_contentView.cursor()->addMultiplicationPointLayout(); } else { - Expression resultExpression = Expression::Parse(text); + Expression resultExpression = Expression::Parse(text, nullptr); if (resultExpression.isUninitialized()) { // The text is not parsable (for instance, ",") and is added char by char. KDSize previousLayoutSize = minimalSizeForOptimalDisplay(); - m_contentView.cursor()->insertText(text); + m_contentView.cursor()->insertText(text, forceCursorRightOfText); reload(previousLayoutSize); return true; } @@ -160,6 +371,14 @@ bool LayoutField::handleEventWithText(const char * text, bool indentation, bool return true; } +bool LayoutField::shouldFinishEditing(Ion::Events::Event event) { + if (m_delegate->layoutFieldShouldFinishEditing(this, event)) { + resetSelection(); + return true; + } + return false; +} + bool LayoutField::handleEvent(Ion::Events::Event event) { bool didHandleEvent = false; KDSize previousSize = minimalSizeForOptimalDisplay(); @@ -171,6 +390,23 @@ bool LayoutField::handleEvent(Ion::Events::Event event) { } shouldRecomputeLayout = shouldRecomputeLayout || moveEventChangedLayout; didHandleEvent = true; + } else if (privateHandleSelectionEvent(event, &shouldRecomputeLayout)) { + didHandleEvent = true; + // Handle matrices + if (!m_contentView.selectionIsEmpty()) { + bool removedSquares = false; + Layout * selectStart = m_contentView.selectionStart(); + Layout * selectEnd = m_contentView.selectionEnd(); + if (*selectStart != *selectEnd) { + Layout p = selectStart->parent(); + assert(p == selectEnd->parent()); + assert(p.type() == LayoutNode::Type::HorizontalLayout); + removedSquares = p.removeGreySquaresFromAllMatrixChildren(); + } else { + removedSquares = selectStart->removeGreySquaresFromAllMatrixChildren(); + } + shouldRecomputeLayout = m_contentView.cursor()->layout().removeGreySquaresFromAllMatrixChildren() || removedSquares || shouldRecomputeLayout; + } } else if (privateHandleEvent(event)) { shouldRecomputeLayout = true; didHandleEvent = true; @@ -191,6 +427,10 @@ bool LayoutField::handleEvent(Ion::Events::Event event) { return true; } +void LayoutField::deleteSelection() { + m_contentView.deleteSelection(); +} + bool LayoutField::privateHandleEvent(Ion::Events::Event event) { if (m_delegate && m_delegate->layoutFieldDidReceiveEvent(this, event)) { return true; @@ -201,11 +441,12 @@ bool LayoutField::privateHandleEvent(Ion::Events::Event event) { } return true; } - if (isEditing() && m_delegate->layoutFieldShouldFinishEditing(this, event)) { //TODO use class method? + if (isEditing() && m_delegate && m_delegate->layoutFieldShouldFinishEditing(this, event)) { //TODO use class method? setEditing(false); if (m_delegate->layoutFieldDidFinishEditing(this, layout(), event)) { // Reinit layout for next use clearLayout(); + resetSelection(); } else { setEditing(true); } @@ -225,6 +466,7 @@ bool LayoutField::privateHandleEvent(Ion::Events::Event event) { } if (event == Ion::Events::Back && isEditing()) { clearLayout(); + resetSelection(); setEditing(false); m_delegate->layoutFieldDidAbortEditing(this); return true; @@ -243,40 +485,51 @@ bool LayoutField::privateHandleEvent(Ion::Events::Event event) { handleEventWithText(Clipboard::sharedClipboard()->storedText(), false, true); } else { assert(event == Ion::Events::Backspace); - m_contentView.cursor()->performBackspace(); + if (!m_contentView.selectionIsEmpty()) { + deleteSelection(); + } else { + m_contentView.cursor()->performBackspace(); + } } return true; } + if (event == Ion::Events::Copy && isEditing()) { + m_contentView.copySelection(context()); + return true; + } if (event == Ion::Events::Clear && isEditing()) { clearLayout(); + resetSelection(); return true; } return false; } +static inline bool IsSimpleMoveEvent(Ion::Events::Event event) { + return event == Ion::Events::Left + || event == Ion::Events::Right + || event == Ion::Events::Up + || event == Ion::Events::Down; +} + bool LayoutField::privateHandleMoveEvent(Ion::Events::Event event, bool * shouldRecomputeLayout) { + if (!IsSimpleMoveEvent(event)) { + return false; + } + if (resetSelection()) { + *shouldRecomputeLayout = true; + return true; + } LayoutCursor result; if (event == Ion::Events::Left) { - result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::MoveDirection::Left, shouldRecomputeLayout); + result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::Direction::Left, shouldRecomputeLayout); } else if (event == Ion::Events::Right) { - result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::MoveDirection::Right, shouldRecomputeLayout); + result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::Direction::Right, shouldRecomputeLayout); } else if (event == Ion::Events::Up) { - result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::MoveDirection::Up, shouldRecomputeLayout); - } else if (event == Ion::Events::Down) { - result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::MoveDirection::Down, shouldRecomputeLayout); - } else if (event == Ion::Events::ShiftLeft) { - *shouldRecomputeLayout = true; - if (m_contentView.cursor()->layoutReference().removeGreySquaresFromAllMatrixAncestors()) { - *shouldRecomputeLayout = true; - } - result.setLayout(layout()); - result.setPosition(LayoutCursor::Position::Left); - } else if (event == Ion::Events::ShiftRight) { - if (m_contentView.cursor()->layoutReference().removeGreySquaresFromAllMatrixAncestors()) { - *shouldRecomputeLayout = true; - } - result.setLayout(layout()); - result.setPosition(LayoutCursor::Position::Right); + result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::Direction::Up, shouldRecomputeLayout); + } else { + assert(event == Ion::Events::Down); + result = m_contentView.cursor()->cursorAtDirection(LayoutCursor::Direction::Down, shouldRecomputeLayout); } if (result.isDefined()) { m_contentView.setCursor(result); @@ -285,6 +538,29 @@ bool LayoutField::privateHandleMoveEvent(Ion::Events::Event event, bool * should return false; } +bool eventIsSelection(Ion::Events::Event event) { + return event == Ion::Events::ShiftLeft || event == Ion::Events::ShiftRight || event == Ion::Events::ShiftUp || event == Ion::Events::ShiftDown; +} + +bool LayoutField::privateHandleSelectionEvent(Ion::Events::Event event, bool * shouldRecomputeLayout) { + if (!eventIsSelection(event)) { + return false; + } + Layout addedSelection; + LayoutCursor::Direction direction = event == Ion::Events::ShiftLeft ? LayoutCursor::Direction::Left : + (event == Ion::Events::ShiftRight ? LayoutCursor::Direction::Right : + (event == Ion::Events::ShiftUp ? LayoutCursor::Direction::Up : + LayoutCursor::Direction::Down)); + LayoutCursor result = m_contentView.cursor()->selectAtDirection(direction, shouldRecomputeLayout, &addedSelection); + if (addedSelection.isUninitialized()) { + return false; + } + m_contentView.addSelection(addedSelection); + assert(result.isDefined()); + m_contentView.setCursor(result); + return true; +} + void LayoutField::scrollRightOfLayout(Layout layoutR) { KDRect layoutRect(layoutR.absoluteOrigin().translatedBy(m_contentView.expressionView()->drawingOrigin()), layoutR.layoutSize()); scrollToBaselinedRect(layoutRect, layoutR.baseline()); @@ -374,7 +650,7 @@ void LayoutField::insertLayoutAtCursor(Layout layoutR, Poincare::Expression corr } // Handle matrices - cursor->layoutReference().addGreySquaresToAllMatrixAncestors(); + cursor->layout().addGreySquaresToAllMatrixAncestors(); // Handle empty layouts cursor->hideEmptyLayoutIfNeeded(); diff --git a/escher/src/message_table_cell_with_editable_text.cpp b/escher/src/message_table_cell_with_editable_text.cpp index 63bd44865..b0ff43b91 100644 --- a/escher/src/message_table_cell_with_editable_text.cpp +++ b/escher/src/message_table_cell_with_editable_text.cpp @@ -46,12 +46,17 @@ void MessageTableCellWithEditableText::setTextColor(KDColor color) { MessageTableCell::setTextColor(color); } -void MessageTableCellWithEditableText::layoutSubviews() { - TableCell::layoutSubviews(); +void MessageTableCellWithEditableText::layoutSubviews(bool force) { + TableCell::layoutSubviews(force); KDSize textFieldSize = m_textField.minimalSizeForOptimalDisplay(); KDSize labelSize = labelView()->minimalSizeForOptimalDisplay(); /* Handle textfield that has no defined width (as their width evolves with * the length of edited text */ - textFieldSize = KDSize(bounds().width() - 2*k_separatorThickness - labelSize.width()-2*k_labelMargin-k_accessoryMargin, textFieldSize.height()); - m_textField.setFrame(KDRect(bounds().width() - textFieldSize.width() - k_separatorThickness-k_accessoryMargin, (bounds().height()-textFieldSize.height()-k_accessoryMargin)/2, textFieldSize.width(), textFieldSize.height()+k_accessoryMargin)); + textFieldSize = KDSize(bounds().width() - 2*k_separatorThickness - labelSize.width()-2*labelMargin()-k_horizontalMargin, textFieldSize.height()); + m_textField.setFrame(KDRect( + bounds().width() - textFieldSize.width() - k_separatorThickness-k_horizontalMargin, + (bounds().height()-textFieldSize.height()-k_horizontalMargin)/2, + textFieldSize.width(), + textFieldSize.height()+k_horizontalMargin), + force); } diff --git a/escher/src/message_tree.cpp b/escher/src/message_tree.cpp deleted file mode 100644 index 59bd96901..000000000 --- a/escher/src/message_tree.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include - -int MessageTree::numberOfChildren() const { - return m_numberOfChildren; -} - -I18n::Message MessageTree::label() const { - return m_label; -} - -bool MessageTree::isNull() const { - return (m_label == (I18n::Message)0); -} diff --git a/escher/src/modal_view_controller.cpp b/escher/src/modal_view_controller.cpp index 699d75436..6fbed78b1 100644 --- a/escher/src/modal_view_controller.cpp +++ b/escher/src/modal_view_controller.cpp @@ -54,15 +54,15 @@ KDRect ModalViewController::ContentView::modalViewFrame() const { return modalViewFrame; } -void ModalViewController::ContentView::layoutSubviews() { +void ModalViewController::ContentView::layoutSubviews(bool force) { assert(m_regularView != nullptr); - m_regularView->setFrame(bounds()); + m_regularView->setFrame(bounds(), force); if (m_isDisplayingModal) { assert(m_currentModalView != nullptr); - m_currentModalView->setFrame(modalViewFrame()); + m_currentModalView->setFrame(modalViewFrame(), force); } else { if (m_currentModalView) { - m_currentModalView->setFrame(KDRectZero); + m_currentModalView->setFrame(KDRectZero, force); } } } @@ -80,9 +80,11 @@ void ModalViewController::ContentView::presentModalView(View * modalView, float layoutSubviews(); } -void ModalViewController::ContentView::dismissModalView() { +void ModalViewController::ContentView::dismissModalView(bool willExitApp) { m_isDisplayingModal = false; - layoutSubviews(); + if (!willExitApp) { + layoutSubviews(); + } m_currentModalView->resetSuperview(); m_currentModalView = nullptr; } @@ -128,10 +130,12 @@ void ModalViewController::reloadModalViewController() { m_contentView.layoutSubviews(); } -void ModalViewController::dismissModalViewController() { +void ModalViewController::dismissModalViewController(bool willExitApp) { m_currentModalViewController->viewDidDisappear(); - Container::activeApp()->setFirstResponder(m_previousResponder); - m_contentView.dismissModalView(); + if (!willExitApp) { + Container::activeApp()->setFirstResponder(m_previousResponder); + } + m_contentView.dismissModalView(willExitApp); m_currentModalViewController = nullptr; } diff --git a/escher/src/nested_menu_controller.cpp b/escher/src/nested_menu_controller.cpp index 0c5f014a2..821fd1769 100644 --- a/escher/src/nested_menu_controller.cpp +++ b/escher/src/nested_menu_controller.cpp @@ -78,8 +78,8 @@ View * NestedMenuController::ListController::view() { void NestedMenuController::ListController::didBecomeFirstResponder() { m_selectableTableView->reloadData(); - m_selectableTableView->selectCellAtLocation(0, m_firstSelectedRow); Container::activeApp()->setFirstResponder(m_selectableTableView); + m_selectableTableView->selectCellAtLocation(0, m_firstSelectedRow); } void NestedMenuController::ListController::setFirstSelectedRow(int firstSelectedRow) { diff --git a/escher/src/scroll_view.cpp b/escher/src/scroll_view.cpp index 13a0aa0ab..77a7a8855 100644 --- a/escher/src/scroll_view.cpp +++ b/escher/src/scroll_view.cpp @@ -93,20 +93,20 @@ KDRect ScrollView::visibleContentRect() { m_frame.height() - m_topMargin - m_bottomMargin); } -void ScrollView::layoutSubviews() { +void ScrollView::layoutSubviews(bool force) { KDRect r1 = KDRectZero; KDRect r2 = KDRectZero; - KDRect innerFrame = decorator()->layoutIndicators(minimalSizeForOptimalDisplay(), contentOffset(), bounds(), &r1, &r2); + KDRect innerFrame = decorator()->layoutIndicators(minimalSizeForOptimalDisplay(), contentOffset(), bounds(), &r1, &r2, force); if (!r1.isEmpty()) { markRectAsDirty(r1); } if (!r2.isEmpty()) { markRectAsDirty(r2); } - m_innerView.setFrame(innerFrame); + m_innerView.setFrame(innerFrame, force); KDPoint absoluteOffset = contentOffset().opposite().translatedBy(KDPoint(m_leftMargin - innerFrame.x(), m_topMargin - innerFrame.y())); KDRect contentFrame = KDRect(absoluteOffset, contentSize()); - m_contentView->setFrame(contentFrame); + m_contentView->setFrame(contentFrame, force); } void ScrollView::setContentOffset(KDPoint offset, bool forceRelayout) { @@ -136,7 +136,7 @@ View * ScrollView::BarDecorator::indicatorAtIndex(int index) { return &m_horizontalBar; } -KDRect ScrollView::BarDecorator::layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2) { +KDRect ScrollView::BarDecorator::layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) { bool hBarWasVisible = m_horizontalBar.visible(); bool hBarIsVisible = m_horizontalBar.update(content.width(), offset.x(), frame.width()); bool vBarWasVisible = m_verticalBar.visible(); @@ -152,13 +152,13 @@ KDRect ScrollView::BarDecorator::layoutIndicators(KDSize content, KDPoint offset /* If the two indicators are visible, we leave an empty rectangle in the right * bottom corner. Otherwise, the only indicator uses all the height/width. */ m_verticalBar.setFrame(KDRect( - frame.width() - vBarFrameBreadth, 0, - vBarFrameBreadth, frame.height() - hBarFrameBreadth - )); + frame.width() - vBarFrameBreadth, 0, + vBarFrameBreadth, frame.height() - hBarFrameBreadth), + force); m_horizontalBar.setFrame(KDRect( - 0, frame.height() - hBarFrameBreadth, - frame.width() - vBarFrameBreadth, hBarFrameBreadth - )); + 0, frame.height() - hBarFrameBreadth, + frame.width() - vBarFrameBreadth, hBarFrameBreadth), + force); return frame; } @@ -176,7 +176,7 @@ View * ScrollView::ArrowDecorator::indicatorAtIndex(int index) { } } -KDRect ScrollView::ArrowDecorator::layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2) { +KDRect ScrollView::ArrowDecorator::layoutIndicators(KDSize content, KDPoint offset, KDRect frame, KDRect * dirtyRect1, KDRect * dirtyRect2, bool force) { // There is no need to dirty the rects KDSize arrowSize = KDFont::LargeFont->glyphSize(); KDCoordinate topArrowFrameBreadth = arrowSize.height() * m_topArrow.update(0 < offset.y()); @@ -184,21 +184,21 @@ KDRect ScrollView::ArrowDecorator::layoutIndicators(KDSize content, KDPoint offs KDCoordinate bottomArrowFrameBreadth = arrowSize.height() * m_bottomArrow.update(offset.y() + frame.height() < content.height()); KDCoordinate leftArrowFrameBreadth = arrowSize.width() * m_leftArrow.update(0 < offset.x()); m_topArrow.setFrame(KDRect( - 0, 0, - frame.width(), topArrowFrameBreadth - )); + 0, 0, + frame.width(), topArrowFrameBreadth), + force); m_rightArrow.setFrame(KDRect( - frame.width() - rightArrowFrameBreadth, 0, - rightArrowFrameBreadth, frame.height() - )); + frame.width() - rightArrowFrameBreadth, 0, + rightArrowFrameBreadth, frame.height()), + force); m_bottomArrow.setFrame(KDRect( - 0, frame.height() - bottomArrowFrameBreadth, - frame.width(), bottomArrowFrameBreadth - )); + 0, frame.height() - bottomArrowFrameBreadth, + frame.width(), bottomArrowFrameBreadth), + force); m_leftArrow.setFrame(KDRect( - 0, 0, - leftArrowFrameBreadth, frame.height() - )); + 0, 0, + leftArrowFrameBreadth, frame.height()), + force); return KDRect( frame.x() + leftArrowFrameBreadth, frame.y() + topArrowFrameBreadth, diff --git a/apps/calculation/scrollable_expression_view.cpp b/escher/src/scrollable_expression_view.cpp similarity index 55% rename from apps/calculation/scrollable_expression_view.cpp rename to escher/src/scrollable_expression_view.cpp index fae79f5d6..41e74a8a8 100644 --- a/apps/calculation/scrollable_expression_view.cpp +++ b/escher/src/scrollable_expression_view.cpp @@ -1,27 +1,27 @@ -#include "scrollable_expression_view.h" +#include +#include +#include #include -using namespace Poincare; -namespace Calculation { - -ScrollableExpressionView::ScrollableExpressionView(Responder * parentResponder) : +ScrollableExpressionView::ScrollableExpressionView(Responder * parentResponder, KDCoordinate leftRightMargin, KDCoordinate topBottomMargin, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor backgroundColor) : ScrollableView(parentResponder, &m_expressionView, this), - m_expressionView() + m_expressionView(horizontalAlignment, verticalAlignment, textColor, backgroundColor) { setDecoratorType(ScrollView::Decorator::Type::Arrows); setMargins( - Metric::CommonSmallMargin, - Metric::CommonLargeMargin, - Metric::CommonSmallMargin, - Metric::CommonLargeMargin + topBottomMargin, + leftRightMargin, + topBottomMargin, + leftRightMargin ); + setBackgroundColor(backgroundColor); } Poincare::Layout ScrollableExpressionView::layout() const { return m_expressionView.layout(); } -void ScrollableExpressionView::setLayout(Layout layout) { +void ScrollableExpressionView::setLayout(Poincare::Layout layout) { m_expressionView.setLayout(layout); } @@ -33,5 +33,3 @@ void ScrollableExpressionView::setBackgroundColor(KDColor backgroundColor) { void ScrollableExpressionView::setExpressionBackgroundColor(KDColor backgroundColor) { m_expressionView.setBackgroundColor(backgroundColor); } - -} diff --git a/escher/src/selectable_table_view.cpp b/escher/src/selectable_table_view.cpp index 24169c068..bb07591f0 100644 --- a/escher/src/selectable_table_view.cpp +++ b/escher/src/selectable_table_view.cpp @@ -58,7 +58,9 @@ void SelectableTableView::didEnterResponderChain(Responder * previousFirstRespon } void SelectableTableView::willExitResponderChain(Responder * nextFirstResponder) { - unhighlightSelectedCell(); + if (nextFirstResponder != nullptr) { + unhighlightSelectedCell(); + } } void SelectableTableView::deselectTable(bool withinTemporarySelection) { @@ -149,7 +151,7 @@ bool SelectableTableView::handleEvent(Ion::Events::Event event) { if (!l.isUninitialized()) { constexpr int bufferSize = TextField::maxBufferSize(); char buffer[bufferSize]; - l.serializeParsedExpression(buffer, bufferSize); + l.serializeParsedExpression(buffer, bufferSize, m_delegate == nullptr ? nullptr : m_delegate->context()); Clipboard::sharedClipboard()->store(buffer); return true; } diff --git a/escher/src/selectable_table_view_delegate.cpp b/escher/src/selectable_table_view_delegate.cpp deleted file mode 100644 index 25da1557a..000000000 --- a/escher/src/selectable_table_view_delegate.cpp +++ /dev/null @@ -1,4 +0,0 @@ -#include - -void SelectableTableViewDelegate::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { -} diff --git a/escher/src/solid_text_area.cpp b/escher/src/solid_text_area.cpp deleted file mode 100644 index b115c9f99..000000000 --- a/escher/src/solid_text_area.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include - -static inline int minInt(int x, int y) { return x < y ? x : y; } - -void SolidTextArea::ContentView::clearRect(KDContext * ctx, KDRect rect) const { - ctx->fillRect(rect, m_backgroundColor); -} - -void SolidTextArea::ContentView::drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn) const { - drawStringAt( - ctx, - line, - fromColumn, - text + fromColumn, - minInt(length - fromColumn, toColumn - fromColumn), - m_textColor, - m_backgroundColor - ); -} diff --git a/escher/src/stack_view.cpp b/escher/src/stack_view.cpp index 97f1c783d..1ac9d43c3 100644 --- a/escher/src/stack_view.cpp +++ b/escher/src/stack_view.cpp @@ -30,13 +30,11 @@ void StackView::setNamedController(ViewController * controller) { } void StackView::drawRect(KDContext * ctx, KDRect rect) const { - const KDFont * font = KDFont::SmallFont; - KDCoordinate height = bounds().height(); - KDCoordinate width = bounds().width(); - ctx->fillRect(KDRect(0, 0, width, 1), m_separatorColor); - ctx->fillRect(KDRect(0, 1, width, height-2), m_backgroundColor); - ctx->fillRect(KDRect(0, height-1, width, 1), m_separatorColor); + KDRect b = bounds(); + drawBorderOfRect(ctx, b, m_separatorColor); + drawInnerRect(ctx, b, m_backgroundColor); // Write title + const KDFont * font = KDFont::SmallFont; KDSize textSize = font->stringSize(m_controller->title()); KDPoint origin((m_frame.width() - textSize.width())/2,(m_frame.height() - textSize.height())/2); ctx->drawString(m_controller->title(), origin, font, m_textColor, m_backgroundColor); diff --git a/escher/src/stack_view_controller.cpp b/escher/src/stack_view_controller.cpp index f19ee1261..8aa337d17 100644 --- a/escher/src/stack_view_controller.cpp +++ b/escher/src/stack_view_controller.cpp @@ -36,20 +36,20 @@ void StackViewController::ControllerView::popStack() { m_numberOfStacks--; } -void StackViewController::ControllerView::layoutSubviews() { +void StackViewController::ControllerView::layoutSubviews(bool force) { KDCoordinate width = m_frame.width(); if (m_displayStackHeaders) { for (int i=0; i 0 ? 1 : 0; - KDRect contentViewFrame = KDRect( 0, + KDRect contentViewFrame = KDRect(0, m_displayStackHeaders * (m_numberOfStacks * Metric::StackTitleHeight + separatorHeight), width, m_frame.height() - m_displayStackHeaders * m_numberOfStacks * Metric::StackTitleHeight); - m_contentView->setFrame(contentViewFrame); + m_contentView->setFrame(contentViewFrame, force); } } diff --git a/escher/src/tab_view.cpp b/escher/src/tab_view.cpp index 618c2b499..5a7dd8011 100644 --- a/escher/src/tab_view.cpp +++ b/escher/src/tab_view.cpp @@ -66,7 +66,7 @@ View * TabView::subviewAtIndex(int index) { return &m_cells[index]; } -void TabView::layoutSubviews() { +void TabView::layoutSubviews(bool force) { KDCoordinate emptyWidth = bounds().width(); for (int i=0; isetFrame(activeViewFrame); + m_activeView->setFrame(activeViewFrame, force); } } diff --git a/escher/src/table_cell.cpp b/escher/src/table_cell.cpp index 6a14efc94..193296a44 100644 --- a/escher/src/table_cell.cpp +++ b/escher/src/table_cell.cpp @@ -2,7 +2,11 @@ #include #include +static inline KDCoordinate minCoordinate(KDCoordinate x, KDCoordinate y) { return x < y ? x : y; } +static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { return x > y ? x : y; } + TableCell::TableCell(Layout layout) : + Bordered(), HighlightCell(), m_layout(layout) { @@ -38,77 +42,131 @@ View * TableCell::subviewAtIndex(int index) { * margins (like ExpressionView), sometimes the subview has no margins (like * MessageView) which prevents us to handle margins only here. */ -void TableCell::layoutSubviews() { +KDCoordinate withMargin(KDCoordinate length, KDCoordinate margin) { + return length == 0 ? 0 : length + margin; +} + +void TableCell::layoutSubviews(bool force) { + /* TODO: this code is awful. However, this should handle multiples cases + * (subviews are not defined, margins are overriden...) */ KDCoordinate width = bounds().width(); KDCoordinate height = bounds().height(); View * label = labelView(); - KDSize labelSize = label ? label->minimalSizeForOptimalDisplay() : KDSizeZero; - if (label) { - switch (m_layout) { - case Layout::Vertical: - label->setFrame(KDRect( - k_separatorThickness+k_labelMargin, - k_separatorThickness+Metric::TableCellLabelTopMargin, - width-2*k_separatorThickness-k_labelMargin, - labelSize.height())); - break; - default: - label->setFrame(KDRect( - k_separatorThickness+k_labelMargin, - k_separatorThickness, - labelSize.width(), - height - 2*k_separatorThickness)); - break; - } - } View * accessory = accessoryView(); - if (accessory) { - KDSize accessorySize = accessory->minimalSizeForOptimalDisplay(); - switch (m_layout) { - case Layout::Vertical: - accessory->setFrame(KDRect( - k_separatorThickness+k_accessoryMargin, - height-k_separatorThickness-accessorySize.height()-k_accessoryBottomMargin, - width-2*k_separatorThickness - k_accessoryMargin, - accessorySize.height())); - break; - default: - // In some cases, the accessory view cannot take all the size it can - KDCoordinate wantedX = width-accessorySize.width()-k_separatorThickness-k_accessoryMargin; - KDCoordinate minX = label ? label->bounds().x()+labelSize.width()+k_labelMargin+k_separatorThickness+k_accessoryMargin : k_accessoryMargin; - if (minX < wantedX) { - accessory->setFrame(KDRect( - wantedX, - k_separatorThickness, - accessorySize.width(), - height-2*k_separatorThickness)); - } else { - accessory->setFrame(KDRect( - minX, - k_separatorThickness, - accessorySize.width(), - height-2*k_separatorThickness)); - } - break; - } - } View * subAccessory = subAccessoryView(); - if (subAccessory && accessory) { - KDSize accessorySize = accessory->minimalSizeForOptimalDisplay(); - KDSize subAccessorySize = subAccessory->minimalSizeForOptimalDisplay(); - subAccessory->setFrame(KDRect(width-k_separatorThickness-k_accessoryMargin-accessorySize.width()-subAccessorySize.width(), k_separatorThickness, - subAccessorySize.width(), height-2*k_separatorThickness)); + KDSize labelSize = label ? label->minimalSizeForOptimalDisplay() : KDSizeZero; + KDSize accessorySize = accessory ? accessory->minimalSizeForOptimalDisplay() : KDSizeZero; + KDSize subAccessorySize = subAccessory ? subAccessory->minimalSizeForOptimalDisplay() : KDSizeZero; + if (m_layout == Layout::Vertical) { + /* + * Vertically: + * ---------------- + * ---------------- + * Line separator + * ---------------- + * k_verticalMargin + * ---------------- + * LABEL + * ---------------- + * k_verticalMargin + * ---------------- + * . + * . [White space if possible, otherwise LABEL overlaps SUBACCESSORY and so on] + * . + * ---------------- + * SUBACCESSORY + * ---------------- + * ACCESSORY + * ---------------- + * k_verticalMargin + * ---------------- + * Line separator + * ---------------- + * ---------------- + * + * + * Horizontally: + * || Line separator | margin* | SUBVIEW | margin* | Line separator || + * + * * = margin can either be labelMargin(), accessoryMargin() or k_horizontalMargin depending on the subview + * + * */ + KDCoordinate horizontalMargin = k_separatorThickness + labelMargin(); + KDCoordinate y = k_separatorThickness; + if (label) { + y += k_verticalMargin; + KDCoordinate labelHeight = minCoordinate(labelSize.height(), height - y - k_separatorThickness - k_verticalMargin); + label->setFrame(KDRect(horizontalMargin, y, width-2*horizontalMargin, labelHeight), force); + y += labelHeight + k_verticalMargin; + } + horizontalMargin = k_separatorThickness + k_horizontalMargin; + y = maxCoordinate(y, height - k_separatorThickness - withMargin(accessorySize.height(), Metric::TableCellVerticalMargin) - withMargin(subAccessorySize.height(), 0)); + if (subAccessory) { + KDCoordinate subAccessoryHeight = minCoordinate(subAccessorySize.height(), height - y - k_separatorThickness - Metric::TableCellVerticalMargin); + accessory->setFrame(KDRect(horizontalMargin, y, width - 2*horizontalMargin, subAccessoryHeight), force); + y += subAccessoryHeight; + } + horizontalMargin = k_separatorThickness + accessoryMargin(); + y = maxCoordinate(y, height - k_separatorThickness - withMargin(accessorySize.height(), Metric::TableCellVerticalMargin)); + if (accessory) { + KDCoordinate accessoryHeight = minCoordinate(accessorySize.height(), height - y - k_separatorThickness - Metric::TableCellVerticalMargin); + accessory->setFrame(KDRect(horizontalMargin, y, width - 2*horizontalMargin, accessoryHeight), force); + } + } else { + /* + * Vertically: + * ---------------- + * ---------------- + * Line separator + * ---------------- + * SUBVIEW + * ---------------- + * Line separator + * ---------------- + * ---------------- + * + * Horizontally: + * || Line separator | Label margin | LABEL | Label margin | ... + * [ White space if possible otherwise the overlap can be from left to + * right subviews or the contrary ] + * + * ... | SUBACCESSORY | ACCESSORY | Accessory margin | Line separator || + * + * */ + + KDCoordinate verticalMargin = k_separatorThickness; + KDCoordinate x = 0; + KDCoordinate labelX = k_separatorThickness + labelMargin(); + KDCoordinate subAccessoryX = maxCoordinate(k_separatorThickness + k_horizontalMargin, width - k_separatorThickness - withMargin(accessorySize.width(), accessoryMargin()) - withMargin(subAccessorySize.width(), 0)); + KDCoordinate accessoryX = maxCoordinate(k_separatorThickness + accessoryMargin(), width - k_separatorThickness - withMargin(accessorySize.width(), accessoryMargin())); + if (label) { + x = labelX; + KDCoordinate labelWidth = minCoordinate(labelSize.width(), width - x - k_separatorThickness - labelMargin()); + if (m_layout == Layout::HorizontalRightOverlap) { + labelWidth = minCoordinate(labelWidth, subAccessoryX - x - labelMargin()); + } + label->setFrame(KDRect(x, verticalMargin, labelWidth, height-2*verticalMargin), force); + x += labelWidth + labelMargin(); + } + if (subAccessory) { + x = maxCoordinate(x, subAccessoryX); + KDCoordinate subAccessoryWidth = minCoordinate(subAccessorySize.width(), width - x - k_separatorThickness - k_horizontalMargin); + if (m_layout == Layout::HorizontalRightOverlap) { + subAccessoryWidth = minCoordinate(subAccessoryWidth, accessoryX - x); + } + subAccessory->setFrame(KDRect(x, verticalMargin, subAccessoryWidth, height-2*verticalMargin), force); + x += subAccessoryWidth; + } + if (accessory) { + x = maxCoordinate(x, accessoryX); + KDCoordinate accessoryWidth = minCoordinate(accessorySize.width(), width - x - k_separatorThickness - accessoryMargin()); + accessory->setFrame(KDRect(x, verticalMargin, accessoryWidth, height-2*verticalMargin), force); + } } } void TableCell::drawRect(KDContext * ctx, KDRect rect) const { - KDCoordinate width = bounds().width(); - KDCoordinate height = bounds().height(); KDColor backgroundColor = isHighlighted() ? Palette::ListCellBackgroundSelected : Palette::ListCellBackground; - ctx->fillRect(KDRect(k_separatorThickness, k_separatorThickness, width-2*k_separatorThickness, height-k_separatorThickness), backgroundColor); - // Draw rectangle around cell - ctx->fillRect(KDRect(0, 0, width, k_separatorThickness), Palette::ListCellBorder); - ctx->fillRect(KDRect(0, k_separatorThickness, k_separatorThickness, height-k_separatorThickness), Palette::ListCellBorder); - ctx->fillRect(KDRect(width-k_separatorThickness, k_separatorThickness, k_separatorThickness, height-k_separatorThickness), Palette::ListCellBorder); - ctx->fillRect(KDRect(0, height-k_separatorThickness, width, k_separatorThickness), Palette::ListCellBorder); - } + drawInnerRect(ctx, bounds(), backgroundColor); + drawBorderOfRect(ctx, bounds(), Palette::ListCellBorder); +} diff --git a/escher/src/table_view.cpp b/escher/src/table_view.cpp index 1172ecbf0..f4c72317e 100644 --- a/escher/src/table_view.cpp +++ b/escher/src/table_view.cpp @@ -33,7 +33,7 @@ const char * TableView::className() const { } #endif -void TableView::layoutSubviews() { +void TableView::layoutSubviews(bool force) { /* On the one hand, ScrollView::layoutSubviews() * calls setFrame(...) over m_contentView, * which typically calls layoutSubviews() over m_contentView. @@ -49,8 +49,8 @@ void TableView::layoutSubviews() { * FIXME: * Finally, this solution is not optimal at all since * layoutSubviews is called twice over m_contentView. */ - m_contentView.layoutSubviews(); - ScrollView::layoutSubviews(); + m_contentView.layoutSubviews(force); + ScrollView::layoutSubviews(force); } void TableView::reloadCellAtLocation(int i, int j) { @@ -166,7 +166,7 @@ View * TableView::ContentView::subviewAtIndex(int index) { return m_dataSource->reusableCell(typeIndex, type); } -void TableView::ContentView::layoutSubviews() { +void TableView::ContentView::layoutSubviews(bool force) { /* The number of subviews might change during the layouting so it needs to be * recomputed at each step of the for loop. */ for (int index = 0; index < numberOfSubviews(); index++) { @@ -174,7 +174,7 @@ void TableView::ContentView::layoutSubviews() { int i = absoluteColumnNumberFromSubviewIndex(index); int j = absoluteRowNumberFromSubviewIndex(index); m_dataSource->willDisplayCellAtLocation((HighlightCell *)cell, i, j); - cell->setFrame(cellFrame(i,j)); + cell->setFrame(cellFrame(i,j), force); } } diff --git a/escher/src/text_area.cpp b/escher/src/text_area.cpp index 3e81eee09..100e537bb 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -3,12 +3,15 @@ #include #include #include +#include #include #include #include +static inline const char * maxPointer(const char * x, const char * y) { return x > y ? x : y; } static inline const char * minPointer(const char * x, const char * y) { return x < y ? x : y; } +static inline size_t minSizeT(size_t x, size_t y) { return x < y ? x : y; } /* TextArea */ @@ -37,6 +40,12 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for if (*text == 0) { return false; } + + // Delete the selected text if needed + if (!contentView()->selectionIsEmpty()) { + deleteSelection(); + } + /* Compute the indentation. If the text cannot be inserted with the * indentation, stop here. */ int spacesCount = 0; @@ -96,7 +105,16 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for bool TextArea::handleEvent(Ion::Events::Event event) { if (m_delegate != nullptr && m_delegate->textAreaDidReceiveEvent(this, event)) { return true; - } else if (handleBoxEvent(event)) { + } + if (handleBoxEvent(event)) { + return true; + } + if (event == Ion::Events::ShiftLeft || event == Ion::Events::ShiftRight) { + selectLeftRight(event == Ion::Events::ShiftLeft, false); + return true; + } + if (event == Ion::Events::ShiftUp || event == Ion::Events::ShiftDown) { + selectUpDown(event == Ion::Events::ShiftUp); return true; } else if (event == Ion::Events::ShiftLeft) { contentView()->moveCursorGeo(-INT_MAX/2, 0); @@ -107,21 +125,70 @@ bool TextArea::handleEvent(Ion::Events::Event event) { } else if (event == Ion::Events::ShiftDown) { contentView()->moveCursorGeo(0, INT_MAX/2); } else if (event == Ion::Events::Left) { + if (contentView()->resetSelection()) { + return true; + } return TextInput::moveCursorLeft(); - } else if (event == Ion::Events::Right) { + } + if (event == Ion::Events::Right) { + if (contentView()->resetSelection()) { + return true; + } return TextInput::moveCursorRight(); + } + + if (event.hasText()) { + return handleEventWithText(event.text()); + } + if (event == Ion::Events::EXE) { + return handleEventWithText("\n"); + } + if (event == Ion::Events::Copy || event == Ion::Events::Cut) { + if (contentView()->selectionIsEmpty()) { + return false; + } + const char * start = contentView()->selectionStart(); + Clipboard::sharedClipboard()->store(start, contentView()->selectionEnd() - start); + if (event == Ion::Events::Cut) { + deleteSelection(); + } + return true; + } + if (event == Ion::Events::Paste) { + return handleEventWithText(Clipboard::sharedClipboard()->storedText(), false, true); + } + + // The following events need a scrollToCursor and return true + if (event == Ion::Events::Backspace) { + if (contentView()->selectionIsEmpty()) { + if (!removePreviousGlyph()) { + return false; + } + } else { + deleteSelection(); + return true; + } } else if (event == Ion::Events::Up) { + contentView()->resetSelection(); contentView()->moveCursorGeo(0, -1); } else if (event == Ion::Events::Down) { + contentView()->resetSelection(); contentView()->moveCursorGeo(0, 1); +<<<<<<< HEAD } else if (event == Ion::Events::Backspace) { return removePreviousGlyph(); } else if (event == Ion::Events::EXE) { return handleEventWithText("\n"); +======= +>>>>>>> upstream/master } else if (event == Ion::Events::Clear) { - if (!contentView()->removeEndOfLine()) { + if (!contentView()->selectionIsEmpty()) { + deleteSelection(); + return true; + } else if (!contentView()->removeEndOfLine()) { contentView()->removeStartOfLine(); } +<<<<<<< HEAD } else if (event == Ion::Events::Paste) { return handleEventWithText(Clipboard::sharedClipboard()->storedText()); } else if (event == Ion::Events::Percent) { @@ -132,6 +199,8 @@ bool TextArea::handleEvent(Ion::Events::Event event) { } else { return handleEventWithText(event.text()); } +======= +>>>>>>> upstream/master } else { return false; } @@ -151,11 +220,11 @@ int TextArea::indentationBeforeCursor() const { * another code point, until reaching the beginning of the line. */ UTF8Helper::PerformAtCodePoints(const_cast