diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index cf7b4d2bd..79f1c10f1 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -319,6 +319,14 @@ void AppsContainer::displayExamModePopUp(bool activate) { } void AppsContainer::shutdownDueToLowBattery() { + if (Ion::Battery::level() != Ion::Battery::Charge::EMPTY) { + /* We early escape here. When the battery switches from LOW to EMPTY, it + * oscillates a few times before stabilizing to EMPTY. So we might call + * 'shutdownDueToLowBattery' but the battery level still answers LOW instead + * of EMPTY. We want to avoid uselessly redrawing the whole window in that + * case. */ + return; + } while (Ion::Battery::level() == Ion::Battery::Charge::EMPTY) { Ion::Backlight::setBrightness(0); m_emptyBatteryWindow.redraw(true); diff --git a/apps/battery_view.cpp b/apps/battery_view.cpp index dbbdd9e34..9e8b97e9c 100644 --- a/apps/battery_view.cpp +++ b/apps/battery_view.cpp @@ -22,6 +22,12 @@ const uint8_t tickMask[BatteryView::k_tickHeight][BatteryView::k_tickWidth] = { }; bool BatteryView::setChargeState(Ion::Battery::Charge chargeState) { + /* There is no specific battery picto for 'empty' battery as the whole device + * shut down. Still, there might be a redrawing of the window before shutting + * down so we handle this case as the 'low' battery one. Plus, we avoid + * trigerring a redrawing by not marking anything as dirty when switching + * from 'low' to 'empty' battery. */ + chargeState = chargeState == Ion::Battery::Charge::EMPTY ? Ion::Battery::Charge::LOW : chargeState; if (chargeState != m_chargeState) { m_chargeState = chargeState; markRectAsDirty(bounds()); @@ -52,6 +58,7 @@ KDColor s_flashWorkingBuffer[BatteryView::k_flashHeight*BatteryView::k_flashWidt KDColor s_tickWorkingBuffer[BatteryView::k_tickHeight*BatteryView::k_tickWidth]; void BatteryView::drawRect(KDContext * ctx, KDRect rect) const { + assert(m_chargeState != Ion::Battery::Charge::EMPTY); /* We draw from left to right. The middle part representing the battery *'content' depends on the charge */ diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index 2a498d278..34044090b 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -18,7 +18,9 @@ Calculation::Calculation() : m_inputText(), m_exactOutputText(), m_approximateOutputText(), + m_displayOutput(DisplayOutput::Unknown), m_height(-1), + m_expandedHeight(-1), m_equalSign(EqualSign::Unknown) { } @@ -58,28 +60,36 @@ void Calculation::setContent(const char * c, Context * context, Expression ansEx PoincareHelpers::Serialize(approximateOutput, m_approximateOutputText, sizeof(m_approximateOutputText)); } -KDCoordinate Calculation::height(Context * context) { - if (m_height < 0) { +KDCoordinate Calculation::height(Context * context, bool expanded) { + KDCoordinate * memoizedHeight = expanded ? &m_expandedHeight : &m_height; + if (*memoizedHeight < 0) { + DisplayOutput display = displayOutput(context); Layout inputLayout = createInputLayout(); KDCoordinate inputHeight = inputLayout.layoutSize().height(); - Layout approximateLayout = createApproximateOutputLayout(context); - Layout exactLayout = createExactOutputLayout(); - DisplayOutput display = displayOutput(context); if (display == DisplayOutput::ExactOnly) { - KDCoordinate exactOutputHeight = exactLayout.layoutSize().height(); - m_height = inputHeight+exactOutputHeight; - } else if (display == DisplayOutput::ApproximateOnly) { - KDCoordinate approximateOutputHeight = approximateLayout.layoutSize().height(); - m_height = inputHeight+approximateOutputHeight; + KDCoordinate exactOutputHeight = createExactOutputLayout().layoutSize().height(); + *memoizedHeight = inputHeight+exactOutputHeight; + } else if (display == DisplayOutput::ApproximateOnly || (!expanded && display == DisplayOutput::ExactAndApproximateToggle)) { + KDCoordinate approximateOutputHeight = createApproximateOutputLayout(context).layoutSize().height(); + *memoizedHeight = inputHeight+approximateOutputHeight; } else { - assert(display == DisplayOutput::ExactAndApproximate); + 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()); - m_height = inputHeight + outputHeight; + *memoizedHeight = inputHeight + outputHeight; + } + /* 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 = *memoizedHeight; + m_expandedHeight = *memoizedHeight; } } - return m_height; + return *memoizedHeight; } const char * Calculation::inputText() { @@ -118,7 +128,9 @@ bool Calculation::isEmpty() { void Calculation::tidy() { /* Uninitialized all Expression stored to free the Pool */ + m_displayOutput = DisplayOutput::Unknown; m_height = -1; + m_expandedHeight = -1; m_equalSign = EqualSign::Unknown; } @@ -155,22 +167,33 @@ Layout Calculation::createApproximateOutputLayout(Context * context) { } Calculation::DisplayOutput Calculation::displayOutput(Context * context) { - if (shouldOnlyDisplayExactOutput()) { - return DisplayOutput::ExactOnly; + if (m_displayOutput != DisplayOutput::Unknown) { + return m_displayOutput; } - bool approximateOnly = false; - if (strcmp(m_exactOutputText, m_approximateOutputText) == 0) { + if (shouldOnlyDisplayExactOutput()) { + m_displayOutput = DisplayOutput::ExactOnly; + } else if (exactOutput().recursivelyMatches([](const Expression e, Context & c, bool replaceSymbols) { + /* If the exact result contains one of the following types, do not + * display it. */ + ExpressionNode::Type t = e.type(); + return (t == ExpressionNode::Type::Random) || (t == ExpressionNode::Type::Round);}, + *context, true)) + { + m_displayOutput = DisplayOutput::ApproximateOnly; + } else if (strcmp(m_exactOutputText, m_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. */ - approximateOnly = exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal; + m_displayOutput = exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal ? DisplayOutput::ApproximateOnly : DisplayOutput::ExactAndApproximate; } else if (strcmp(m_exactOutputText, Undefined::Name()) == 0 || strcmp(m_approximateOutputText, Unreal::Name()) == 0) { // If the approximate result is 'unreal' or the exact result is 'undef' - approximateOnly = true; + m_displayOutput = DisplayOutput::ApproximateOnly; + } else if (input().isApproximate(*context) || exactOutput().isApproximate(*context)) { + m_displayOutput = DisplayOutput::ExactAndApproximateToggle; } else { - approximateOnly = input().isApproximate(*context) || exactOutput().isApproximate(*context); + m_displayOutput = DisplayOutput::ExactAndApproximate; } - return approximateOnly ? DisplayOutput::ApproximateOnly : DisplayOutput::ExactAndApproximate; + return m_displayOutput; } bool Calculation::shouldOnlyDisplayExactOutput() { diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index 0a4822209..ff00be74c 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -19,9 +19,11 @@ public: }; enum class DisplayOutput : uint8_t { + Unknown, ExactOnly, ApproximateOnly, - ExactAndApproximate + ExactAndApproximate, + ExactAndApproximateToggle }; Calculation(); @@ -29,7 +31,7 @@ public: /* c.reset() is the equivalent of c = Calculation() without copy assingment. */ void reset(); void setContent(const char * c, Poincare::Context * context, Poincare::Expression ansExpression); - KDCoordinate height(Poincare::Context * context); + KDCoordinate height(Poincare::Context * context, bool expanded = false); const char * inputText(); const char * exactOutputText(); const char * approximateOutputText(); @@ -52,7 +54,9 @@ private: char m_inputText[Constant::MaxSerializedExpressionSize]; char m_exactOutputText[Constant::MaxSerializedExpressionSize]; char m_approximateOutputText[Constant::MaxSerializedExpressionSize]; + DisplayOutput m_displayOutput; KDCoordinate m_height; + KDCoordinate m_expandedHeight; EqualSign m_equalSign; }; diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index ba3ec25cd..3b75f6ee6 100644 --- a/apps/calculation/history_controller.cpp +++ b/apps/calculation/history_controller.cpp @@ -105,20 +105,23 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { return false; } -void HistoryController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { +void HistoryController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { + if (withinTemporarySelection || previousSelectedCellY == selectedRow()) { + return; + } + HistoryViewCell * cell = static_cast(t->selectedCell()); if (previousSelectedCellY == -1) { - setSelectedSubviewType(SubviewType::Output); + setSelectedSubviewType(SubviewType::Output, cell); } else if (selectedRow() < previousSelectedCellY) { - setSelectedSubviewType(SubviewType::Output); + setSelectedSubviewType(SubviewType::Output, cell); } else if (selectedRow() > previousSelectedCellY) { - setSelectedSubviewType(SubviewType::Input); + setSelectedSubviewType(SubviewType::Input, cell); } HistoryViewCell * selectedCell = (HistoryViewCell *)(t->selectedCell()); if (selectedCell == nullptr) { return; } app()->setFirstResponder(selectedCell); - selectedCell->reloadCell(); } int HistoryController::numberOfRows() { @@ -139,9 +142,9 @@ int HistoryController::reusableCellCount(int type) { void HistoryController::willDisplayCellForIndex(HighlightCell * cell, int index) { HistoryViewCell * myCell = (HistoryViewCell *)cell; - myCell->setCalculation(m_calculationStore->calculationAtIndex(index)); + myCell->setCalculation(m_calculationStore->calculationAtIndex(index), index == selectedRow() && selectedSubviewType() == SubviewType::Output); myCell->setEven(index%2 == 0); - myCell->reloadCell(); + myCell->setHighlighted(myCell->isHighlighted()); } KDCoordinate HistoryController::rowHeight(int j) { @@ -150,7 +153,7 @@ KDCoordinate HistoryController::rowHeight(int j) { } Calculation * calculation = m_calculationStore->calculationAtIndex(j); App * calculationApp = (App *)app(); - return calculation->height(calculationApp->localContext()) + 4 * Metric::CommonSmallMargin; + return calculation->height(calculationApp->localContext(), j == selectedRow() && selectedSubviewType() == SubviewType::Output) + 4 * Metric::CommonSmallMargin; } int HistoryController::typeAtLocation(int i, int j) { @@ -161,4 +164,10 @@ void HistoryController::scrollToCell(int i, int j) { m_selectableTableView.scrollToCell(i, j); } +void HistoryController::historyViewCellDidChangeSelection() { + /* Update the whole table as the height of the selected cell row might have + * changed. */ + m_selectableTableView.reloadData(); +} + } diff --git a/apps/calculation/history_controller.h b/apps/calculation/history_controller.h index c80346b25..67caf5ec9 100644 --- a/apps/calculation/history_controller.h +++ b/apps/calculation/history_controller.h @@ -24,10 +24,11 @@ public: void willDisplayCellForIndex(HighlightCell * cell, int index) override; KDCoordinate rowHeight(int j) override; int typeAtLocation(int i, int j) override; - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection = false) override; void scrollToCell(int i, int j); private: CalculationSelectableTableView * selectableTableView(); + void historyViewCellDidChangeSelection() override; constexpr static int k_maxNumberOfDisplayedRows = 5; CalculationSelectableTableView m_selectableTableView; HistoryViewCell m_calculationHistory[k_maxNumberOfDisplayedRows]; diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index fe0bba7a9..204d3f6ec 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -19,7 +19,9 @@ void HistoryViewCellDataSource::setSelectedSubviewType(SubviewType subviewType, m_selectedSubviewType = subviewType; if (cell) { cell->setHighlighted(cell->isHighlighted()); + cell->cellDidSelectSubview(subviewType); } + historyViewCellDidChangeSelection(); } /* HistoryViewCell */ @@ -27,9 +29,7 @@ void HistoryViewCellDataSource::setSelectedSubviewType(SubviewType subviewType, HistoryViewCell::HistoryViewCell(Responder * parentResponder) : Responder(parentResponder), m_calculation(), - m_inputLayout(), - m_leftOutputLayout(), - m_rightOutputLayout(), + m_calculationExpanded(false), m_inputView(this), m_scrollableOutputView(this) { @@ -58,29 +58,48 @@ void HistoryViewCell::setHighlighted(bool highlight) { m_scrollableOutputView.evenOddCell()->setHighlighted(true); } } - reloadScroll(); } Poincare::Layout HistoryViewCell::layout() const { assert(m_dataSource); if (m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Input) { - return m_inputLayout; + return m_inputView.layout(); } else { return m_scrollableOutputView.layout(); } } -void HistoryViewCell::reloadCell() { - m_scrollableOutputView.evenOddCell()->reloadCell(); - layoutSubviews(); - reloadScroll(); -} - void HistoryViewCell::reloadScroll() { m_inputView.reloadScroll(); m_scrollableOutputView.reloadScroll(); } +void HistoryViewCell::reloadOutputSelection() { + App * calculationApp = (App *)app(); + Calculation::DisplayOutput display = m_calculation.displayOutput(calculationApp->localContext()); + /* Select the right output according to the calculation display output. This + * will reload the scroll to display the selected output. */ + if (display == Calculation::DisplayOutput::ExactAndApproximate) { + m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Left); + } else { + assert(display == Calculation::DisplayOutput::ApproximateOnly || (display == Calculation::DisplayOutput::ExactAndApproximateToggle) || display == Calculation::DisplayOutput::ExactOnly); + m_scrollableOutputView.setSelectedSubviewPosition(Shared::ScrollableExactApproximateExpressionsView::SubviewPosition::Right); + } +} + +void HistoryViewCell::cellDidSelectSubview(HistoryViewCellDataSource::SubviewType type) { + // Init output selection + if (type == HistoryViewCellDataSource::SubviewType::Output) { + reloadOutputSelection(); + } + /* 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. */ + setCalculation(&m_calculation, type == HistoryViewCellDataSource::SubviewType::Output); + // Reload scroll when switching from one subview to another + reloadScroll(); +} + KDColor HistoryViewCell::backgroundColor() const { KDColor background = m_even ? KDColorWhite : Palette::WallScreen; return background; @@ -114,35 +133,37 @@ void HistoryViewCell::layoutSubviews() { )); } -void HistoryViewCell::setCalculation(Calculation * calculation) { - if (*calculation == m_calculation) { +void HistoryViewCell::setCalculation(Calculation * calculation, bool expanded) { + if (m_calculationExpanded == expanded && *calculation == m_calculation) { return; } + // Memoization m_calculation = *calculation; - m_inputLayout = calculation->createInputLayout(); - m_inputView.setLayout(m_inputLayout); + m_calculationExpanded = expanded; App * calculationApp = (App *)app(); + Calculation::DisplayOutput display = calculation->displayOutput(calculationApp->localContext()); + m_inputView.setLayout(calculation->createInputLayout()); /* Both output 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. */ - if (!m_leftOutputLayout.isUninitialized()) { - m_leftOutputLayout = Poincare::Layout(); - } - if (!m_rightOutputLayout.isUninitialized()) { - m_rightOutputLayout = Poincare::Layout(); - } - Calculation::DisplayOutput display = calculation->displayOutput(calculationApp->localContext()); + Poincare::Layout leftOutputLayout = Poincare::Layout(); + Poincare::Layout rightOutputLayout; if (display == Calculation::DisplayOutput::ExactOnly) { - m_rightOutputLayout = calculation->createExactOutputLayout(); + rightOutputLayout = calculation->createExactOutputLayout(); } else { - m_rightOutputLayout = calculation->createApproximateOutputLayout(calculationApp->localContext()); - if (display == Calculation::DisplayOutput::ExactAndApproximate) { - m_leftOutputLayout = calculation->createExactOutputLayout(); + rightOutputLayout = calculation->createApproximateOutputLayout(calculationApp->localContext()); + if (display == Calculation::DisplayOutput::ExactAndApproximate || (display == Calculation::DisplayOutput::ExactAndApproximateToggle && expanded)) { + leftOutputLayout = calculation->createExactOutputLayout(); } } - m_scrollableOutputView.setLayouts(m_rightOutputLayout, m_leftOutputLayout); + m_scrollableOutputView.setLayouts(rightOutputLayout, leftOutputLayout); I18n::Message equalMessage = calculation->exactAndApproximateDisplayedOutputsAreEqual(calculationApp->localContext()) == Calculation::EqualSign::Equal ? I18n::Message::Equal : I18n::Message::AlmostEqual; m_scrollableOutputView.setEqualMessage(equalMessage); + + /* The displayed input and outputs have changed. We need to re-layout the cell + * and re-initialize the scroll. */ + layoutSubviews(); + reloadScroll(); } void HistoryViewCell::didBecomeFirstResponder() { @@ -161,9 +182,8 @@ bool HistoryViewCell::handleEvent(Ion::Events::Event event) { HistoryViewCellDataSource::SubviewType otherSubviewType = m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Input ? HistoryViewCellDataSource::SubviewType::Output : HistoryViewCellDataSource::SubviewType::Input; CalculationSelectableTableView * tableView = (CalculationSelectableTableView *)parentResponder(); tableView->scrollToSubviewOfTypeOfCellAtLocation(otherSubviewType, tableView->selectedColumn(), tableView->selectedRow()); - HistoryViewCell * selectedCell = (HistoryViewCell *)(tableView->selectedCell()); - m_dataSource->setSelectedSubviewType(otherSubviewType, selectedCell); - app()->setFirstResponder(selectedCell); + m_dataSource->setSelectedSubviewType(otherSubviewType, this); + app()->setFirstResponder(this); return true; } return false; diff --git a/apps/calculation/history_view_cell.h b/apps/calculation/history_view_cell.h index 55053d6dc..5a2d00d88 100644 --- a/apps/calculation/history_view_cell.h +++ b/apps/calculation/history_view_cell.h @@ -20,14 +20,17 @@ public: void setSelectedSubviewType(SubviewType subviewType, HistoryViewCell * cell = nullptr); 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. */ + virtual void historyViewCellDidChangeSelection() = 0; SubviewType m_selectedSubviewType; }; class HistoryViewCell : public ::EvenOddCell, public Responder { public: HistoryViewCell(Responder * parentResponder = nullptr); - void reloadCell() override; - void reloadScroll(); + void cellDidSelectSubview(HistoryViewCellDataSource::SubviewType type); void setEven(bool even) override; void setHighlighted(bool highlight) override; void setDataSource(HistoryViewCellDataSource * dataSource) { m_dataSource = dataSource; } @@ -36,7 +39,8 @@ public: } Poincare::Layout layout() const override; KDColor backgroundColor() const override; - void setCalculation(Calculation * calculation); + Calculation * calculation() { return &m_calculation; } + void setCalculation(Calculation * calculation, bool expanded = false); int numberOfSubviews() const override; View * subviewAtIndex(int index) override; void layoutSubviews() override; @@ -45,10 +49,10 @@ public: Shared::ScrollableExactApproximateExpressionsView * outputView(); private: constexpr static KDCoordinate k_resultWidth = 80; + void reloadScroll(); + void reloadOutputSelection(); Calculation m_calculation; - Poincare::Layout m_inputLayout; - Poincare::Layout m_leftOutputLayout; - Poincare::Layout m_rightOutputLayout; + bool m_calculationExpanded; ScrollableExpressionView m_inputView; Shared::ScrollableExactApproximateExpressionsView m_scrollableOutputView; HistoryViewCellDataSource * m_dataSource; diff --git a/apps/calculation/scrollable_expression_view.cpp b/apps/calculation/scrollable_expression_view.cpp index 4860699ad..fae79f5d6 100644 --- a/apps/calculation/scrollable_expression_view.cpp +++ b/apps/calculation/scrollable_expression_view.cpp @@ -17,6 +17,10 @@ ScrollableExpressionView::ScrollableExpressionView(Responder * parentResponder) ); } +Poincare::Layout ScrollableExpressionView::layout() const { + return m_expressionView.layout(); +} + void ScrollableExpressionView::setLayout(Layout layout) { m_expressionView.setLayout(layout); } diff --git a/apps/calculation/scrollable_expression_view.h b/apps/calculation/scrollable_expression_view.h index 15ce343bf..95fc67350 100644 --- a/apps/calculation/scrollable_expression_view.h +++ b/apps/calculation/scrollable_expression_view.h @@ -8,6 +8,7 @@ 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); diff --git a/apps/calculation/test/calculation_store.cpp b/apps/calculation/test/calculation_store.cpp index 0e9f7f7da..d132110ea 100644 --- a/apps/calculation/test/calculation_store.cpp +++ b/apps/calculation/test/calculation_store.cpp @@ -91,8 +91,8 @@ QUIZ_CASE(calculation_display_exact_approximate) { 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::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); - assertCalculationDisplay("2x-x", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &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, "[[1,undef,3]]", &globalContext, &store); assertCalculationDisplay("28^7", ::Calculation::Calculation::DisplayOutput::ExactAndApproximate, ::Calculation::Calculation::EqualSign::Unknown, nullptr, nullptr, &globalContext, &store); @@ -104,16 +104,20 @@ QUIZ_CASE(calculation_display_exact_approximate) { 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); 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); + } QUIZ_CASE(calculation_symbolic_computation) { Shared::GlobalContext globalContext; CalculationStore store; - assertCalculationDisplay("x+x+1+3+√(π)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &store); - assertCalculationDisplay("f(x)", ::Calculation::Calculation::DisplayOutput::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &globalContext, &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::ExactOnly, ::Calculation::Calculation::EqualSign::Unknown, "undef", "undef", &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); diff --git a/apps/code/console_controller.cpp b/apps/code/console_controller.cpp index d5da9c826..429a0f170 100644 --- a/apps/code/console_controller.cpp +++ b/apps/code/console_controller.cpp @@ -245,7 +245,10 @@ void ConsoleController::willDisplayCellAtLocation(HighlightCell * cell, int i, i } } -void ConsoleController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { +void ConsoleController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { + if (withinTemporarySelection) { + return; + } if (t->selectedRow() == m_consoleStore.numberOfLines()) { m_editCell.setEditing(true); return; diff --git a/apps/code/console_controller.h b/apps/code/console_controller.h index d39f6edfe..43779f340 100644 --- a/apps/code/console_controller.h +++ b/apps/code/console_controller.h @@ -52,7 +52,7 @@ public: void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; // SelectableTableViewDelegate - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; // TextFieldDelegate bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; diff --git a/apps/code/menu_controller.cpp b/apps/code/menu_controller.cpp index 60dda9eec..78adafc2f 100644 --- a/apps/code/menu_controller.cpp +++ b/apps/code/menu_controller.cpp @@ -272,8 +272,8 @@ void MenuController::willDisplayScriptTitleCellForIndex(HighlightCell * cell, in (static_cast(cell))->textField()->setText(m_scriptStore->scriptAtIndex(index).fullName()); } -void MenuController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { - if (selectedRow() == numberOfRows() - 1 && selectedColumn() == 1 && m_shouldDisplayAddScriptRow) { +void MenuController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { + if (!withinTemporarySelection && selectedRow() == numberOfRows() - 1 && selectedColumn() == 1 && m_shouldDisplayAddScriptRow) { t->selectCellAtLocation(0, numberOfRows()-1); } } diff --git a/apps/code/menu_controller.h b/apps/code/menu_controller.h index 7b94014f4..5848a302a 100644 --- a/apps/code/menu_controller.h +++ b/apps/code/menu_controller.h @@ -46,7 +46,7 @@ public: void willDisplayScriptTitleCellForIndex(HighlightCell * cell, int index); /* SelectableTableViewDelegate */ - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; /* TextFieldDelegate */ bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; diff --git a/apps/graph/Makefile b/apps/graph/Makefile index 5f8ea6f7e..b3b7ef5b0 100644 --- a/apps/graph/Makefile +++ b/apps/graph/Makefile @@ -12,6 +12,8 @@ app_src += $(addprefix apps/graph/,\ graph/graph_controller.cpp \ graph/graph_controller_helper.cpp \ graph/graph_view.cpp \ + graph/preimage_graph_controller.cpp\ + graph/preimage_parameter_controller.cpp\ graph/integral_graph_controller.cpp \ graph/intersection_graph_controller.cpp \ graph/root_graph_controller.cpp \ diff --git a/apps/graph/base.de.i18n b/apps/graph/base.de.i18n index ed06c9cbc..e5ec488fa 100644 --- a/apps/graph/base.de.i18n +++ b/apps/graph/base.de.i18n @@ -11,12 +11,14 @@ Compute = "Berechnen" Zeros = "Nullstellen" Tangent = "Tangente" Intersection = "Schnittmenge" +Preimage = "Urbild" SelectLowerBound = "Untere Integrationsgrenze" SelectUpperBound = "Obere Integrationsgrenze" NoMaximumFound = "Kein Maximalwert gefunden" NoMinimumFound = "Kein Mindestwert gefunden" NoZeroFound = "Keine Nullstelle gefunden" NoIntersectionFound = "Kein Schnittpunkt gefunden" +NoPreimageFound = "Kein Urbild gefunden" DerivativeFunctionColumn = "Spalte der Ableitungsfunktion" HideDerivativeColumn = "Ableitungsfunktion ausblenden" AllowedCharactersAZaz09 = "Erlaubte Zeichen: A-Z, a-z, 0-9, _" diff --git a/apps/graph/base.en.i18n b/apps/graph/base.en.i18n index 34847cd07..18142b32a 100644 --- a/apps/graph/base.en.i18n +++ b/apps/graph/base.en.i18n @@ -11,12 +11,14 @@ Compute = "Calculate" Zeros = "Zeros" Tangent = "Tangent" Intersection = "Intersection" +Preimage = "Inverse image" SelectLowerBound = "Select lower bound" SelectUpperBound = "Select upper bound" NoMaximumFound = "No maximum found" NoMinimumFound = "No minimum found" NoZeroFound = "No zero found" NoIntersectionFound = "No intersection found" +NoPreimageFound = "No inverse image found" DerivativeFunctionColumn = "Derivative function column" HideDerivativeColumn = "Hide the derivative function" AllowedCharactersAZaz09 = "Allowed characters: A-Z, a-z, 0-9, _" diff --git a/apps/graph/base.es.i18n b/apps/graph/base.es.i18n index 3d095ec4d..fbb763c64 100644 --- a/apps/graph/base.es.i18n +++ b/apps/graph/base.es.i18n @@ -11,12 +11,14 @@ Compute = "Calcular" Zeros = "Raíces" Tangent = "Tangente" Intersection = "Intersección" +Preimage = "Imagen inversa" SelectLowerBound = "Seleccionar el límite inferior" SelectUpperBound = "Seleccionar el límite superior" NoMaximumFound = "Níngun máximo encontrado" NoMinimumFound = "Níngun mínimo encontrado" NoZeroFound = "Ninguna raíz encontrada" NoIntersectionFound = "Ninguna intersección encontrada" +NoPreimageFound = "Ninguna imagen inversa encontrada" DerivativeFunctionColumn = "Columna de la derivada" HideDerivativeColumn = "Ocultar la derivada" AllowedCharactersAZaz09 = "Caracteres permitidos : A-Z, a-z, 0-9, _" diff --git a/apps/graph/base.fr.i18n b/apps/graph/base.fr.i18n index 6dd75e4ea..5014969aa 100644 --- a/apps/graph/base.fr.i18n +++ b/apps/graph/base.fr.i18n @@ -11,12 +11,14 @@ Compute = "Calculer" Zeros = "Zéros" Tangent = "Tangente" Intersection = "Intersection" +Preimage = "Antécédent" SelectLowerBound = "Selectionner la borne inférieure" SelectUpperBound = "Selectionner la borne supérieure" NoMaximumFound = "Aucun maximum trouvé" NoMinimumFound = "Aucun minimum trouvé" NoZeroFound = "Aucun zéro trouvé" NoIntersectionFound = "Aucune intersection trouvée" +NoPreimageFound = "Aucun antécédent trouvé" DerivativeFunctionColumn = "Colonne de la fonction derivée" HideDerivativeColumn = "Masquer la fonction derivée" AllowedCharactersAZaz09 = "Caractères autorisés : A-Z, a-z, 0-9, _" diff --git a/apps/graph/base.pt.i18n b/apps/graph/base.pt.i18n index eed09f293..1e33f5c91 100644 --- a/apps/graph/base.pt.i18n +++ b/apps/graph/base.pt.i18n @@ -11,12 +11,14 @@ Compute = "Calcular" Zeros = "Raízes" Tangent = "Tangente" Intersection = "Intersecção" +Preimage = "Imagem inversa" SelectLowerBound = "Selecionar limite superior" SelectUpperBound = "Selecionar limite inferior" NoMaximumFound = "Nenhum máximo encontrado" NoMinimumFound = "Nenhum mínimo encontrado" NoZeroFound = "Nenhuma raiz encontrada" NoIntersectionFound = "Nenhuma intersecção encontrada" +NoPreimageFound = "Nenhuma imagem inversa encontrada" DerivativeFunctionColumn = "Coluna da função derivada" HideDerivativeColumn = "Esconder função derivada" AllowedCharactersAZaz09 = "Caracteres permitidos : A-Z, a-z, 0-9, _" diff --git a/apps/graph/graph/calculation_parameter_controller.cpp b/apps/graph/graph/calculation_parameter_controller.cpp index cd5bb5877..f7df007b4 100644 --- a/apps/graph/graph/calculation_parameter_controller.cpp +++ b/apps/graph/graph/calculation_parameter_controller.cpp @@ -9,8 +9,11 @@ namespace Graph { CalculationParameterController::CalculationParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, GraphView * graphView, BannerView * bannerView, InteractiveCurveViewRange * range, CurveViewCursor * cursor) : ViewController(parentResponder), + m_preimageCell(I18n::Message::Preimage), m_selectableTableView(this), m_record(), + m_preimageParameterController(nullptr, inputEventHandlerDelegate, range, cursor, &m_preimageGraphController), + m_preimageGraphController(nullptr, graphView, bannerView, range, cursor), m_tangentGraphController(nullptr, graphView, bannerView, range, cursor), m_integralGraphController(nullptr, inputEventHandlerDelegate, graphView, range, cursor), m_minimumGraphController(nullptr, graphView, bannerView, range, cursor), @@ -34,30 +37,35 @@ void CalculationParameterController::didBecomeFirstResponder() { } bool CalculationParameterController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::OK || event == Ion::Events::EXE) { + int row = selectedRow(); + if (event == Ion::Events::OK || event == Ion::Events::EXE || (event == Ion::Events::Right && row == 0)) { ViewController * controller = nullptr; - switch(selectedRow()) { + switch(row) { case 0: + m_preimageParameterController.setRecord(m_record); + controller = &m_preimageParameterController; + break; + case 1: m_intersectionGraphController.setRecord(m_record); controller = &m_intersectionGraphController; break; - case 1: + case 2: m_maximumGraphController.setRecord(m_record); controller = &m_maximumGraphController; break; - case 2: + case 3: m_minimumGraphController.setRecord(m_record); controller = &m_minimumGraphController; break; - case 3: + case 4: m_rootGraphController.setRecord(m_record); controller = &m_rootGraphController; break; - case 4: + case 5: m_tangentGraphController.setRecord(m_record); controller = &m_tangentGraphController; break; - case 5: + case 6: m_integralGraphController.setRecord(m_record); controller = &m_integralGraphController; break; @@ -65,8 +73,10 @@ bool CalculationParameterController::handleEvent(Ion::Events::Event event) { return false; } StackViewController * stack = static_cast(parentResponder()); - stack->pop(); - stack->pop(); + if (row > 0) { + stack->pop(); + stack->pop(); + } stack->push(controller); return true; } @@ -79,28 +89,41 @@ bool CalculationParameterController::handleEvent(Ion::Events::Event event) { } int CalculationParameterController::numberOfRows() { + constexpr int k_totalNumberOfCells = k_totalNumberOfReusableCells + 1; return k_totalNumberOfCells; }; - -HighlightCell * CalculationParameterController::reusableCell(int index) { - assert(index >= 0); - assert(index < k_totalNumberOfCells); - return &m_cells[index]; -} - -int CalculationParameterController::reusableCellCount() { - return k_totalNumberOfCells; -} - -KDCoordinate CalculationParameterController::cellHeight() { +KDCoordinate CalculationParameterController::rowHeight(int j) { return Metric::ParameterCellHeight; } +HighlightCell * CalculationParameterController::reusableCell(int index, int type) { + assert(index >= 0); + assert(index < reusableCellCount(type)); + if (type == 0) { + return &m_cells[index]; + } + assert(type == 1); + return &m_preimageCell; +} + +int CalculationParameterController::reusableCellCount(int type) { + if (type == 0) { + return k_totalNumberOfReusableCells; + } + return 1; +} + +int CalculationParameterController::typeAtLocation(int i, int j) { + assert(i == 0); + return j == 0; +} + void CalculationParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { - MessageTableCell * myCell = (MessageTableCell *)cell; - I18n::Message titles[k_totalNumberOfCells] = {I18n::Message::Intersection, I18n::Message::Maximum, I18n::Message::Minimum, I18n::Message::Zeros, I18n::Message::Tangent, I18n::Message::Integral}; - myCell->setMessage(titles[index]); + if (cell != &m_preimageCell) { + I18n::Message titles[] = {I18n::Message::Intersection, I18n::Message::Maximum, I18n::Message::Minimum, I18n::Message::Zeros, I18n::Message::Tangent, I18n::Message::Integral}; + static_cast(cell)->setMessage(titles[index - 1]); + } } void CalculationParameterController::setRecord(Ion::Storage::Record record) { diff --git a/apps/graph/graph/calculation_parameter_controller.h b/apps/graph/graph/calculation_parameter_controller.h index 77b1cac88..043f53a7b 100644 --- a/apps/graph/graph/calculation_parameter_controller.h +++ b/apps/graph/graph/calculation_parameter_controller.h @@ -3,6 +3,7 @@ #include #include "../cartesian_function_store.h" +#include "preimage_parameter_controller.h" #include "tangent_graph_controller.h" #include "extremum_graph_controller.h" #include "integral_graph_controller.h" @@ -14,7 +15,7 @@ namespace Graph { -class CalculationParameterController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { +class CalculationParameterController : public ViewController, public ListViewDataSource, public SelectableTableViewDataSource { public: CalculationParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * range, Shared::CurveViewCursor * cursor); View * view() override; @@ -22,16 +23,20 @@ public: bool handleEvent(Ion::Events::Event event) override; void didBecomeFirstResponder() override; int numberOfRows() override; - KDCoordinate cellHeight() override; - HighlightCell * reusableCell(int index) override; - int reusableCellCount() override; + KDCoordinate rowHeight(int j) override; + HighlightCell * reusableCell(int index, int type) override; + int reusableCellCount(int type) override; + int typeAtLocation(int i, int j) override; void willDisplayCellForIndex(HighlightCell * cell, int index) override; void setRecord(Ion::Storage::Record record); private: - constexpr static int k_totalNumberOfCells = 6; - MessageTableCell m_cells[k_totalNumberOfCells]; + MessageTableCellWithChevron m_preimageCell; + constexpr static int k_totalNumberOfReusableCells = 6; + MessageTableCell m_cells[k_totalNumberOfReusableCells]; SelectableTableView m_selectableTableView; Ion::Storage::Record m_record; + PreimageParameterController m_preimageParameterController; + PreimageGraphController m_preimageGraphController; TangentGraphController m_tangentGraphController; IntegralGraphController m_integralGraphController; MinimumGraphController m_minimumGraphController; diff --git a/apps/graph/graph/preimage_graph_controller.cpp b/apps/graph/graph/preimage_graph_controller.cpp new file mode 100644 index 000000000..818de7210 --- /dev/null +++ b/apps/graph/graph/preimage_graph_controller.cpp @@ -0,0 +1,29 @@ +#include "preimage_graph_controller.h" + +namespace Graph { + +PreimageGraphController::PreimageGraphController( + Responder * parentResponder, + GraphView * graphView, + BannerView * bannerView, + Shared::InteractiveCurveViewRange * curveViewRange, + Shared::CurveViewCursor * cursor +) : + CalculationGraphController( + parentResponder, + graphView, + bannerView, + curveViewRange, + cursor, + I18n::Message::NoPreimageFound + ), + m_image(NAN) +{ +} + +Poincare::Expression::Coordinate2D PreimageGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) { + Poincare::Expression expression = Poincare::Float::Builder(m_image); + return functionStore()->modelForRecord(m_record)->nextIntersectionFrom(start, step, max, context, expression); +} + +} diff --git a/apps/graph/graph/preimage_graph_controller.h b/apps/graph/graph/preimage_graph_controller.h new file mode 100644 index 000000000..b9a553e6a --- /dev/null +++ b/apps/graph/graph/preimage_graph_controller.h @@ -0,0 +1,23 @@ +#include "calculation_graph_controller.h" +#include + +namespace Graph { + +class PreimageGraphController : public CalculationGraphController { +public: + PreimageGraphController( + Responder * parentResponder, + GraphView * graphView, + BannerView * bannerView, + Shared::InteractiveCurveViewRange * curveViewRange, + Shared::CurveViewCursor * cursor + ); + const char * title() override { return I18n::translate(I18n::Message::Preimage); } + double image() { return m_image; } + void setImage(double value) { m_image = value; } +private: + Poincare::Expression::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; + double m_image; +}; + +} diff --git a/apps/graph/graph/preimage_parameter_controller.cpp b/apps/graph/graph/preimage_parameter_controller.cpp new file mode 100644 index 000000000..89797dd0b --- /dev/null +++ b/apps/graph/graph/preimage_parameter_controller.cpp @@ -0,0 +1,53 @@ +#include "preimage_parameter_controller.h" +#include "../app.h" +#include + +namespace Graph { + +PreimageParameterController::PreimageParameterController( + Responder * parentResponder, + InputEventHandlerDelegate * inputEventHandlerDelegate, + Shared::InteractiveCurveViewRange * graphRange, + Shared::CurveViewCursor * cursor, + PreimageGraphController * preimageGraphController +) : + Shared::GoToParameterController( + parentResponder, + inputEventHandlerDelegate, + graphRange, + cursor, + I18n::Message::Y + ), + m_preimageGraphController(preimageGraphController) +{ +} + +const char * PreimageParameterController::title() { + return I18n::translate(I18n::Message::Preimage); +} + +void PreimageParameterController::viewWillAppear() { + m_preimageGraphController->setImage(m_cursor->y()); + Shared::GoToParameterController::viewWillAppear(); +} + +void PreimageParameterController::buttonAction() { + m_preimageGraphController->setRecord(m_record); + StackViewController * stack = static_cast(parentResponder()); + stack->pop(); + stack->pop(); + stack->pop(); + stack->push(m_preimageGraphController); +} + +double PreimageParameterController::parameterAtIndex(int index) { + assert(index == 0); + return m_preimageGraphController->image(); +} +bool PreimageParameterController::setParameterAtIndex(int parameterIndex, double f) { + assert(parameterIndex == 0); + m_preimageGraphController->setImage(f); + return true; +} + +} diff --git a/apps/graph/graph/preimage_parameter_controller.h b/apps/graph/graph/preimage_parameter_controller.h new file mode 100644 index 000000000..dbe683004 --- /dev/null +++ b/apps/graph/graph/preimage_parameter_controller.h @@ -0,0 +1,31 @@ +#ifndef GRAPH_PREIMAGE_PARAMETER_CONTROLLER +#define GRAPH_PREIMAGE_PARAMETER_CONTROLLER + +#include "../../shared/go_to_parameter_controller.h" +#include "preimage_graph_controller.h" + +namespace Graph { + +class PreimageParameterController : public Shared::GoToParameterController { +public: + PreimageParameterController( + Responder * parentResponder, + InputEventHandlerDelegate * inputEventHandlerDelegate, + Shared::InteractiveCurveViewRange * graphRange, + Shared::CurveViewCursor * cursor, + PreimageGraphController * preimageGraphController + ); + const char * title() override; + void setRecord(Ion::Storage::Record record) { m_record = record; } + void viewWillAppear() override; +private: + void buttonAction() override; + double parameterAtIndex(int index) override; + bool setParameterAtIndex(int parameterIndex, double f) override; + Ion::Storage::Record m_record; + PreimageGraphController * m_preimageGraphController; +}; + +} + +#endif diff --git a/apps/home/controller.cpp b/apps/home/controller.cpp index 10f0075a5..f177204ec 100644 --- a/apps/home/controller.cpp +++ b/apps/home/controller.cpp @@ -134,7 +134,10 @@ int Controller::numberOfIcons() { return m_container->numberOfApps() - 1; } -void Controller::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { +void Controller::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { + if (withinTemporarySelection) { + return; + } /* If the number of apps (including home) is != 3*n+1, when we display the * lowest icons, the other(s) are empty. As no icon is thus redrawn on the * previous ones, the cell is not cleaned. We need to redraw a white rect on diff --git a/apps/home/controller.h b/apps/home/controller.h index ae3be69e5..116b6291a 100644 --- a/apps/home/controller.h +++ b/apps/home/controller.h @@ -25,7 +25,7 @@ public: virtual HighlightCell * reusableCell(int index) override; virtual int reusableCellCount() override; void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; private: int numberOfIcons(); class ContentView : public View { diff --git a/apps/regression/calculation_controller.cpp b/apps/regression/calculation_controller.cpp index 7a7bfce1b..145950d19 100644 --- a/apps/regression/calculation_controller.cpp +++ b/apps/regression/calculation_controller.cpp @@ -25,7 +25,7 @@ CalculationController::CalculationController(Responder * parentResponder, Button m_hideableCell(), m_store(store) { - m_r2Layout = HorizontalLayout::Builder(CodePointLayout::Builder('r', KDFont::SmallFont), VerticalOffsetLayout::Builder(CodePointLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript)); + m_r2Layout = HorizontalLayout::Builder(CodePointLayout::Builder('r', KDFont::SmallFont), VerticalOffsetLayout::Builder(CodePointLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Position::Superscript)); m_selectableTableView.setVerticalCellOverlap(0); m_selectableTableView.setBackgroundColor(Palette::WallScreenDark); m_selectableTableView.setMargins(k_margin, k_scrollBarMargin, k_scrollBarMargin, k_margin); @@ -65,7 +65,10 @@ void CalculationController::didBecomeFirstResponder() { TabTableController::didBecomeFirstResponder(); } -void CalculationController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { +void CalculationController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { + if (withinTemporarySelection) { + return; + } /* To prevent selecting cell with no content (top left corner of the table), * as soon as the selected cell is the top left corner, we either reselect * the previous cell or select the tab controller depending on from which cell diff --git a/apps/regression/calculation_controller.h b/apps/regression/calculation_controller.h index e59265a62..00237fa73 100644 --- a/apps/regression/calculation_controller.h +++ b/apps/regression/calculation_controller.h @@ -29,7 +29,7 @@ public: void didBecomeFirstResponder() override; // SelectableTableViewDelegate - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; // AlternateEmptyViewDefaultDelegate bool isEmpty() const override; diff --git a/apps/regression/model/cubic_model.cpp b/apps/regression/model/cubic_model.cpp index 7a1febc48..32bd4d4af 100644 --- a/apps/regression/model/cubic_model.cpp +++ b/apps/regression/model/cubic_model.cpp @@ -26,7 +26,7 @@ Layout CubicModel::layout() { CodePointLayout::Builder('X', k_layoutFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('3', k_layoutFont), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ), CodePointLayout::Builder('+', k_layoutFont), CodePointLayout::Builder('b', k_layoutFont), @@ -34,7 +34,7 @@ Layout CubicModel::layout() { CodePointLayout::Builder('X', k_layoutFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('2', k_layoutFont), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ), CodePointLayout::Builder('+', k_layoutFont), CodePointLayout::Builder('c', k_layoutFont), diff --git a/apps/regression/model/exponential_model.cpp b/apps/regression/model/exponential_model.cpp index be49691c0..e29bdb047 100644 --- a/apps/regression/model/exponential_model.cpp +++ b/apps/regression/model/exponential_model.cpp @@ -22,7 +22,7 @@ Layout ExponentialModel::layout() { CodePointLayout::Builder(UCodePointMiddleDot, k_layoutFont), CodePointLayout::Builder('X', k_layoutFont) ), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ) }; m_layout = HorizontalLayout::Builder(layoutChildren, size); diff --git a/apps/regression/model/logistic_model.cpp b/apps/regression/model/logistic_model.cpp index 9ff69660f..5ffe02a48 100644 --- a/apps/regression/model/logistic_model.cpp +++ b/apps/regression/model/logistic_model.cpp @@ -28,7 +28,7 @@ Layout LogisticModel::layout() { CodePointLayout::Builder('e', k_layoutFont), VerticalOffsetLayout::Builder( HorizontalLayout::Builder(exponentLayoutChildren, exponentSize), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ) }; m_layout = FractionLayout::Builder( diff --git a/apps/regression/model/power_model.cpp b/apps/regression/model/power_model.cpp index 779d290a7..928e25514 100644 --- a/apps/regression/model/power_model.cpp +++ b/apps/regression/model/power_model.cpp @@ -19,7 +19,7 @@ Layout PowerModel::layout() { CodePointLayout::Builder('X', k_layoutFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('b', k_layoutFont), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ), }; m_layout = HorizontalLayout::Builder(layoutChildren, size); diff --git a/apps/regression/model/quadratic_model.cpp b/apps/regression/model/quadratic_model.cpp index 51b64eceb..a2cb20400 100644 --- a/apps/regression/model/quadratic_model.cpp +++ b/apps/regression/model/quadratic_model.cpp @@ -26,7 +26,7 @@ Layout QuadraticModel::layout() { CodePointLayout::Builder('X', k_layoutFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('2', k_layoutFont), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ), CodePointLayout::Builder('+', k_layoutFont), CodePointLayout::Builder('b', k_layoutFont), diff --git a/apps/regression/model/quartic_model.cpp b/apps/regression/model/quartic_model.cpp index 84d021e6d..09388c0a3 100644 --- a/apps/regression/model/quartic_model.cpp +++ b/apps/regression/model/quartic_model.cpp @@ -26,7 +26,7 @@ Layout QuarticModel::layout() { CodePointLayout::Builder('X', k_layoutFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('4', k_layoutFont), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ), CodePointLayout::Builder('+', k_layoutFont), CodePointLayout::Builder('b', k_layoutFont), @@ -34,7 +34,7 @@ Layout QuarticModel::layout() { CodePointLayout::Builder('X', k_layoutFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('3', k_layoutFont), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ), CodePointLayout::Builder('+', k_layoutFont), CodePointLayout::Builder('c', k_layoutFont), @@ -42,7 +42,7 @@ Layout QuarticModel::layout() { CodePointLayout::Builder('X', k_layoutFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('2', k_layoutFont), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ), CodePointLayout::Builder('+', k_layoutFont), CodePointLayout::Builder('d', k_layoutFont), diff --git a/apps/sequence/cache_context.cpp b/apps/sequence/cache_context.cpp index 96fb44608..0645e34e5 100644 --- a/apps/sequence/cache_context.cpp +++ b/apps/sequence/cache_context.cpp @@ -8,9 +8,9 @@ namespace Sequence { template CacheContext::CacheContext(Context * parentContext) : - VariableContext("n", parentContext), m_values{{NAN, NAN}, - {NAN, NAN}} + {NAN, NAN}}, + m_parentContext(parentContext) { } @@ -24,7 +24,12 @@ const Expression CacheContext::expressionForSymbol(const SymbolAbstract & sym Symbol s = const_cast(static_cast(symbol)); return Float::Builder(m_values[nameIndexForSymbol(s)][rankIndexForSymbol(s)]); } - return VariableContext::expressionForSymbol(symbol, clone); + return m_parentContext->expressionForSymbol(symbol, clone); +} + +template +void CacheContext::setExpressionForSymbol(const Expression & expression, const SymbolAbstract & symbol, Context & context) { + m_parentContext->setExpressionForSymbol(expression, symbol, context); } template diff --git a/apps/sequence/cache_context.h b/apps/sequence/cache_context.h index e9dc40560..011596ece 100644 --- a/apps/sequence/cache_context.h +++ b/apps/sequence/cache_context.h @@ -4,21 +4,22 @@ #include #include #include -#include #include "sequence_context.h" namespace Sequence { template -class CacheContext : public Poincare::VariableContext { +class CacheContext : public Poincare::Context { public: CacheContext(Poincare::Context * parentContext); const Poincare::Expression expressionForSymbol(const Poincare::SymbolAbstract & symbol, bool clone) override; + void setExpressionForSymbol(const Poincare::Expression & expression, const Poincare::SymbolAbstract & symbol, Poincare::Context & context) 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/list/list_parameter_controller.cpp b/apps/sequence/list/list_parameter_controller.cpp index cc0f25fcf..ac36094be 100644 --- a/apps/sequence/list/list_parameter_controller.cpp +++ b/apps/sequence/list/list_parameter_controller.cpp @@ -92,8 +92,8 @@ bool ListParameterController::textFieldDidFinishEditing(TextField * textField, c return true; } -void ListParameterController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { - if (previousSelectedCellX == t->selectedColumn() && previousSelectedCellY == t->selectedRow()) { +void ListParameterController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { + if (withinTemporarySelection || (previousSelectedCellX == t->selectedColumn() && previousSelectedCellY == t->selectedRow())) { return; } if (!hasInitialRankRow()) { diff --git a/apps/sequence/list/list_parameter_controller.h b/apps/sequence/list/list_parameter_controller.h index 09ff8c4cd..e43ee5d43 100644 --- a/apps/sequence/list/list_parameter_controller.h +++ b/apps/sequence/list/list_parameter_controller.h @@ -19,7 +19,7 @@ public: bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; Shared::TextFieldDelegateApp * textFieldDelegateApp() override; // ListViewDataSource diff --git a/apps/sequence/list/sequence_toolbox.cpp b/apps/sequence/list/sequence_toolbox.cpp index b71c41246..e6281baae 100644 --- a/apps/sequence/list/sequence_toolbox.cpp +++ b/apps/sequence/list/sequence_toolbox.cpp @@ -78,18 +78,18 @@ void SequenceToolbox::buildExtraCellsLayouts(const char * sequenceName, int recu const char * indice = j == 0 ? "n" : "n+1"; m_addedCellLayout[j] = HorizontalLayout::Builder( CodePointLayout::Builder(sequenceName[0], KDFont::LargeFont), - VerticalOffsetLayout::Builder(LayoutHelper::String(indice, strlen(indice), KDFont::LargeFont), VerticalOffsetLayoutNode::Type::Subscript) + VerticalOffsetLayout::Builder(LayoutHelper::String(indice, strlen(indice), KDFont::LargeFont), VerticalOffsetLayoutNode::Position::Subscript) ); m_addedCellLayout[j+recurrenceDepth] = HorizontalLayout::Builder( CodePointLayout::Builder(otherSequenceName[0], KDFont::LargeFont), - VerticalOffsetLayout::Builder(LayoutHelper::String(indice, strlen(indice), KDFont::LargeFont), VerticalOffsetLayoutNode::Type::Subscript) + VerticalOffsetLayout::Builder(LayoutHelper::String(indice, strlen(indice), KDFont::LargeFont), VerticalOffsetLayoutNode::Position::Subscript) ); } if (recurrenceDepth < 2) { const char * indice = recurrenceDepth == 0 ? "n" : (recurrenceDepth == 1 ? "n+1" : "n+2"); m_addedCellLayout[2*recurrenceDepth] = HorizontalLayout::Builder( CodePointLayout::Builder(otherSequenceName[0], KDFont::LargeFont), - VerticalOffsetLayout::Builder(LayoutHelper::String(indice, strlen(indice), KDFont::LargeFont), VerticalOffsetLayoutNode::Type::Subscript) + VerticalOffsetLayout::Builder(LayoutHelper::String(indice, strlen(indice), KDFont::LargeFont), VerticalOffsetLayoutNode::Position::Subscript) ); } } diff --git a/apps/sequence/list/type_parameter_controller.cpp b/apps/sequence/list/type_parameter_controller.cpp index ab01f1c43..aa2e9b4e7 100644 --- a/apps/sequence/list/type_parameter_controller.cpp +++ b/apps/sequence/list/type_parameter_controller.cpp @@ -124,7 +124,7 @@ void TypeParameterController::willDisplayCellAtLocation(HighlightCell * cell, in const char * subscripts[3] = {"n", "n+1", "n+2"}; m_layouts[j] = HorizontalLayout::Builder( CodePointLayout::Builder(nextName[0], font), - VerticalOffsetLayout::Builder(LayoutHelper::String(subscripts[j], strlen(subscripts[j]), font), VerticalOffsetLayoutNode::Type::Subscript) + VerticalOffsetLayout::Builder(LayoutHelper::String(subscripts[j], strlen(subscripts[j]), font), VerticalOffsetLayoutNode::Position::Subscript) ); ExpressionTableCellWithPointer * myCell = (ExpressionTableCellWithPointer *)cell; myCell->setLayout(m_layouts[j]); diff --git a/apps/sequence/sequence.cpp b/apps/sequence/sequence.cpp index 0f2a359d7..cf03d9e9e 100644 --- a/apps/sequence/sequence.cpp +++ b/apps/sequence/sequence.cpp @@ -78,7 +78,7 @@ Poincare::Layout Sequence::nameLayout() { if (m_nameLayout.isUninitialized()) { m_nameLayout = HorizontalLayout::Builder( CodePointLayout::Builder(fullName()[0], KDFont::SmallFont), - VerticalOffsetLayout::Builder(CodePointLayout::Builder(Symbol(), KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Subscript) + VerticalOffsetLayout::Builder(CodePointLayout::Builder(Symbol(), KDFont::SmallFont), VerticalOffsetLayoutNode::Position::Subscript) ); } return m_nameLayout; @@ -92,7 +92,7 @@ bool Sequence::isDefined() { case Type::SingleRecurrence: return data->initialConditionSize(0) > 0 && value().size > metaDataSize() + data->initialConditionSize(0); default: - return data->initialConditionSize(0) > 0 && data->initialConditionSize(0) > 0 && value().size > metaDataSize() + data->initialConditionSize(0) + data->initialConditionSize(1); + return data->initialConditionSize(0) > 0 && data->initialConditionSize(1) > 0 && value().size > metaDataSize() + data->initialConditionSize(0) + data->initialConditionSize(1); } } @@ -236,16 +236,16 @@ void Sequence::DefinitionModel::buildName(Sequence * sequence) { if (sequence->type() == Type::Explicit) { m_name = HorizontalLayout::Builder( CodePointLayout::Builder(name, k_layoutFont), - VerticalOffsetLayout::Builder(LayoutHelper::String("n", 1, k_layoutFont), VerticalOffsetLayoutNode::Type::Subscript)); + VerticalOffsetLayout::Builder(LayoutHelper::String("n", 1, k_layoutFont), VerticalOffsetLayoutNode::Position::Subscript)); } else if (sequence->type() == Type::SingleRecurrence) { m_name = HorizontalLayout::Builder( CodePointLayout::Builder(name, k_layoutFont), - VerticalOffsetLayout::Builder(LayoutHelper::String("n+1", 3, k_layoutFont), VerticalOffsetLayoutNode::Type::Subscript)); + VerticalOffsetLayout::Builder(LayoutHelper::String("n+1", 3, k_layoutFont), VerticalOffsetLayoutNode::Position::Subscript)); } else { assert(sequence->type() == Type::DoubleRecurrence); m_name = HorizontalLayout::Builder( CodePointLayout::Builder(name, k_layoutFont), - VerticalOffsetLayout::Builder(LayoutHelper::String("n+2", 3, k_layoutFont), VerticalOffsetLayoutNode::Type::Subscript)); + VerticalOffsetLayout::Builder(LayoutHelper::String("n+2", 3, k_layoutFont), VerticalOffsetLayoutNode::Position::Subscript)); } } @@ -273,7 +273,7 @@ void Sequence::InitialConditionModel::buildName(Sequence * sequence) { Layout indexLayout = LayoutHelper::String(buffer, strlen(buffer), k_layoutFont); m_name = HorizontalLayout::Builder( CodePointLayout::Builder(sequence->fullName()[0], k_layoutFont), - VerticalOffsetLayout::Builder(indexLayout, VerticalOffsetLayoutNode::Type::Subscript)); + VerticalOffsetLayout::Builder(indexLayout, VerticalOffsetLayoutNode::Position::Subscript)); } template double Sequence::templatedApproximateAtAbscissa(double, SequenceContext*) const; diff --git a/apps/settings/sub_menu/preferences_controller.cpp b/apps/settings/sub_menu/preferences_controller.cpp index fb53112d4..527a4a530 100644 --- a/apps/settings/sub_menu/preferences_controller.cpp +++ b/apps/settings/sub_menu/preferences_controller.cpp @@ -94,7 +94,7 @@ Layout PreferencesController::layoutForPreferences(I18n::Message message) { const char * superscript = "𝐢θ"; return HorizontalLayout::Builder( LayoutHelper::String(base, strlen(base), k_layoutFont), - VerticalOffsetLayout::Builder(LayoutHelper::String(superscript, strlen(superscript), k_layoutFont), VerticalOffsetLayoutNode::Type::Superscript) + VerticalOffsetLayout::Builder(LayoutHelper::String(superscript, strlen(superscript), k_layoutFont), VerticalOffsetLayoutNode::Position::Superscript) ); } default: diff --git a/apps/shared/expression_model_list_controller.cpp b/apps/shared/expression_model_list_controller.cpp index fe4a33aa4..1bca31721 100644 --- a/apps/shared/expression_model_list_controller.cpp +++ b/apps/shared/expression_model_list_controller.cpp @@ -16,11 +16,11 @@ ExpressionModelListController::ExpressionModelListController(Responder * parentR m_addNewModel.setMessage(text); } -void ExpressionModelListController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { +void ExpressionModelListController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { int currentSelectedRow = selectedRow(); // Update m_cumulatedHeightForSelectedIndex if we scrolled one cell up/down - if (previousSelectedCellY >= 0 && previousSelectedCellY == previousSelectedCellY + 1) { + if (currentSelectedRow >= 0 && currentSelectedRow == previousSelectedCellY + 1) { /* We selected the cell under the previous cell. Shift the memoized cell * heights. */ shiftMemoization(true); diff --git a/apps/shared/expression_model_list_controller.h b/apps/shared/expression_model_list_controller.h index 9fc00367f..82ae4db1a 100644 --- a/apps/shared/expression_model_list_controller.h +++ b/apps/shared/expression_model_list_controller.h @@ -13,7 +13,7 @@ public: protected: static constexpr KDCoordinate k_expressionMargin = 5; // SelectableTableViewDelegate - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; // TableViewDataSource virtual int numberOfExpressionRows(); KDCoordinate memoizedRowHeight(int j); diff --git a/apps/shared/function_list_controller.cpp b/apps/shared/function_list_controller.cpp index cc65e5211..9dfa8dce2 100644 --- a/apps/shared/function_list_controller.cpp +++ b/apps/shared/function_list_controller.cpp @@ -214,11 +214,11 @@ void FunctionListController::willExitResponderChain(Responder * nextFirstRespond /* SelectableTableViewDelegate */ -void FunctionListController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { +void FunctionListController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { // Update memoization of cell heights - ExpressionModelListController::tableViewDidChangeSelection(t, previousSelectedCellX, previousSelectedCellY); + ExpressionModelListController::tableViewDidChangeSelection(t, previousSelectedCellX, previousSelectedCellY, withinTemporarySelection); // Do not select the cell left of the "addEmptyFunction" cell - if (isAddEmptyRow(selectedRow()) && selectedColumn() == 0) { + if (!withinTemporarySelection && isAddEmptyRow(selectedRow()) && selectedColumn() == 0) { t->selectCellAtLocation(1, numberOfRows()-1); } } diff --git a/apps/shared/function_list_controller.h b/apps/shared/function_list_controller.h index 273edd401..8bc0dd055 100644 --- a/apps/shared/function_list_controller.h +++ b/apps/shared/function_list_controller.h @@ -45,7 +45,7 @@ public: View * view() override { return &m_selectableTableView; } /* SelectableTableViewDelegate*/ - void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; + void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) override; /* ExpressionModelListController */ SelectableTableView * selectableTableView() override { return &m_selectableTableView; } diff --git a/apps/shared/scrollable_exact_approximate_expressions_cell.cpp b/apps/shared/scrollable_exact_approximate_expressions_cell.cpp index e9c456b75..cfb76a462 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_cell.cpp +++ b/apps/shared/scrollable_exact_approximate_expressions_cell.cpp @@ -21,15 +21,12 @@ void ScrollableExactApproximateExpressionsCell::setEven(bool even) { m_view.evenOddCell()->setEven(even); } -void ScrollableExactApproximateExpressionsCell::reloadCell() { - m_view.evenOddCell()->reloadCell(); -} - void ScrollableExactApproximateExpressionsCell::reloadScroll() { m_view.reloadScroll(); } void ScrollableExactApproximateExpressionsCell::didBecomeFirstResponder() { + m_view.setSelectedSubviewPosition(ScrollableExactApproximateExpressionsView::SubviewPosition::Left); app()->setFirstResponder(&m_view); } diff --git a/apps/shared/scrollable_exact_approximate_expressions_cell.h b/apps/shared/scrollable_exact_approximate_expressions_cell.h index 3d8a8209f..fd3da4ace 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_cell.h +++ b/apps/shared/scrollable_exact_approximate_expressions_cell.h @@ -17,7 +17,6 @@ public: } void setHighlighted(bool highlight) override; void setEven(bool even) override; - void reloadCell() override; void reloadScroll(); Responder * responder() override { return this; diff --git a/apps/shared/scrollable_exact_approximate_expressions_view.cpp b/apps/shared/scrollable_exact_approximate_expressions_view.cpp index 0e003e857..d878def8b 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_view.cpp +++ b/apps/shared/scrollable_exact_approximate_expressions_view.cpp @@ -25,6 +25,7 @@ void ScrollableExactApproximateExpressionsView::ContentCell::setHighlighted(bool m_highlighted = highlight; m_leftExpressionView.setBackgroundColor(backgroundColor()); m_rightExpressionView.setBackgroundColor(backgroundColor()); + m_approximateSign.setBackgroundColor(backgroundColor()); if (highlight) { if (m_selectedSubviewPosition == SubviewPosition::Left) { m_leftExpressionView.setBackgroundColor(Palette::Select); @@ -34,15 +35,19 @@ void ScrollableExactApproximateExpressionsView::ContentCell::setHighlighted(bool } } -void ScrollableExactApproximateExpressionsView::ContentCell::reloadCell() { - setHighlighted(isHighlighted()); +void ScrollableExactApproximateExpressionsView::ContentCell::setEven(bool even) { + EvenOddCell::setEven(even); + m_leftExpressionView.setBackgroundColor(backgroundColor()); + m_rightExpressionView.setBackgroundColor(backgroundColor()); m_approximateSign.setBackgroundColor(backgroundColor()); +} + +void ScrollableExactApproximateExpressionsView::ContentCell::reloadTextColor() { if (numberOfSubviews() == 1) { m_rightExpressionView.setTextColor(KDColorBlack); } else { m_rightExpressionView.setTextColor(Palette::GreyVeryDark); } - layoutSubviews(); } KDSize ScrollableExactApproximateExpressionsView::ContentCell::minimalSizeForOptimalDisplay() const { @@ -114,19 +119,33 @@ ScrollableExactApproximateExpressionsView::ScrollableExactApproximateExpressions } void ScrollableExactApproximateExpressionsView::setLayouts(Poincare::Layout rightLayout, Poincare::Layout leftLayout) { - m_contentCell.rightExpressionView()->setLayout(rightLayout); - m_contentCell.leftExpressionView()->setLayout(leftLayout); - m_contentCell.layoutSubviews(); + bool updateRightLayout = m_contentCell.rightExpressionView()->setLayout(rightLayout); + bool updateLeftLayout = m_contentCell.leftExpressionView()->setLayout(leftLayout); + if (updateRightLayout || updateLeftLayout) { + m_contentCell.reloadTextColor(); + m_contentCell.layoutSubviews(); + } } void ScrollableExactApproximateExpressionsView::setEqualMessage(I18n::Message equalSignMessage) { m_contentCell.approximateSign()->setMessage(equalSignMessage); } +void ScrollableExactApproximateExpressionsView::reloadScroll() { + if (selectedSubviewPosition() == SubviewPosition::Left) { + // Scroll to the left extremity + ScrollableView::reloadScroll(); + } else { + // Scroll to the right extremity + scrollToContentPoint(KDPoint(m_contentCell.bounds().width(), 0), true); + } +} + void ScrollableExactApproximateExpressionsView::didBecomeFirstResponder() { if (m_contentCell.leftExpressionView()->layout().isUninitialized()) { setSelectedSubviewPosition(SubviewPosition::Right); - } else { + } + if (m_contentCell.rightExpressionView()->layout().isUninitialized()) { setSelectedSubviewPosition(SubviewPosition::Left); } } @@ -135,9 +154,9 @@ bool ScrollableExactApproximateExpressionsView::handleEvent(Ion::Events::Event e if (m_contentCell.leftExpressionView()->layout().isUninitialized()) { return ScrollableView::handleEvent(event); } - bool rightExpressionIsVisible = minimalSizeForOptimalDisplay().width() - m_contentCell.rightExpressionView()->minimalSizeForOptimalDisplay().width() - m_manualScrollingOffset.x() < bounds().width() + bool rightExpressionIsVisible = minimalSizeForOptimalDisplay().width() - m_contentCell.rightExpressionView()->minimalSizeForOptimalDisplay().width() - contentOffset().x() < bounds().width() ; - bool leftExpressionIsVisible = m_contentCell.leftExpressionView()->minimalSizeForOptimalDisplay().width() - m_manualScrollingOffset.x() > 0; + bool leftExpressionIsVisible = m_contentCell.leftExpressionView()->minimalSizeForOptimalDisplay().width() - contentOffset().x() > 0; if ((event == Ion::Events::Right && selectedSubviewPosition() == SubviewPosition::Left && rightExpressionIsVisible) || (event == Ion::Events::Left && selectedSubviewPosition() == SubviewPosition::Right && leftExpressionIsVisible)) { SubviewPosition otherSubviewPosition = selectedSubviewPosition() == SubviewPosition::Left ? SubviewPosition::Right : SubviewPosition::Left; diff --git a/apps/shared/scrollable_exact_approximate_expressions_view.h b/apps/shared/scrollable_exact_approximate_expressions_view.h index 38a35357e..31d2afd87 100644 --- a/apps/shared/scrollable_exact_approximate_expressions_view.h +++ b/apps/shared/scrollable_exact_approximate_expressions_view.h @@ -23,6 +23,7 @@ public: void setSelectedSubviewPosition(SubviewPosition subviewPosition) { m_contentCell.setSelectedSubviewPosition(subviewPosition); } + void reloadScroll(); void didBecomeFirstResponder() override; bool handleEvent(Ion::Events::Event event) override; Poincare::Layout layout() const { @@ -34,7 +35,8 @@ private: ContentCell(); KDColor backgroundColor() const override; void setHighlighted(bool highlight) override; - void reloadCell() override; + void setEven(bool even) override; + void reloadTextColor(); KDSize minimalSizeForOptimalDisplay() const override; ExpressionView * rightExpressionView() { return &m_rightExpressionView; diff --git a/apps/shared/text_field_delegate_app.cpp b/apps/shared/text_field_delegate_app.cpp index d7b2d2582..720b6c0b1 100644 --- a/apps/shared/text_field_delegate_app.cpp +++ b/apps/shared/text_field_delegate_app.cpp @@ -57,7 +57,7 @@ bool TextFieldDelegateApp::fieldDidReceiveEvent(EditableField * field, Responder /* TODO decode here to encode again in handleEventWithText? */ constexpr int bufferSize = CodePoint::MaxCodePointCharLength+1; char buffer[bufferSize]; - size_t length = UTF8Decoder::CodePointToChars(XNT(), buffer, bufferSize); + size_t length = UTF8Decoder::CodePointToChars(field->XNTCodePoint(XNT()), buffer, bufferSize); assert(length < bufferSize - 1); buffer[length] = 0; return field->handleEventWithText(buffer); diff --git a/apps/solver/list_controller.cpp b/apps/solver/list_controller.cpp index bd7f0eac4..bd8cb69a0 100644 --- a/apps/solver/list_controller.cpp +++ b/apps/solver/list_controller.cpp @@ -122,7 +122,7 @@ bool textRepresentsAnEquality(const char * text) { bool layoutRepresentsAnEquality(Poincare::Layout l) { Poincare::Layout match = l.recursivelyMatches( [](Poincare::Layout layout) { - return layout.isCodePoint() && static_cast(layout).codePoint() == '='; }); + return layout.type() == Poincare::LayoutNode::Type::CodePointLayout && static_cast(layout).codePoint() == '='; }); return !match.isUninitialized(); } diff --git a/apps/solver/solutions_controller.cpp b/apps/solver/solutions_controller.cpp index 68041e423..77e2138e4 100644 --- a/apps/solver/solutions_controller.cpp +++ b/apps/solver/solutions_controller.cpp @@ -77,7 +77,7 @@ SolutionsController::SolutionsController(Responder * parentResponder, EquationSt m_delta2Layout(), m_contentView(this) { - m_delta2Layout = HorizontalLayout::Builder(VerticalOffsetLayout::Builder(CodePointLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript), LayoutHelper::String("-4ac", 4, KDFont::SmallFont)); + 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, 3, KDFont::SmallFont), 0, false); for (int i = 0; i < EquationStore::k_maxNumberOfExactSolutions; i++) { @@ -203,7 +203,6 @@ void SolutionsController::willDisplayCellAtLocation(HighlightCell * cell, int i, } EvenOddCell * evenOddCell = static_cast(cell); evenOddCell->setEven(j%2 == 0); - evenOddCell->reloadCell(); } KDCoordinate SolutionsController::columnWidth(int i) { diff --git a/escher/include/escher/expression_view.h b/escher/include/escher/expression_view.h index 0a1bdb7fe..b11ea2db6 100644 --- a/escher/include/escher/expression_view.h +++ b/escher/include/escher/expression_view.h @@ -16,7 +16,7 @@ public: ExpressionView(float horizontalAlignment = 0.0f, float verticalAlignment = 0.5f, KDColor textColor = KDColorBlack, KDColor backgroundColor = KDColorWhite); Poincare::Layout layout() const { return m_layout; } - void setLayout(Poincare::Layout layout); + bool setLayout(Poincare::Layout layout); void drawRect(KDContext * ctx, KDRect rect) const override; void setBackgroundColor(KDColor backgroundColor); void setTextColor(KDColor textColor); diff --git a/escher/include/escher/scrollable_view.h b/escher/include/escher/scrollable_view.h index ba4aaea4e..4f1996461 100644 --- a/escher/include/escher/scrollable_view.h +++ b/escher/include/escher/scrollable_view.h @@ -12,9 +12,6 @@ public: void reloadScroll(bool forceRelayout = false); protected: KDSize contentSize() const override; - KDPoint m_manualScrollingOffset; }; #endif - - diff --git a/escher/include/escher/selectable_table_view.h b/escher/include/escher/selectable_table_view.h index bc58044ec..cecd44a74 100644 --- a/escher/include/escher/selectable_table_view.h +++ b/escher/include/escher/selectable_table_view.h @@ -29,8 +29,8 @@ public: virtual bool handleEvent(Ion::Events::Event event) override; virtual void didEnterResponderChain(Responder * previousFirstResponder) override; virtual void willExitResponderChain(Responder * nextFirstResponder) override; - void deselectTable(); - bool selectCellAtLocation(int i, int j, bool setFirstResponder = true); + void deselectTable(bool withinTemporarySelection = false); + bool selectCellAtLocation(int i, int j, bool setFirstResponder = true, bool withinTemporarySelection = false); HighlightCell * selectedCell(); protected: SelectableTableViewDataSource * m_selectionDataSource; diff --git a/escher/include/escher/selectable_table_view_delegate.h b/escher/include/escher/selectable_table_view_delegate.h index 33249f3f9..e8f11864a 100644 --- a/escher/include/escher/selectable_table_view_delegate.h +++ b/escher/include/escher/selectable_table_view_delegate.h @@ -5,7 +5,12 @@ class SelectableTableView; class SelectableTableViewDelegate { public: - virtual void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY); + /* 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); }; #endif diff --git a/escher/include/escher/table_view.h b/escher/include/escher/table_view.h index ca2db0d32..5c16a8c30 100644 --- a/escher/include/escher/table_view.h +++ b/escher/include/escher/table_view.h @@ -34,7 +34,6 @@ protected: void setHorizontalCellOverlap(KDCoordinate o) { m_horizontalCellOverlap = o; } void setVerticalCellOverlap(KDCoordinate o) { m_verticalCellOverlap = o; } - void scrollToCell(int i, int j) const; void reloadCellAtLocation(int i, int j); HighlightCell * cellAtLocation(int i, int j); TableViewDataSource * dataSource(); @@ -42,6 +41,7 @@ protected: int columnsScrollingOffset() const; int numberOfDisplayableRows() const; int numberOfDisplayableColumns() const; + KDRect cellFrame(int i, int j) const; void layoutSubviews() override; protected: #if ESCHER_VIEW_LOGGING @@ -54,9 +54,6 @@ protected: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - /* realCellWidth enables to handle list view for which - * TableViewDataSource->cellWidht = 0 */ - KDRect cellFrame(int i, int j) const; /* These two methods transform an index (of subview for instance) into * coordinates that refer to the data source entire table */ int absoluteColumnNumberFromSubviewIndex(int index) const; diff --git a/escher/src/expression_view.cpp b/escher/src/expression_view.cpp index 6360470f8..64cf5c47f 100644 --- a/escher/src/expression_view.cpp +++ b/escher/src/expression_view.cpp @@ -14,9 +14,13 @@ ExpressionView::ExpressionView(float horizontalAlignment, float verticalAlignmen { } -void ExpressionView::setLayout(Layout layoutR) { +bool ExpressionView::setLayout(Layout layoutR) { + if (m_layout.isIdenticalTo(layoutR)) { + return false; + } m_layout = layoutR; markRectAsDirty(bounds()); + return true; } void ExpressionView::setBackgroundColor(KDColor backgroundColor) { diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index 5fba22bea..a25088ef0 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -28,8 +28,9 @@ void LayoutField::ContentView::setEditing(bool isEditing) { void LayoutField::ContentView::clearLayout() { HorizontalLayout h = HorizontalLayout::Builder(); - m_expressionView.setLayout(h); - m_cursor.setLayout(h); + if (m_expressionView.setLayout(h)) { + m_cursor.setLayout(h); + } } KDSize LayoutField::ContentView::minimalSizeForOptimalDisplay() const { @@ -136,10 +137,10 @@ bool LayoutField::handleEventWithText(const char * text, bool indentation, bool /* Special case: if the text is "random()", the cursor should not be set * inside the parentheses. */ pointedLayout = resultLayout; - } else if (resultLayout.isHorizontal()) { + } else if (resultLayout.type() == LayoutNode::Type::HorizontalLayout) { pointedLayout = resultLayout.recursivelyMatches( [](Poincare::Layout layout) { - return layout.isLeftParenthesis() || layout.isEmpty();}); + return layout.type() == LayoutNode::Type::LeftParenthesisLayout || layout.isEmpty();}); } } /* Insert the layout. If pointedLayout is uninitialized, the cursor will @@ -294,7 +295,7 @@ void LayoutField::insertLayoutAtCursor(Layout layoutR, Layout pointedLayoutR, bo // Handle empty layouts m_contentView.cursor()->showEmptyLayoutIfNeeded(); - bool layoutWillBeMerged = layoutR.isHorizontal(); + bool layoutWillBeMerged = layoutR.type() == LayoutNode::Type::HorizontalLayout; Layout lastMergedLayoutChild = layoutWillBeMerged ? layoutR.childAtIndex(layoutR.numberOfChildren()-1) : Layout(); // Add the layout diff --git a/escher/src/scrollable_view.cpp b/escher/src/scrollable_view.cpp index 9efc0fd1e..5d5e90f57 100644 --- a/escher/src/scrollable_view.cpp +++ b/escher/src/scrollable_view.cpp @@ -7,8 +7,7 @@ static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { retur ScrollableView::ScrollableView(Responder * parentResponder, View * view, ScrollViewDataSource * dataSource) : Responder(parentResponder), - ScrollView(view, dataSource), - m_manualScrollingOffset(KDPointZero) + ScrollView(view, dataSource) { setDecoratorType(ScrollView::Decorator::Type::None); } @@ -16,40 +15,38 @@ ScrollableView::ScrollableView(Responder * parentResponder, View * view, ScrollV bool ScrollableView::handleEvent(Ion::Events::Event event) { KDPoint translation = KDPointZero; if (event == Ion::Events::Left) { - KDCoordinate movementToEdge = m_manualScrollingOffset.x(); + KDCoordinate movementToEdge = contentOffset().x(); if (movementToEdge > 0) { translation = KDPoint(-minCoordinate(Metric::ScrollStep, movementToEdge), 0); } } if (event == Ion::Events::Right) { - KDCoordinate movementToEdge = minimalSizeForOptimalDisplay().width() - bounds().width() - m_manualScrollingOffset.x(); + KDCoordinate movementToEdge = minimalSizeForOptimalDisplay().width() - bounds().width() - contentOffset().x(); if (movementToEdge > 0) { translation = KDPoint(minCoordinate(Metric::ScrollStep, movementToEdge), 0); } } if (event == Ion::Events::Up) { - KDCoordinate movementToEdge = m_manualScrollingOffset.y(); + KDCoordinate movementToEdge = contentOffset().y(); if (movementToEdge > 0) { translation = KDPoint(0, -minCoordinate(Metric::ScrollStep, movementToEdge)); } } if (event == Ion::Events::Down) { - KDCoordinate movementToEdge = minimalSizeForOptimalDisplay().height() - bounds().height() - m_manualScrollingOffset.y(); + KDCoordinate movementToEdge = minimalSizeForOptimalDisplay().height() - bounds().height() - contentOffset().y(); if (movementToEdge > 0) { translation = KDPoint(0, minCoordinate(Metric::ScrollStep, movementToEdge)); } } if (translation != KDPointZero) { - m_manualScrollingOffset = m_manualScrollingOffset.translatedBy(translation); - setContentOffset(m_manualScrollingOffset); + setContentOffset(contentOffset().translatedBy(translation)); return true; } return false; } void ScrollableView::reloadScroll(bool forceReLayout) { - m_manualScrollingOffset = KDPointZero; - setContentOffset(m_manualScrollingOffset, forceReLayout); + setContentOffset(KDPointZero, forceReLayout); } KDSize ScrollableView::contentSize() const { diff --git a/escher/src/selectable_table_view.cpp b/escher/src/selectable_table_view.cpp index 7282164f9..226701a6d 100644 --- a/escher/src/selectable_table_view.cpp +++ b/escher/src/selectable_table_view.cpp @@ -37,7 +37,7 @@ void SelectableTableView::selectColumn(int i) { void SelectableTableView::reloadData(bool setFirstResponder) { int col = selectedColumn(); int row = selectedRow(); - deselectTable(); + deselectTable(true); /* FIXME: The problem with calling deselectTable is that at this point in time * the datasource's model is very likely to have changed. Therefore it's * rather complicated to get a pointer to the currently selected cell (in @@ -45,7 +45,7 @@ void SelectableTableView::reloadData(bool setFirstResponder) { /* As a workaround, datasources can reset the highlighted state in their * willDisplayCell callback. */ TableView::layoutSubviews(); - selectCellAtLocation(col, row, setFirstResponder); + selectCellAtLocation(col, row, setFirstResponder, true); } void SelectableTableView::didEnterResponderChain(Responder * previousFirstResponder) { @@ -60,18 +60,18 @@ void SelectableTableView::willExitResponderChain(Responder * nextFirstResponder) unhighlightSelectedCell(); } -void SelectableTableView::deselectTable() { +void SelectableTableView::deselectTable(bool withinTemporarySelection) { unhighlightSelectedCell(); int previousSelectedCellX = selectedColumn(); int previousSelectedCellY = selectedRow(); selectColumn(0); selectRow(-1); if (m_delegate) { - m_delegate->tableViewDidChangeSelection(this, previousSelectedCellX, previousSelectedCellY); + m_delegate->tableViewDidChangeSelection(this, previousSelectedCellX, previousSelectedCellY, withinTemporarySelection); } } -bool SelectableTableView::selectCellAtLocation(int i, int j, bool setFirstResponder) { +bool SelectableTableView::selectCellAtLocation(int i, int j, bool setFirstResponder, bool withinTemporarySelection) { if (i < 0 || i >= dataSource()->numberOfColumns()) { return false; } @@ -85,7 +85,7 @@ bool SelectableTableView::selectCellAtLocation(int i, int j, bool setFirstRespon selectRow(j); if (m_delegate) { - m_delegate->tableViewDidChangeSelection(this, previousX, previousY); + m_delegate->tableViewDidChangeSelection(this, previousX, previousY, withinTemporarySelection); } /* We need to scroll: diff --git a/escher/src/selectable_table_view_delegate.cpp b/escher/src/selectable_table_view_delegate.cpp index 3a8de3636..25da1557a 100644 --- a/escher/src/selectable_table_view_delegate.cpp +++ b/escher/src/selectable_table_view_delegate.cpp @@ -1,4 +1,4 @@ #include -void SelectableTableViewDelegate::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { +void SelectableTableViewDelegate::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) { } diff --git a/escher/src/table_view.cpp b/escher/src/table_view.cpp index ed0ae9c7c..79345f8f5 100644 --- a/escher/src/table_view.cpp +++ b/escher/src/table_view.cpp @@ -20,7 +20,7 @@ TableViewDataSource * TableView::dataSource() { // This method computes the minimal scrolling needed to properly display the // requested cell. void TableView::scrollToCell(int i, int j) { - m_contentView.scrollToCell(i, j); + scrollToContentRect(m_contentView.cellFrame(i, j), true); } HighlightCell * TableView::cellAtLocation(int i, int j) { @@ -96,10 +96,6 @@ KDCoordinate TableView::ContentView::width() const { return result ? result : m_tableView->maxContentWidthDisplayableWithoutScrolling(); } -void TableView::ContentView::scrollToCell(int x, int y) const { - m_tableView->scrollToContentRect(cellFrame(x, y), true); -} - void TableView::ContentView::reloadCellAtLocation(int i, int j) { HighlightCell * cell = cellAtLocation(i, j); if (cell) { diff --git a/escher/src/text_view.cpp b/escher/src/text_view.cpp index b8bc1b8e9..384b81ea3 100644 --- a/escher/src/text_view.cpp +++ b/escher/src/text_view.cpp @@ -1,13 +1,17 @@ #include void TextView::setBackgroundColor(KDColor backgroundColor) { - m_backgroundColor = backgroundColor; - markRectAsDirty(bounds()); + if (m_backgroundColor != backgroundColor) { + m_backgroundColor = backgroundColor; + markRectAsDirty(bounds()); + } } void TextView::setTextColor(KDColor textColor) { - m_textColor = textColor; - markRectAsDirty(bounds()); + if (m_textColor != textColor) { + m_textColor = textColor; + markRectAsDirty(bounds()); + } } void TextView::setAlignment(float horizontalAlignment, float verticalAlignment) { diff --git a/kandinsky/fonts/LargeSourcePixel.otf b/kandinsky/fonts/LargeSourcePixel.otf index af4a23373..1e6c616ea 100644 Binary files a/kandinsky/fonts/LargeSourcePixel.otf and b/kandinsky/fonts/LargeSourcePixel.otf differ diff --git a/kandinsky/fonts/SmallSourcePixel.otf b/kandinsky/fonts/SmallSourcePixel.otf index 02feffc75..43e3c0e56 100644 Binary files a/kandinsky/fonts/SmallSourcePixel.otf and b/kandinsky/fonts/SmallSourcePixel.otf differ diff --git a/poincare/include/poincare/absolute_value_layout.h b/poincare/include/poincare/absolute_value_layout.h index 5a909f36d..f23b56737 100644 --- a/poincare/include/poincare/absolute_value_layout.h +++ b/poincare/include/poincare/absolute_value_layout.h @@ -11,6 +11,9 @@ class AbsoluteValueLayoutNode final : public BracketPairLayoutNode { public: using BracketPairLayoutNode::BracketPairLayoutNode; + // Layout + Type type() const override { return Type::AbsoluteValueLayout; } + // SerializationHelperInterface int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, AbsoluteValue::s_functionHelper.name()); diff --git a/poincare/include/poincare/binomial_coefficient_layout.h b/poincare/include/poincare/binomial_coefficient_layout.h index 2187496ce..9a6136616 100644 --- a/poincare/include/poincare/binomial_coefficient_layout.h +++ b/poincare/include/poincare/binomial_coefficient_layout.h @@ -11,6 +11,9 @@ class BinomialCoefficientLayoutNode final : public LayoutNode { public: using LayoutNode::LayoutNode; + // Layout + Type type() const override { return Type::BinomialCoefficientLayout; } + // LayoutNode void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; diff --git a/poincare/include/poincare/bracket_pair_layout.h b/poincare/include/poincare/bracket_pair_layout.h index c3e33309b..7505596c8 100644 --- a/poincare/include/poincare/bracket_pair_layout.h +++ b/poincare/include/poincare/bracket_pair_layout.h @@ -10,6 +10,9 @@ class BracketPairLayoutNode : public LayoutNode { public: using LayoutNode::LayoutNode; + // Layout + Type type() const override { return Type::BracketPairLayout; } + static void RenderWithChildSize(KDSize childSize, KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor); // LayoutNode diff --git a/poincare/include/poincare/ceiling_layout.h b/poincare/include/poincare/ceiling_layout.h index 6a3755175..fe3377f5d 100644 --- a/poincare/include/poincare/ceiling_layout.h +++ b/poincare/include/poincare/ceiling_layout.h @@ -12,6 +12,9 @@ class CeilingLayoutNode final : public BracketPairLayoutNode { public: using BracketPairLayoutNode::BracketPairLayoutNode; + // Layout + Type type() const override { return Type::CeilingLayout; } + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, Ceiling::s_functionHelper.name()); } diff --git a/poincare/include/poincare/code_point_layout.h b/poincare/include/poincare/code_point_layout.h index 458641c39..fd765bc93 100644 --- a/poincare/include/poincare/code_point_layout.h +++ b/poincare/include/poincare/code_point_layout.h @@ -20,6 +20,10 @@ public: m_font(font) {} + // Layout + Type type() const override { return Type::CodePointLayout; } + bool isIdenticalTo(Layout l) override; + // CodePointLayout CodePoint codePoint() const { return m_codePoint; } const KDFont * font() const { return m_font; } @@ -28,7 +32,6 @@ public: void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; - bool isCodePoint() const override { return true; } bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override; bool canBeOmittedMultiplicationLeftFactor() const override; bool canBeOmittedMultiplicationRightFactor() const override; diff --git a/poincare/include/poincare/condensed_sum_layout.h b/poincare/include/poincare/condensed_sum_layout.h index 93c8ca9f9..0495cbdb1 100644 --- a/poincare/include/poincare/condensed_sum_layout.h +++ b/poincare/include/poincare/condensed_sum_layout.h @@ -13,6 +13,9 @@ class CondensedSumLayoutNode /*final*/ : public LayoutNode { public: using LayoutNode::LayoutNode; + // Layout + Type type() const override { return Type::CondensedSumLayout; } + /* CondensedSumLayout is only used in apps/shared/sum_graph_controller.cpp, in * a view with no cursor. */ void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override { assert(false); } diff --git a/poincare/include/poincare/conjugate_layout.h b/poincare/include/poincare/conjugate_layout.h index 8dabdf3cf..4f7747d47 100644 --- a/poincare/include/poincare/conjugate_layout.h +++ b/poincare/include/poincare/conjugate_layout.h @@ -10,6 +10,9 @@ class ConjugateLayoutNode final : public LayoutNode { public: using LayoutNode::LayoutNode; + // Layout + Type type() const override { return Type::ConjugateLayout; } + // LayoutNode void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; diff --git a/poincare/include/poincare/empty_layout.h b/poincare/include/poincare/empty_layout.h index cc07406bb..b3fe6a6d8 100644 --- a/poincare/include/poincare/empty_layout.h +++ b/poincare/include/poincare/empty_layout.h @@ -13,6 +13,10 @@ public: Grey }; + // Layout + Type type() const override { return Type::EmptyLayout; } + bool isIdenticalTo(Layout l) override; + EmptyLayoutNode(Color color = Color::Yellow, bool visible = true, const KDFont * font = KDFont::LargeFont, bool margins = true) : LayoutNode(), m_isVisible(visible), diff --git a/poincare/include/poincare/expression.h b/poincare/include/poincare/expression.h index 5f0198e8f..f889f4cc7 100644 --- a/poincare/include/poincare/expression.h +++ b/poincare/include/poincare/expression.h @@ -71,6 +71,7 @@ class Expression : public TreeHandle { friend class PowerNode; friend class PredictionInterval; friend class Product; + friend class Randint; friend class RealPart; friend class Round; friend class SignFunction; diff --git a/poincare/include/poincare/floor_layout.h b/poincare/include/poincare/floor_layout.h index 0e560a917..61e07f26f 100644 --- a/poincare/include/poincare/floor_layout.h +++ b/poincare/include/poincare/floor_layout.h @@ -11,6 +11,10 @@ namespace Poincare { class FloorLayoutNode final : public BracketPairLayoutNode { public: using BracketPairLayoutNode::BracketPairLayoutNode; + + // Layout + Type type() const override { return Type::FloorLayout; } + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { return SerializationHelper::Prefix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, Floor::s_functionHelper.name()); } diff --git a/poincare/include/poincare/fraction_layout.h b/poincare/include/poincare/fraction_layout.h index 03b3979b0..423198210 100644 --- a/poincare/include/poincare/fraction_layout.h +++ b/poincare/include/poincare/fraction_layout.h @@ -10,6 +10,9 @@ class FractionLayoutNode /*final*/ : public LayoutNode { public: using LayoutNode::LayoutNode; + // Layout + Type type() const override { return Type::FractionLayout; } + // LayoutNode void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; diff --git a/poincare/include/poincare/grid_layout.h b/poincare/include/poincare/grid_layout.h index d6f8f8f70..f58fc3bc7 100644 --- a/poincare/include/poincare/grid_layout.h +++ b/poincare/include/poincare/grid_layout.h @@ -22,6 +22,9 @@ public: m_numberOfColumns(0) {} + // Layout + Type type() const override { return Type::GridLayout; } + int numberOfRows() const { return m_numberOfRows; } int numberOfColumns() const { return m_numberOfColumns; } virtual void setNumberOfRows(int numberOfRows) { m_numberOfRows = numberOfRows; } diff --git a/poincare/include/poincare/horizontal_layout.h b/poincare/include/poincare/horizontal_layout.h index 6f11a7b78..1333108a8 100644 --- a/poincare/include/poincare/horizontal_layout.h +++ b/poincare/include/poincare/horizontal_layout.h @@ -18,6 +18,9 @@ public: m_numberOfChildren(0) {} + // Layout + Type type() const override { return Type::HorizontalLayout; } + // LayoutNode void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; @@ -25,7 +28,6 @@ public: void deleteBeforeCursor(LayoutCursor * cursor) override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; - bool isHorizontal() const override { return true; } bool isEmpty() const override { return m_numberOfChildren == 1 && const_cast(this)->childAtIndex(0)->isEmpty(); } bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override { return m_numberOfChildren != 0; } bool hasText() const override; diff --git a/poincare/include/poincare/integral_layout.h b/poincare/include/poincare/integral_layout.h index 9b33b2c40..1a4836c61 100644 --- a/poincare/include/poincare/integral_layout.h +++ b/poincare/include/poincare/integral_layout.h @@ -14,6 +14,9 @@ public: using LayoutNode::LayoutNode; + // Layout + Type type() const override { return Type::IntegralLayout; } + // LayoutNode void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; diff --git a/poincare/include/poincare/layout.h b/poincare/include/poincare/layout.h index 7cffc3ded..8aa19ad4e 100644 --- a/poincare/include/poincare/layout.h +++ b/poincare/include/poincare/layout.h @@ -24,6 +24,10 @@ public: return static_cast(TreeHandle::node()); } + // Properties + LayoutNode::Type type() const { return node()->type(); } + bool isIdenticalTo(Layout l) { return isUninitialized() ? l.isUninitialized() : node()->isIdenticalTo(l); } + // Rendering void draw(KDContext * ctx, KDPoint p, KDColor expressionColor = KDColorBlack, KDColor backgroundColor = KDColorWhite) { return node()->draw(ctx, p, expressionColor, backgroundColor); @@ -45,11 +49,6 @@ public: Layout recursivelyMatches(LayoutTest test) const; bool mustHaveLeftSibling() const { return const_cast(this)->node()->mustHaveLeftSibling(); } bool isEmpty() const { return const_cast(this)->node()->isEmpty(); } - bool isHorizontal() const { return const_cast(this)->node()->isHorizontal(); } - bool isMatrix() const { return const_cast(this)->node()->isMatrix(); } - bool isVerticalOffset() const { return const_cast(this)->node()->isVerticalOffset(); } - bool isLeftParenthesis() const { return const_cast(this)->node()->isLeftParenthesis(); } - bool isCodePoint() const { return const_cast(this)->node()->isCodePoint(); } bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { return const_cast(this)->node()->isCollapsable(numberOfOpenParenthesis, goingLeft); } int leftCollapsingAbsorbingChildIndex() const { return const_cast(this)->node()->leftCollapsingAbsorbingChildIndex(); } int rightCollapsingAbsorbingChildIndex() const { return const_cast(this)->node()->rightCollapsingAbsorbingChildIndex(); } diff --git a/poincare/include/poincare/layout_node.h b/poincare/include/poincare/layout_node.h index bfa3aaa9c..297fa85d8 100644 --- a/poincare/include/poincare/layout_node.h +++ b/poincare/include/poincare/layout_node.h @@ -16,6 +16,30 @@ public: Up, Down }; + enum class Type : uint8_t { + AbsoluteValueLayout, + BinomialCoefficientLayout, + BracketPairLayout, + CeilingLayout, + CodePointLayout, + CondensedSumLayout, + ConjugateLayout, + EmptyLayout, + FloorLayout, + FractionLayout, + GridLayout, + HorizontalLayout, + IntegralLayout, + LeftParenthesisLayout, + LeftSquareBracketLayout, + MatrixLayout, + NthRootLayout, + ProductLayout, + RightParenthesisLayout, + RightSquareBracketLayout, + SumLayout, + VerticalOffsetLayout + }; // Constructor LayoutNode() : @@ -28,6 +52,12 @@ public: { } + /* Poor man's RTTI */ + virtual Type type() const = 0; + + // Comparison + virtual bool isIdenticalTo(Layout l); + // Rendering void draw(KDContext * ctx, KDPoint p, KDColor expressionColor = KDColorBlack, KDColor backgroundColor = KDColorWhite); KDPoint origin(); @@ -93,17 +123,9 @@ public: * returns true, because |3|2 means |3|*2. A '+' CodePointLayout returns false, * because +'something' nevers means +*'something'. */ virtual bool mustHaveLeftSibling() const { return false; } - virtual bool isVerticalOffset() const { return false; } /* For now, mustHaveLeftSibling and isVerticalOffset behave the same, but code * is clearer with different names. */ - virtual bool isHorizontal() const { return false; } - virtual bool isLeftParenthesis() const { return false; } - virtual bool isRightParenthesis() const { return false; } - virtual bool isLeftBracket() const { return false; } - virtual bool isRightBracket() const { return false; } virtual bool isEmpty() const { return false; } - virtual bool isMatrix() const { return false; } - virtual bool isCodePoint() const { return false; } virtual bool hasUpperLeftIndex() const { return false; } virtual CodePoint XNTCodePoint() const { LayoutNode * p = parent(); diff --git a/poincare/include/poincare/left_parenthesis_layout.h b/poincare/include/poincare/left_parenthesis_layout.h index 8aaa41bde..f552a16f7 100644 --- a/poincare/include/poincare/left_parenthesis_layout.h +++ b/poincare/include/poincare/left_parenthesis_layout.h @@ -11,11 +11,13 @@ class LeftParenthesisLayoutNode final : public ParenthesisLayoutNode { public: using ParenthesisLayoutNode::ParenthesisLayoutNode; + // Layout + Type type() const override { return Type::LeftParenthesisLayout; } + static void RenderWithChildHeight(KDCoordinate childHeight, KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor); // Layout Node bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override; - bool isLeftParenthesis() const override { return true; } // Serializable Node int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { diff --git a/poincare/include/poincare/left_square_bracket_layout.h b/poincare/include/poincare/left_square_bracket_layout.h index c4f069c44..6789a741d 100644 --- a/poincare/include/poincare/left_square_bracket_layout.h +++ b/poincare/include/poincare/left_square_bracket_layout.h @@ -10,10 +10,13 @@ namespace Poincare { class LeftSquareBracketLayoutNode final : public SquareBracketLayoutNode { public: using SquareBracketLayoutNode::SquareBracketLayoutNode; + + // Layout + Type type() const override { return Type::LeftSquareBracketLayout; } + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { return SerializationHelper::CodePoint(buffer, bufferSize, '['); } - bool isLeftBracket() const override { return true; } // TreeNode size_t size() const override { return sizeof(LeftSquareBracketLayoutNode); } diff --git a/poincare/include/poincare/matrix_layout.h b/poincare/include/poincare/matrix_layout.h index 21cf04985..3d68602f7 100644 --- a/poincare/include/poincare/matrix_layout.h +++ b/poincare/include/poincare/matrix_layout.h @@ -15,6 +15,9 @@ class MatrixLayoutNode final : public GridLayoutNode { public: using GridLayoutNode::GridLayoutNode; + // Layout + Type type() const override { return Type::MatrixLayout; } + // MatrixLayoutNode void addGreySquares(); void removeGreySquares(); @@ -23,7 +26,6 @@ public: void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void willAddSiblingToEmptyChildAtIndex(int childIndex) override; - bool isMatrix() const override { return true; } // SerializableNode int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; diff --git a/poincare/include/poincare/nth_root_layout.h b/poincare/include/poincare/nth_root_layout.h index 1b2e27fc9..3b19fd2d0 100644 --- a/poincare/include/poincare/nth_root_layout.h +++ b/poincare/include/poincare/nth_root_layout.h @@ -19,6 +19,10 @@ public: m_hasIndex(hasIndex) {} + // Layout + Type type() const override { return Type::NthRootLayout; } + bool isIdenticalTo(Layout l) override; + // LayoutNode void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; diff --git a/poincare/include/poincare/product_layout.h b/poincare/include/poincare/product_layout.h index 3ea063232..fa78f0b1a 100644 --- a/poincare/include/poincare/product_layout.h +++ b/poincare/include/poincare/product_layout.h @@ -9,6 +9,10 @@ namespace Poincare { class ProductLayoutNode final : public SequenceLayoutNode { public: using SequenceLayoutNode::SequenceLayoutNode; + + // Layout + Type type() const override { return Type::ProductLayout; } + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; size_t size() const override { return sizeof(ProductLayoutNode); } #if POINCARE_TREE_LOG diff --git a/poincare/include/poincare/randint.h b/poincare/include/poincare/randint.h index ef2004b60..ace041666 100644 --- a/poincare/include/poincare/randint.h +++ b/poincare/include/poincare/randint.h @@ -36,6 +36,8 @@ private: return templateApproximate(context, complexFormat, angleUnit); } template Evaluation templateApproximate(Context& context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; + // Simplification + Expression shallowReduce(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ReductionTarget target, bool symbolicComputation) override; }; class Randint final : public Expression { @@ -44,6 +46,8 @@ public: Randint(const RandintNode * n) : Expression(n) {} static Randint Builder(Expression child0, Expression child1) { return TreeHandle::FixedArityBuilder(ArrayBuilder(child0, child1).array(), 2); } static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("randint", 2, &UntypedBuilderTwoChildren); +private: + Expression shallowReduce(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::ReductionTarget target); }; } diff --git a/poincare/include/poincare/right_parenthesis_layout.h b/poincare/include/poincare/right_parenthesis_layout.h index 4782c5fe3..42c81295c 100644 --- a/poincare/include/poincare/right_parenthesis_layout.h +++ b/poincare/include/poincare/right_parenthesis_layout.h @@ -11,11 +11,13 @@ class RightParenthesisLayoutNode final : public ParenthesisLayoutNode { public: using ParenthesisLayoutNode::ParenthesisLayoutNode; + // Layout + Type type() const override { return Type::RightParenthesisLayout; } + static void RenderWithChildHeight(KDCoordinate childHeight, KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor); // LayoutNode bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override; - bool isRightParenthesis() const override { return true; } // SerializableNode int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { diff --git a/poincare/include/poincare/right_square_bracket_layout.h b/poincare/include/poincare/right_square_bracket_layout.h index e37132b85..1689e7782 100644 --- a/poincare/include/poincare/right_square_bracket_layout.h +++ b/poincare/include/poincare/right_square_bracket_layout.h @@ -10,10 +10,13 @@ namespace Poincare { class RightSquareBracketLayoutNode final : public SquareBracketLayoutNode { public: using SquareBracketLayoutNode::SquareBracketLayoutNode; + + // Layout + Type type() const override { return Type::RightSquareBracketLayout; } + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override { return SerializationHelper::CodePoint(buffer, bufferSize, ']'); } - bool isRightBracket() const override { return true; } // TreeNode size_t size() const override { return sizeof(RightSquareBracketLayoutNode); } diff --git a/poincare/include/poincare/sum_layout.h b/poincare/include/poincare/sum_layout.h index 57c6206db..ee040c522 100644 --- a/poincare/include/poincare/sum_layout.h +++ b/poincare/include/poincare/sum_layout.h @@ -9,6 +9,10 @@ namespace Poincare { class SumLayoutNode final : public SequenceLayoutNode { public: using SequenceLayoutNode::SequenceLayoutNode; + + // Layout + Type type() const override { return Type::SumLayout; } + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; size_t size() const override { return sizeof(SumLayoutNode); } #if POINCARE_TREE_LOG diff --git a/poincare/include/poincare/tree_pool.h b/poincare/include/poincare/tree_pool.h index 78c8a7342..a124964e3 100644 --- a/poincare/include/poincare/tree_pool.h +++ b/poincare/include/poincare/tree_pool.h @@ -44,7 +44,7 @@ public: #if POINCARE_TREE_LOG void flatLog(std::ostream & stream); void treeLog(std::ostream & stream); - void log() { treeLog(std::cout); } + __attribute__((__used__)) void log() { treeLog(std::cout); } #endif int numberOfNodes() const; diff --git a/poincare/include/poincare/vertical_offset_layout.h b/poincare/include/poincare/vertical_offset_layout.h index 94162aa44..b3ef24c5a 100644 --- a/poincare/include/poincare/vertical_offset_layout.h +++ b/poincare/include/poincare/vertical_offset_layout.h @@ -8,18 +8,22 @@ namespace Poincare { class VerticalOffsetLayoutNode final : public LayoutNode { public: - enum class Type { + enum class Position { Subscript, Superscript }; - VerticalOffsetLayoutNode(Type type = Type::Superscript) : + VerticalOffsetLayoutNode(Position position = Position::Superscript) : LayoutNode(), - m_type(type) + m_position(position) {} + // Layout + Type type() const override { return Type::VerticalOffsetLayout; } + bool isIdenticalTo(Layout l) override; + // VerticalOffsetLayoutNode - Type type() const { return m_type; } + Position position() const { return m_position; } // LayoutNode void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; @@ -29,7 +33,6 @@ public: void deleteBeforeCursor(LayoutCursor * cursor) override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; bool mustHaveLeftSibling() const override { return true; } - bool isVerticalOffset() const override { return true; } bool canBeOmittedMultiplicationRightFactor() const override { return false; } // TreeNode @@ -37,7 +40,7 @@ public: int numberOfChildren() const override { return 1; } #if POINCARE_TREE_LOG virtual void logNodeName(std::ostream & stream) const override { - stream << (m_type == Type::Subscript ? "Subscript" : "Superscript"); + stream << (m_position == Position::Subscript ? "Subscript" : "Superscript"); } #endif @@ -53,12 +56,12 @@ private: void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override {} LayoutNode * indiceLayout() { return childAtIndex(0); } LayoutNode * baseLayout(); - Type m_type; + Position m_position; }; class VerticalOffsetLayout final : public Layout { public: - static VerticalOffsetLayout Builder(Layout l, VerticalOffsetLayoutNode::Type type); + static VerticalOffsetLayout Builder(Layout l, VerticalOffsetLayoutNode::Position position); VerticalOffsetLayout() = delete; }; diff --git a/poincare/src/bracket_layout.cpp b/poincare/src/bracket_layout.cpp index a13f6d040..2b22c8aa5 100644 --- a/poincare/src/bracket_layout.cpp +++ b/poincare/src/bracket_layout.cpp @@ -47,9 +47,9 @@ KDCoordinate BracketLayoutNode::computeBaseline() { assert(parentLayout != nullptr); int idxInParent = parentLayout->indexOfChild(this); int numberOfSiblings = parentLayout->numberOfChildren(); - if (((isLeftParenthesis() || isLeftBracket()) && idxInParent == numberOfSiblings - 1) - || ((isRightParenthesis() || isRightBracket()) && idxInParent == 0) - || ((isLeftParenthesis() || isLeftBracket()) && idxInParent < numberOfSiblings - 1 && parentLayout->childAtIndex(idxInParent + 1)->isVerticalOffset())) + if (((type() == Type::LeftParenthesisLayout || type() == Type::LeftSquareBracketLayout) && idxInParent == numberOfSiblings - 1) + || ((type() == Type::RightParenthesisLayout || type() == Type::RightSquareBracketLayout) && idxInParent == 0) + || ((type() == Type::LeftParenthesisLayout || type() == Type::LeftSquareBracketLayout) && idxInParent < numberOfSiblings - 1 && parentLayout->childAtIndex(idxInParent + 1)->type() == Type::VerticalOffsetLayout)) { /* The bracket does not have siblings on its open direction, or it is a left * bracket that is base of a superscript layout. In the latter case, it @@ -60,13 +60,13 @@ KDCoordinate BracketLayoutNode::computeBaseline() { int currentNumberOfOpenBrackets = 1; KDCoordinate result = 0; - int increment = (isLeftParenthesis() || isLeftBracket()) ? 1 : -1; + int increment = (type() == Type::LeftParenthesisLayout || type() == Type::LeftSquareBracketLayout) ? 1 : -1; for (int i = idxInParent + increment; i >= 0 && i < numberOfSiblings; i+=increment) { LayoutNode * sibling = parentLayout->childAtIndex(i); - if ((isLeftParenthesis() && sibling->isRightParenthesis()) - || (isLeftBracket() && sibling->isRightBracket()) - || (isRightParenthesis() && sibling->isLeftParenthesis()) - || (isRightBracket() && sibling->isLeftBracket())) + if ((type() == Type::LeftParenthesisLayout && sibling->type() == Type::RightParenthesisLayout) + || (type() == Type::LeftSquareBracketLayout && sibling->type() == Type::RightSquareBracketLayout) + || (type() == Type::RightParenthesisLayout && sibling->type() == Type::LeftParenthesisLayout) + || (type() == Type::RightSquareBracketLayout && sibling->type() == Type::LeftSquareBracketLayout)) { if (i == idxInParent + increment) { /* If the bracket is immediately closed, we set the baseline to half the @@ -77,10 +77,10 @@ KDCoordinate BracketLayoutNode::computeBaseline() { if (currentNumberOfOpenBrackets == 0) { break; } - } else if ((isLeftParenthesis() && sibling->isLeftParenthesis()) - || (isLeftBracket() && sibling->isLeftBracket()) - || (isRightParenthesis() && sibling->isRightParenthesis()) - || (isRightBracket() && sibling->isRightBracket())) + } else if ((type() == Type::LeftParenthesisLayout && sibling->type() == Type::LeftParenthesisLayout) + || (type() == Type::LeftSquareBracketLayout && sibling->type() == Type::LeftSquareBracketLayout) + || (type() == Type::RightParenthesisLayout && sibling->type() == Type::RightParenthesisLayout) + || (type() == Type::RightSquareBracketLayout && sibling->type() == Type::RightSquareBracketLayout)) { currentNumberOfOpenBrackets++; } @@ -103,9 +103,9 @@ KDCoordinate BracketLayoutNode::computeChildHeight() { KDCoordinate result = Metric::MinimalBracketAndParenthesisHeight; int idxInParent = parentLayout->indexOfChild(this); int numberOfSiblings = parentLayout->numberOfChildren(); - if ((isLeftParenthesis() || isLeftBracket()) + if ((type() == Type::LeftParenthesisLayout || type() == Type::LeftSquareBracketLayout) && idxInParent < numberOfSiblings - 1 - && parentLayout->childAtIndex(idxInParent + 1)->isVerticalOffset()) + && parentLayout->childAtIndex(idxInParent + 1)->type() == Type::VerticalOffsetLayout) { /* If a left bracket is the base of a superscript layout, it should have a * a default height, else it creates an infinite loop because the bracket @@ -117,22 +117,22 @@ KDCoordinate BracketLayoutNode::computeChildHeight() { KDCoordinate maxAboveBaseline = 0; int currentNumberOfOpenBrackets = 1; - int increment = (isLeftParenthesis() || isLeftBracket()) ? 1 : -1; + int increment = (type() == Type::LeftParenthesisLayout || type() == Type::LeftSquareBracketLayout) ? 1 : -1; for (int i = idxInParent + increment; i >= 0 && i < numberOfSiblings; i+= increment) { LayoutNode * sibling = parentLayout->childAtIndex(i); - if ((isLeftParenthesis() && sibling->isRightParenthesis()) - || (isLeftBracket() && sibling->isRightBracket()) - || (isRightParenthesis() && sibling->isLeftParenthesis()) - || (isRightBracket() && sibling->isLeftBracket())) + if ((type() == Type::LeftParenthesisLayout && sibling->type() == Type::RightParenthesisLayout) + || (type() == Type::LeftSquareBracketLayout && sibling->type() == Type::RightSquareBracketLayout) + || (type() == Type::RightParenthesisLayout && sibling->type() == Type::LeftParenthesisLayout) + || (type() == Type::RightSquareBracketLayout && sibling->type() == Type::LeftSquareBracketLayout)) { currentNumberOfOpenBrackets--; if (currentNumberOfOpenBrackets == 0) { break; } - } else if ((isLeftParenthesis() && sibling->isLeftParenthesis()) - || (isLeftBracket() && sibling->isLeftBracket()) - || (isRightParenthesis() && sibling->isRightParenthesis()) - || (isRightBracket() && sibling->isRightBracket())) + } else if ((type() == Type::LeftParenthesisLayout && sibling->type() == Type::LeftParenthesisLayout) + || (type() == Type::LeftSquareBracketLayout && sibling->type() == Type::LeftSquareBracketLayout) + || (type() == Type::RightParenthesisLayout && sibling->type() == Type::RightParenthesisLayout) + || (type() == Type::RightSquareBracketLayout && sibling->type() == Type::RightSquareBracketLayout)) { currentNumberOfOpenBrackets++; } diff --git a/poincare/src/code_point_layout.cpp b/poincare/src/code_point_layout.cpp index 4914aecb4..38dd16b0e 100644 --- a/poincare/src/code_point_layout.cpp +++ b/poincare/src/code_point_layout.cpp @@ -4,6 +4,14 @@ namespace Poincare { +bool CodePointLayoutNode::isIdenticalTo(Layout l) { + if (l.type() != Type::CodePointLayout) { + return false; + } + CodePointLayout & cpl = static_cast(l); + return codePoint() == cpl.codePoint() && font() == cpl.font(); +} + // LayoutNode void CodePointLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) { if (cursor->position() == LayoutCursor::Position::Right) { @@ -49,7 +57,7 @@ bool CodePointLayoutNode::isCollapsable(int * numberOfOpenParenthesis, bool goin int indexOfThis = parent.indexOfChild(thisRef); if (indexOfThis > 0) { Layout leftBrother = parent.childAtIndex(indexOfThis-1); - if (leftBrother.isCodePoint() + if (leftBrother.type() == Type::CodePointLayout && static_cast(leftBrother).codePoint() == UCodePointLatinLetterSmallCapitalE) { return true; diff --git a/poincare/src/empty_layout.cpp b/poincare/src/empty_layout.cpp index 4bed5b777..b9a02747f 100644 --- a/poincare/src/empty_layout.cpp +++ b/poincare/src/empty_layout.cpp @@ -5,6 +5,14 @@ namespace Poincare { +bool EmptyLayoutNode::isIdenticalTo(Layout l) { + if (l.type() != Type::EmptyLayout) { + return false; + } + EmptyLayoutNode * n = static_cast(l.node()); + return color() == n->color(); +} + void EmptyLayoutNode::deleteBeforeCursor(LayoutCursor * cursor) { cursor->setPosition(LayoutCursor::Position::Left); LayoutNode * p = parent(); diff --git a/poincare/src/fraction_layout.cpp b/poincare/src/fraction_layout.cpp index 8b6c85f9c..8af85a37d 100644 --- a/poincare/src/fraction_layout.cpp +++ b/poincare/src/fraction_layout.cpp @@ -133,13 +133,13 @@ int FractionLayoutNode::serialize(char * buffer, int bufferSize, Preferences::Pr } // Add a multiplication if omitted. - if (idxInParent > 0 && p->isHorizontal() && p->childAtIndex(idxInParent - 1)->canBeOmittedMultiplicationLeftFactor()) { + if (idxInParent > 0 && p->type() == Type::HorizontalLayout && p->childAtIndex(idxInParent - 1)->canBeOmittedMultiplicationLeftFactor()) { numberOfChar+= SerializationHelper::CodePoint(buffer + numberOfChar, bufferSize - numberOfChar, UCodePointMiddleDot); if (numberOfChar >= bufferSize-1) { return bufferSize-1;} } bool addParenthesis = false; - if (idxInParent >= 0 && idxInParent < (p->numberOfChildren() - 1) && p->isHorizontal() && p->childAtIndex(idxInParent + 1)->isVerticalOffset()) { + if (idxInParent >= 0 && idxInParent < (p->numberOfChildren() - 1) && p->type() == Type::HorizontalLayout && p->childAtIndex(idxInParent + 1)->type() == Type::VerticalOffsetLayout) { addParenthesis = true; // Add parenthesis numberOfChar+= SerializationHelper::CodePoint(buffer + numberOfChar, bufferSize - numberOfChar, '('); @@ -157,7 +157,7 @@ int FractionLayoutNode::serialize(char * buffer, int bufferSize, Preferences::Pr } // Add a multiplication if omitted. - if (idxInParent >= 0 && idxInParent < (p->numberOfChildren() - 1) && p->isHorizontal() && p->childAtIndex(idxInParent + 1)->canBeOmittedMultiplicationRightFactor()) { + if (idxInParent >= 0 && idxInParent < (p->numberOfChildren() - 1) && p->type() == Type::HorizontalLayout && p->childAtIndex(idxInParent + 1)->canBeOmittedMultiplicationRightFactor()) { numberOfChar+= SerializationHelper::CodePoint(buffer + numberOfChar, bufferSize - numberOfChar, UCodePointMiddleDot); if (numberOfChar >= bufferSize-1) { return bufferSize-1;} } diff --git a/poincare/src/horizontal_layout.cpp b/poincare/src/horizontal_layout.cpp index 10dc63766..475ab8737 100644 --- a/poincare/src/horizontal_layout.cpp +++ b/poincare/src/horizontal_layout.cpp @@ -309,7 +309,7 @@ bool HorizontalLayoutNode::willReplaceChild(LayoutNode * oldChild, LayoutNode * /* If the new child is also an horizontal layout, steal the children of the * new layout then destroy it. */ bool oldWasAncestorOfNewLayout = newChild->hasAncestor(oldChild, false); - if (newChild->isHorizontal()) { + if (newChild->type() == LayoutNode::Type::HorizontalLayout) { int indexForInsertion = indexOfChild(oldChild); if (cursor != nullptr) { /* If the old layout is not an ancestor of the new layout, or if the @@ -354,7 +354,7 @@ bool HorizontalLayoutNode::willReplaceChild(LayoutNode * oldChild, LayoutNode * // HorizontalLayout void HorizontalLayout::addOrMergeChildAtIndex(Layout l, int index, bool removeEmptyChildren, LayoutCursor * cursor) { - if (l.isHorizontal()) { + if (l.type() == LayoutNode::Type::HorizontalLayout) { mergeChildrenAtIndex(HorizontalLayout(static_cast(l.node())), index, removeEmptyChildren, cursor); } else { addChildAtIndex(l, index, numberOfChildren(), cursor, removeEmptyChildren); diff --git a/poincare/src/layout.cpp b/poincare/src/layout.cpp index 98641e9bd..333e0441a 100644 --- a/poincare/src/layout.cpp +++ b/poincare/src/layout.cpp @@ -94,12 +94,12 @@ void Layout::replaceWith(Layout newChild, LayoutCursor * cursor) { void Layout::replaceWithJuxtapositionOf(Layout leftChild, Layout rightChild, LayoutCursor * cursor, bool putCursorInTheMiddle) { Layout p = parent(); assert(!p.isUninitialized()); - if (!p.isHorizontal()) { + if (p.type() != LayoutNode::Type::HorizontalLayout) { /* One of the children to juxtapose might be "this", so we cannot just call * replaceWith. */ HorizontalLayout horizontalLayoutR = HorizontalLayout::Builder(); p.replaceChild(*this, horizontalLayoutR, cursor); - horizontalLayoutR.addOrMergeChildAtIndex(leftChild, 0, true); + horizontalLayoutR.addOrMergeChildAtIndex(leftChild, 0, false); if (putCursorInTheMiddle) { if (!horizontalLayoutR.isEmpty()) { cursor->setLayout(horizontalLayoutR.childAtIndex(horizontalLayoutR.numberOfChildren()-1)); @@ -166,21 +166,21 @@ void Layout::addSibling(LayoutCursor * cursor, Layout sibling, bool moveCursor) Layout rootLayout = root(); Layout p = parent(); assert(!p.isUninitialized()); - if (p.isHorizontal()) { + if (p.type() == LayoutNode::Type::HorizontalLayout) { int indexInParent = p.indexOfChild(*this); int siblingIndex = cursor->position() == LayoutCursor::Position::Left ? indexInParent : indexInParent + 1; /* Special case: If the neighbour sibling is a VerticalOffsetLayout, let it * handle the insertion of the new sibling. Do not enter the special case if * "this" is a VerticalOffsetLayout, to avoid an infinite loop. */ - if (!isVerticalOffset()) { + if (type() != LayoutNode::Type::VerticalOffsetLayout) { Layout neighbour; if (cursor->position() == LayoutCursor::Position::Left && indexInParent > 0) { neighbour = p.childAtIndex(indexInParent - 1); } else if (cursor->position() == LayoutCursor::Position::Right && indexInParent < p.numberOfChildren() - 1) { neighbour = p.childAtIndex(indexInParent + 1); } - if (!neighbour.isUninitialized() && neighbour.isVerticalOffset()) { + if (!neighbour.isUninitialized() && neighbour.type() == LayoutNode::Type::VerticalOffsetLayout) { if (moveCursor) { cursor->setLayout(neighbour); cursor->setPosition(cursor->position() == LayoutCursor::Position::Left ? LayoutCursor::Position::Right : LayoutCursor::Position::Left); @@ -234,7 +234,7 @@ void Layout::removeChildAtIndex(int index, LayoutCursor * cursor, bool force) { void Layout::collapseOnDirection(HorizontalDirection direction, int absorbingChildIndex) { Layout p = parent(); - if (p.isUninitialized() || !p.isHorizontal()) { + if (p.isUninitialized() || p.type() != LayoutNode::Type::HorizontalLayout) { return; } int idxInParent = p.indexOfChild(*this); @@ -242,7 +242,7 @@ void Layout::collapseOnDirection(HorizontalDirection direction, int absorbingChi int numberOfOpenParenthesis = 0; bool canCollapse = true; Layout absorbingChild = childAtIndex(absorbingChildIndex); - if (absorbingChild.isUninitialized() || !absorbingChild.isHorizontal()) { + if (absorbingChild.isUninitialized() || absorbingChild.type() != LayoutNode::Type::HorizontalLayout) { return; } HorizontalLayout horizontalAbsorbingChild = HorizontalLayout(static_cast(absorbingChild.node())); @@ -285,7 +285,7 @@ void Layout::collapseSiblings(LayoutCursor * cursor) { Layout rootLayout = root(); if (node()->shouldCollapseSiblingsOnRight()) { Layout absorbingChild = childAtIndex(rightCollapsingAbsorbingChildIndex()); - if (!absorbingChild.isHorizontal()) { + if (absorbingChild.type() != LayoutNode::Type::HorizontalLayout) { Layout horRef = HorizontalLayout::Builder(); replaceChild(absorbingChild, horRef, cursor, true); horRef.addChildAtIndexInPlace(absorbingChild, 0, 0); @@ -294,7 +294,7 @@ void Layout::collapseSiblings(LayoutCursor * cursor) { } if (node()->shouldCollapseSiblingsOnLeft()) { Layout absorbingChild = childAtIndex(leftCollapsingAbsorbingChildIndex()); - if (!absorbingChild.isHorizontal()) { + if (absorbingChild.type() != LayoutNode::Type::HorizontalLayout) { Layout horRef = HorizontalLayout::Builder(); replaceChild(absorbingChild, horRef, cursor, true); horRef.addChildAtIndexInPlace(absorbingChild, 0, 0); diff --git a/poincare/src/layout_cursor.cpp b/poincare/src/layout_cursor.cpp index da42ec1a4..686405c13 100644 --- a/poincare/src/layout_cursor.cpp +++ b/poincare/src/layout_cursor.cpp @@ -78,7 +78,7 @@ void LayoutCursor::addEmptyExponentialLayout() { EmptyLayout emptyLayout = EmptyLayout::Builder(); HorizontalLayout sibling = HorizontalLayout::Builder( CodePointLayout::Builder(UCodePointScriptSmallE), - VerticalOffsetLayout::Builder(emptyLayout, VerticalOffsetLayoutNode::Type::Superscript)); + VerticalOffsetLayout::Builder(emptyLayout, VerticalOffsetLayoutNode::Position::Superscript)); m_layout.addSibling(this, sibling, false); m_layout = emptyLayout; } @@ -103,13 +103,13 @@ void LayoutCursor::addEmptySquareRootLayout() { } void LayoutCursor::addEmptyPowerLayout() { - VerticalOffsetLayout offsetLayout = VerticalOffsetLayout::Builder(EmptyLayout::Builder(), VerticalOffsetLayoutNode::Type::Superscript); + VerticalOffsetLayout offsetLayout = VerticalOffsetLayout::Builder(EmptyLayout::Builder(), VerticalOffsetLayoutNode::Position::Superscript); privateAddEmptyPowerLayout(offsetLayout); m_layout = offsetLayout.childAtIndex(0); } void LayoutCursor::addEmptySquarePowerLayout() { - VerticalOffsetLayout offsetLayout = VerticalOffsetLayout::Builder(CodePointLayout::Builder('2'), VerticalOffsetLayoutNode::Type::Superscript); + VerticalOffsetLayout offsetLayout = VerticalOffsetLayout::Builder(CodePointLayout::Builder('2'), VerticalOffsetLayoutNode::Position::Superscript); privateAddEmptyPowerLayout(offsetLayout); } @@ -121,7 +121,7 @@ void LayoutCursor::addEmptyTenPowerLayout() { CodePointLayout::Builder('0'), VerticalOffsetLayout::Builder( emptyLayout, - VerticalOffsetLayoutNode::Type::Superscript)); + VerticalOffsetLayoutNode::Position::Superscript)); m_layout.addSibling(this, sibling, false); m_layout = emptyLayout; } @@ -190,7 +190,7 @@ void LayoutCursor::insertText(const char * text) { } void LayoutCursor::addLayoutAndMoveCursor(Layout l) { - bool layoutWillBeMerged = l.isHorizontal(); + bool layoutWillBeMerged = l.type() == LayoutNode::Type::HorizontalLayout; m_layout.addSibling(this, l, true); if (!layoutWillBeMerged) { l.collapseSiblings(this); @@ -199,7 +199,7 @@ void LayoutCursor::addLayoutAndMoveCursor(Layout l) { void LayoutCursor::clearLayout() { Layout rootLayoutR = m_layout.root(); - assert(rootLayoutR.isHorizontal()); + assert(rootLayoutR.type() == LayoutNode::Type::HorizontalLayout); rootLayoutR.removeChildrenInPlace(rootLayoutR.numberOfChildren()); m_layout = rootLayoutR; } @@ -239,10 +239,10 @@ bool LayoutCursor::baseForNewPowerLayout() { * be the base of a new power layout: the base layout should be anything but * an horizontal layout with no child. */ if (m_position == Position::Right) { - return !(m_layout.isHorizontal() && m_layout.numberOfChildren() == 0); + return !(m_layout.type() == LayoutNode::Type::HorizontalLayout && m_layout.numberOfChildren() == 0); } else { assert(m_position == Position::Left); - if (m_layout.isHorizontal()) { + if (m_layout.type() == LayoutNode::Type::HorizontalLayout) { return false; } if (m_layout.isEmpty()) { @@ -254,7 +254,7 @@ bool LayoutCursor::baseForNewPowerLayout() { } LayoutCursor equivalentLayoutCursor = m_layout.equivalentCursor(this); if (equivalentLayoutCursor.layoutReference().isUninitialized() - || (equivalentLayoutCursor.layoutReference().isHorizontal() + || (equivalentLayoutCursor.layoutReference().type() == LayoutNode::Type::HorizontalLayout && equivalentLayoutCursor.position() == Position::Left)) { return false; @@ -285,7 +285,7 @@ bool LayoutCursor::privateShowHideEmptyLayoutIfNeeded(bool show) { /* Change the visibility of the neighbouring empty layout: it might be either * an EmptyLayout or an HorizontalLayout with one child only, and this child * is an EmptyLayout. */ - if (adjacentEmptyLayout.isHorizontal()) { + if (adjacentEmptyLayout.type() == LayoutNode::Type::HorizontalLayout) { static_cast(adjacentEmptyLayout.childAtIndex(0).node())->setVisible(show); } else { static_cast(adjacentEmptyLayout.node())->setVisible(show); diff --git a/poincare/src/layout_helper.cpp b/poincare/src/layout_helper.cpp index 91a48b984..c5f989c62 100644 --- a/poincare/src/layout_helper.cpp +++ b/poincare/src/layout_helper.cpp @@ -92,7 +92,7 @@ HorizontalLayout LayoutHelper::CodePointString(const CodePoint * buffer, int buf Layout LayoutHelper::Logarithm(Layout argument, Layout index) { HorizontalLayout resultLayout = String("log", 3); - VerticalOffsetLayout offsetLayout = VerticalOffsetLayout::Builder(index, VerticalOffsetLayoutNode::Type::Subscript); + VerticalOffsetLayout offsetLayout = VerticalOffsetLayout::Builder(index, VerticalOffsetLayoutNode::Position::Subscript); resultLayout.addChildAtIndex(offsetLayout, resultLayout.numberOfChildren(), resultLayout.numberOfChildren(), nullptr); resultLayout.addOrMergeChildAtIndex(Parentheses(argument, false), resultLayout.numberOfChildren(), true); return resultLayout; diff --git a/poincare/src/layout_node.cpp b/poincare/src/layout_node.cpp index 53f80eba5..1132300fe 100644 --- a/poincare/src/layout_node.cpp +++ b/poincare/src/layout_node.cpp @@ -7,6 +7,19 @@ namespace Poincare { +bool LayoutNode::isIdenticalTo(Layout l) { + if (l.isUninitialized() || type() != l.type() || numberOfChildren() != l.numberOfChildren()) { + return false; + } + for (int i = 0; i < numberOfChildren(); i++) { + Layout child = childAtIndex(i); + if (!childAtIndex(i)->isIdenticalTo(l.childAtIndex(i))) { + return false; + } + } + return true; +} + // Rendering void LayoutNode::draw(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { @@ -215,7 +228,7 @@ bool LayoutNode::changeGreySquaresOfAllMatrixAncestors(bool add) { bool changedSquares = false; Layout currentAncestor = Layout(parent()); while (!currentAncestor.isUninitialized()) { - if (currentAncestor.isMatrix()) { + if (currentAncestor.type() == Type::MatrixLayout) { if (add) { MatrixLayout(static_cast(currentAncestor.node())).addGreySquares(); } else { diff --git a/poincare/src/matrix_layout.cpp b/poincare/src/matrix_layout.cpp index 659b3682f..d390eea1e 100644 --- a/poincare/src/matrix_layout.cpp +++ b/poincare/src/matrix_layout.cpp @@ -180,7 +180,7 @@ void MatrixLayoutNode::newRowOrColumnAtIndex(int index) { } if (childIndex % m_numberOfColumns == correspondingColumn) { if (lastLayoutOfRow->isEmpty()) { - if (!lastLayoutOfRow->isHorizontal()) { + if (lastLayoutOfRow->type() != Type::HorizontalLayout) { static_cast(lastLayoutOfRow)->setColor(EmptyLayoutNode::Color::Yellow); } else { assert(lastLayoutOfRow->numberOfChildren() == 1); @@ -203,7 +203,7 @@ void MatrixLayoutNode::newRowOrColumnAtIndex(int index) { break; } if (lastLayoutOfColumn->isEmpty()) { - if (!lastLayoutOfColumn->isHorizontal()) { + if (lastLayoutOfColumn->type() != Type::HorizontalLayout) { static_cast(lastLayoutOfColumn)->setColor(EmptyLayoutNode::Color::Yellow); } else { assert(lastLayoutOfColumn->numberOfChildren() == 1); @@ -253,7 +253,7 @@ bool MatrixLayoutNode::hasGreySquares() const { } LayoutNode * lastChild = const_cast(this)->childAtIndex(m_numberOfRows * m_numberOfColumns - 1); if (lastChild->isEmpty() - && !lastChild->isHorizontal() + && lastChild->type() != Type::HorizontalLayout && (static_cast(lastChild))->color() == EmptyLayoutNode::Color::Grey) { assert(isRowEmpty(m_numberOfRows - 1)); diff --git a/poincare/src/nth_root_layout.cpp b/poincare/src/nth_root_layout.cpp index c689064d0..62beeb8af 100644 --- a/poincare/src/nth_root_layout.cpp +++ b/poincare/src/nth_root_layout.cpp @@ -20,6 +20,14 @@ const uint8_t radixPixel[NthRootLayoutNode::k_leftRadixHeight][NthRootLayoutNode {0xFF, 0xFF, 0xFF, 0xFF, 0x00}, }; +bool NthRootLayoutNode::isIdenticalTo(Layout l) { + if (l.type() != Type::NthRootLayout) { + return false; + } + NthRootLayout & nrl = static_cast(l); + return hasUpperLeftIndex() == nrl.node()->hasUpperLeftIndex() && LayoutNode::isIdenticalTo(l); +} + void NthRootLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) { if (cursor->layoutNode() == radicandLayout() && cursor->position() == LayoutCursor::Position::Left) diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index c1b5563d3..f17f5d676 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -138,7 +138,7 @@ Layout PowerNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int result.addOrMergeChildAtIndex(childAtIndex(0)->createLayout(floatDisplayMode, numberOfSignificantDigits), 0, false); result.addChildAtIndex(VerticalOffsetLayout::Builder( indiceOperand->createLayout(floatDisplayMode, numberOfSignificantDigits), - VerticalOffsetLayoutNode::Type::Superscript), + VerticalOffsetLayoutNode::Position::Superscript), result.numberOfChildren(), result.numberOfChildren(), nullptr); @@ -572,10 +572,22 @@ Expression Power::shallowReduce(Context & context, Preferences::ComplexFormat co * This rule is not generally true: ((-2)^2)^(1/2) != (-2)^(2*1/2) = -2 * This rule is true if: * - a > 0 - * - in Real: when b and c are integers - * - in other modes: when c is integer - * (Warning: in real mode only c integer is not enough: - * ex: ((-2)^(1/2))^2 = unreal != -2) + * - c is an integer + * + * Warning 1: in real mode only c integer is not enough: + * ex: ((-2)^(1/2))^2 = unreal != -2 + * We escape that case by returning 'unreal' if the a^b is complex. + * + * Warning 2: If we did not apply this rule on expressions of the form + * (a^b)^(-1), we would end up in infinite loop when factorizing an addition + * on the same denominator. + * For ex: + * 1+[tan(2)^1/2]^(-1) --> (tan(2)^1/2+tan(2)^1/2*[tan(2)^1/2]^(-1))/tan(2)^1/2 + * --> tan(2)+tan(2)*[tan(2)^1/2]^(-1)/tan(2) + * --> tan(2)^(3/2)+tan(2)^(3/2)*[tan(2)^1/2]^(-1)/tan(2)^3/2 + * --> ... + * Indeed, we have to apply the rule (a^b)^c -> a^(b*c) as soon as c is an + * integer. */ if (childAtIndex(0).type() == ExpressionNode::Type::Power) { Power p = childAtIndex(0).convert(); @@ -584,11 +596,17 @@ Expression Power::shallowReduce(Context & context, Preferences::ComplexFormat co bool cInteger = (childAtIndex(1).type() == ExpressionNode::Type::Rational && childAtIndex(1).convert().integerDenominator().isOne()); if (aPositive || cInteger) { - // Check that the complex format is not Real or that b is an integer - bool bInteger = (p.childAtIndex(1).type() == ExpressionNode::Type::Rational && p.childAtIndex(1).convert().integerDenominator().isOne()); - if (aPositive || complexFormat != Preferences::ComplexFormat::Real || bInteger) { - return simplifyPowerPower(context, complexFormat, angleUnit, target); + /* If the complexFormat is real, we check that the inner power is defined + * before applying the rule (a^b)^c -> a^(b*c). Otherwise, we return + * 'unreal'. */ + if (complexFormat == Preferences::ComplexFormat::Real) { + Expression approximation = p.approximate(context, complexFormat, angleUnit); + if (approximation.type() == ExpressionNode::Type::Unreal) { + replaceWithInPlace(approximation); + return approximation; + } } + return simplifyPowerPower(context, complexFormat, angleUnit, target); } } // Step 11: (a*b*c*...)^r ? diff --git a/poincare/src/randint.cpp b/poincare/src/randint.cpp index 47d20058b..eee3afb1f 100644 --- a/poincare/src/randint.cpp +++ b/poincare/src/randint.cpp @@ -1,9 +1,13 @@ #include #include -#include -#include +#include +#include #include +#include +#include +#include #include +#include extern "C" { #include @@ -29,7 +33,7 @@ template Evaluation RandintNode::templateApproximate(Context & c Evaluation bInput = childAtIndex(1)->approximate(T(), context, complexFormat, angleUnit); T a = aInput.toScalar(); T b = bInput.toScalar(); - if (std::isnan(a) || std::isnan(b) || a != std::round(a) || b != std::round(b) || a > b) { + if (std::isnan(a) || std::isnan(b) || a != std::round(a) || b != std::round(b) || a > b || std::isinf(a) || std::isinf(b)) { return Complex::Undefined(); } @@ -37,5 +41,26 @@ template Evaluation RandintNode::templateApproximate(Context & c return Complex::Builder(result); } +Expression RandintNode::shallowReduce(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ReductionTarget target, bool symbolicComputation) { + return Randint(this).shallowReduce(context, complexFormat, angleUnit, target); +} + +Expression Randint::shallowReduce(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::ReductionTarget target) { + Expression e = Expression::defaultShallowReduce(); + if (e.isUndefined()) { + return e; + } + float eval = approximateToScalar(context, complexFormat, angleUnit); + Expression result; + if (std::isnan(eval)) { + result = Undefined::Builder(); + } else if (std::isinf(eval)) { + result = Infinity::Builder(eval < 0); + } else { + result = Rational::Builder(Integer((int)eval)); + } + replaceWithInPlace(result); + return result; +} } diff --git a/poincare/src/symbol.cpp b/poincare/src/symbol.cpp index 4d64effca..65037ff94 100644 --- a/poincare/src/symbol.cpp +++ b/poincare/src/symbol.cpp @@ -90,28 +90,28 @@ Layout SymbolNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, in CodePointLayout::Builder('u'), VerticalOffsetLayout::Builder( CodePointLayout::Builder('n'), - VerticalOffsetLayoutNode::Type::Subscript)); + VerticalOffsetLayoutNode::Position::Subscript)); } if (strcmp(m_name, "u(n+1)") == 0) { return HorizontalLayout::Builder( CodePointLayout::Builder('u'), VerticalOffsetLayout::Builder( LayoutHelper::String("n+1", 3), - VerticalOffsetLayoutNode::Type::Subscript)); + VerticalOffsetLayoutNode::Position::Subscript)); } if (strcmp(m_name, "v(n)") == 0) { return HorizontalLayout::Builder( CodePointLayout::Builder('v'), VerticalOffsetLayout::Builder( CodePointLayout::Builder('n'), - VerticalOffsetLayoutNode::Type::Subscript)); + VerticalOffsetLayoutNode::Position::Subscript)); } if (strcmp(m_name, "v(n+1)") == 0) { return HorizontalLayout::Builder( CodePointLayout::Builder('v'), VerticalOffsetLayout::Builder( LayoutHelper::String("n+1", 3), - VerticalOffsetLayoutNode::Type::Subscript)); + VerticalOffsetLayoutNode::Position::Subscript)); } return LayoutHelper::String(m_name, strlen(m_name)); } diff --git a/poincare/src/vertical_offset_layout.cpp b/poincare/src/vertical_offset_layout.cpp index 627e15af7..4aaad5bc1 100644 --- a/poincare/src/vertical_offset_layout.cpp +++ b/poincare/src/vertical_offset_layout.cpp @@ -8,6 +8,14 @@ namespace Poincare { +bool VerticalOffsetLayoutNode::isIdenticalTo(Layout l) { + if (l.type() != Type::VerticalOffsetLayout) { + return false; + } + VerticalOffsetLayoutNode * n = static_cast(l.node()); + return position() == n->position() && LayoutNode::isIdenticalTo(l); +} + void VerticalOffsetLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) { if (cursor->layoutNode() == indiceLayout() && cursor->position() == LayoutCursor::Position::Left) @@ -54,7 +62,7 @@ void VerticalOffsetLayoutNode::moveCursorRight(LayoutCursor * cursor, bool * sho } void VerticalOffsetLayoutNode::moveCursorUp(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { - if (m_type == Type::Superscript) { + if (m_position == Position::Superscript) { // Case: Superscript. if (cursor->isEquivalentTo(LayoutCursor(this, LayoutCursor::Position::Right))) { // Case: Right. Move to the indice. @@ -71,7 +79,7 @@ void VerticalOffsetLayoutNode::moveCursorUp(LayoutCursor * cursor, bool * should } /* Case: Subscript, Left or Right of the indice. Put the cursor at the same * position, pointing this. */ - if (m_type == Type::Subscript + if (m_position == Position::Subscript && (cursor->isEquivalentTo(LayoutCursor(indiceLayout(), LayoutCursor::Position::Left)) || cursor->isEquivalentTo(LayoutCursor(indiceLayout(), LayoutCursor::Position::Right)))) { @@ -82,7 +90,7 @@ void VerticalOffsetLayoutNode::moveCursorUp(LayoutCursor * cursor, bool * should } void VerticalOffsetLayoutNode::moveCursorDown(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) { - if (m_type == Type::Subscript) { + if (m_position == Position::Subscript) { // Case: Subscript. if (cursor->isEquivalentTo(LayoutCursor(this, LayoutCursor::Position::Right))) { // Case: Right. Move to the indice. @@ -99,7 +107,7 @@ void VerticalOffsetLayoutNode::moveCursorDown(LayoutCursor * cursor, bool * shou } /* Case: Superscript, Left or Right of the indice. Put the cursor at the same * position, pointing this. */ - if (m_type == Type::Superscript + if (m_position == Position::Superscript && cursor->layoutNode() == indiceLayout()) { cursor->setLayoutNode(this); @@ -149,7 +157,7 @@ void VerticalOffsetLayoutNode::deleteBeforeCursor(LayoutCursor * cursor) { } int VerticalOffsetLayoutNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { - if (m_type == Type::Subscript) { + if (m_position == Position::Subscript) { if (bufferSize == 0) { return -1; } @@ -172,7 +180,7 @@ int VerticalOffsetLayoutNode::serialize(char * buffer, int bufferSize, Preferenc return numberOfChar; } - assert(m_type == Type::Superscript); + assert(m_position == Position::Superscript); /* If the layout is a superscript, write: * "UCodePointLeftSuperscript indice UCodePointRightSuperscript" */ int numberOfChar = SerializationHelper::CodePoint(buffer, bufferSize, UCodePointLeftSuperscript); @@ -189,10 +197,10 @@ int VerticalOffsetLayoutNode::serialize(char * buffer, int bufferSize, Preferenc KDSize VerticalOffsetLayoutNode::computeSize() { KDSize indiceSize = indiceLayout()->layoutSize(); KDCoordinate width = indiceSize.width(); - if (m_type == Type::Superscript) { + if (m_position == Position::Superscript) { LayoutNode * parentNode = parent(); assert(parentNode != nullptr); - assert(parentNode->isHorizontal()); + assert(parentNode->type() == Type::HorizontalLayout); int idxInParent = parentNode->indexOfChild(this); if (idxInParent < parentNode->numberOfChildren() - 1 && parentNode->childAtIndex(idxInParent + 1)->hasUpperLeftIndex()) { width += k_separationMargin; @@ -203,7 +211,7 @@ KDSize VerticalOffsetLayoutNode::computeSize() { } KDCoordinate VerticalOffsetLayoutNode::computeBaseline() { - if (m_type == Type::Subscript) { + if (m_position == Position::Subscript) { return baseLayout()->baseline(); } else { return indiceLayout()->layoutSize().height() - k_indiceHeight + baseLayout()->baseline(); @@ -212,21 +220,21 @@ KDCoordinate VerticalOffsetLayoutNode::computeBaseline() { KDPoint VerticalOffsetLayoutNode::positionOfChild(LayoutNode * child) { assert(child == indiceLayout()); - if (m_type == Type::Superscript) { + if (m_position == Position::Superscript) { return KDPointZero; } - assert(m_type == Type::Subscript); + assert(m_position == Position::Subscript); return KDPoint(0, baseLayout()->layoutSize().height() - k_indiceHeight); } bool VerticalOffsetLayoutNode::willAddSibling(LayoutCursor * cursor, LayoutNode * sibling, bool moveCursor) { - if (sibling->isVerticalOffset()) { + if (sibling->type() == Type::VerticalOffsetLayout) { VerticalOffsetLayoutNode * verticalOffsetSibling = static_cast(sibling); - if (verticalOffsetSibling->type() == Type::Superscript) { + if (verticalOffsetSibling->position() == Position::Superscript) { Layout rootLayout = root(); Layout thisRef = Layout(this); Layout parentRef = Layout(parent()); - assert(parentRef.isHorizontal()); + assert(parentRef.type() == Type::HorizontalLayout); // Add the Left parenthesis int idxInParent = parentRef.indexOfChild(thisRef); int leftParenthesisIndex = idxInParent; @@ -259,15 +267,15 @@ bool VerticalOffsetLayoutNode::willAddSibling(LayoutCursor * cursor, LayoutNode LayoutNode * VerticalOffsetLayoutNode::baseLayout() { LayoutNode * parentNode = parent(); assert(parentNode != nullptr); - assert(parentNode->isHorizontal()); + assert(parentNode->type() == Type::HorizontalLayout); int idxInParent = parentNode->indexOfChild(this); assert(idxInParent > 0); return parentNode->childAtIndex(idxInParent - 1); } -VerticalOffsetLayout VerticalOffsetLayout::Builder(Layout l, VerticalOffsetLayoutNode::Type type) { +VerticalOffsetLayout VerticalOffsetLayout::Builder(Layout l, VerticalOffsetLayoutNode::Position position) { void * bufferNode = TreePool::sharedPool()->alloc(sizeof(VerticalOffsetLayoutNode)); - VerticalOffsetLayoutNode * node = new (bufferNode) VerticalOffsetLayoutNode(type); + VerticalOffsetLayoutNode * node = new (bufferNode) VerticalOffsetLayoutNode(position); TreeHandle h = TreeHandle::BuildWithGhostChildren(node); h.replaceChildAtIndexInPlace(0, l); return static_cast(h); diff --git a/poincare/test/complex.cpp b/poincare/test/complex.cpp index e74e0e3ac..9ae0a374c 100644 --- a/poincare/test/complex.cpp +++ b/poincare/test/complex.cpp @@ -104,7 +104,7 @@ QUIZ_CASE(poincare_complex_simplify) { assert_parsed_expression_simplify_to("permute(10, 4)", "5040", User, Radian, Cartesian); // TODO: prediction is not simplified yet //assert_parsed_expression_simplify_to("prediction(-2,-3)", "prediction(-2)", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("randint(2,4)", "randint(2,4)", User, Radian, Cartesian); + assert_parsed_expression_simplify_to("randint(2,2)", "2", User, Radian, Cartesian); assert_parsed_expression_simplify_to("random()", "random()", User, Radian, Cartesian); assert_parsed_expression_simplify_to("re(x)", "re(x)", User, Radian, Cartesian); assert_parsed_expression_simplify_to("round(x,y)", "round(x,y)", User, Radian, Cartesian); diff --git a/poincare/test/layouts.cpp b/poincare/test/layouts.cpp index 5a3171ffc..37e55e170 100644 --- a/poincare/test/layouts.cpp +++ b/poincare/test/layouts.cpp @@ -67,7 +67,7 @@ QUIZ_CASE(poincare_create_all_layouts) { EmptyLayout e23 = EmptyLayout::Builder(); EmptyLayout e24 = EmptyLayout::Builder(); SumLayout e25 = SumLayout::Builder(e21, e22, e23, e24); - VerticalOffsetLayout e26 = VerticalOffsetLayout::Builder(e25, VerticalOffsetLayoutNode::Type::Superscript); + VerticalOffsetLayout e26 = VerticalOffsetLayout::Builder(e25, VerticalOffsetLayoutNode::Position::Superscript); } Matrix BuildOneChildMatrix(Expression entry) { @@ -141,7 +141,7 @@ QUIZ_CASE(poincare_parse_layouts) { CodePointLayout::Builder('3'), CodePointLayout::Builder('+'), CodePointLayout::Builder('4')), - VerticalOffsetLayoutNode::Type::Superscript)); + VerticalOffsetLayoutNode::Position::Superscript)); e = Power::Builder( Rational::Builder(2), Addition::Builder( @@ -154,7 +154,7 @@ QUIZ_CASE(poincare_parse_layouts) { l1.addChildAtIndex(CodePointLayout::Builder('l'), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); l1.addChildAtIndex(CodePointLayout::Builder('o'), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); l1.addChildAtIndex(CodePointLayout::Builder('g'), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); - l1.addChildAtIndex(VerticalOffsetLayout::Builder(CodePointLayout::Builder('3'), VerticalOffsetLayoutNode::Type::Subscript), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); + l1.addChildAtIndex(VerticalOffsetLayout::Builder(CodePointLayout::Builder('3'), VerticalOffsetLayoutNode::Position::Subscript), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); l1.addChildAtIndex(LeftParenthesisLayout::Builder(), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); l1.addChildAtIndex(CodePointLayout::Builder('2'), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); l1.addChildAtIndex(RightParenthesisLayout::Builder(), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); @@ -189,7 +189,7 @@ QUIZ_CASE(poincare_parse_layouts) { CodePointLayout::Builder('2'), VerticalOffsetLayout::Builder( CodePointLayout::Builder('2'), - VerticalOffsetLayoutNode::Type::Superscript), + VerticalOffsetLayoutNode::Position::Superscript), CodePointLayout::Builder('!')); e = Factorial::Builder( Power::Builder( @@ -223,7 +223,7 @@ QUIZ_CASE(poincare_parse_layouts) { CodePointLayout::Builder('3'), VerticalOffsetLayout::Builder( CodePointLayout::Builder('2'), - VerticalOffsetLayoutNode::Type::Superscript), + VerticalOffsetLayoutNode::Position::Superscript), CodePointLayout::Builder('!')), CodePointLayout::Builder('7'), CodePointLayout::Builder('4'), @@ -251,7 +251,7 @@ QUIZ_CASE(poincare_parse_layouts) { CodePointLayout::Builder('7'), CodePointLayout::Builder('4'), CodePointLayout::Builder('5')), - VerticalOffsetLayoutNode::Type::Superscript)); + VerticalOffsetLayoutNode::Position::Superscript)); m = BuildOneChildMatrix( Factorial::Builder( Rational::Builder(3))); @@ -268,8 +268,41 @@ QUIZ_CASE(poincare_parse_layouts) { CodePointLayout::Builder(UCodePointScriptSmallE), VerticalOffsetLayout::Builder( CodePointLayout::Builder('3'), - VerticalOffsetLayoutNode::Type::Superscript)); + VerticalOffsetLayoutNode::Position::Superscript)); e = Multiplication::Builder(Rational::Builder(2),Power::Builder(Constant::Builder(UCodePointScriptSmallE),Parenthesis::Builder(Rational::Builder(3)))); assert_parsed_expression_is("2ℯ^(3)", Multiplication::Builder(Rational::Builder(2),Power::Builder(Constant::Builder(UCodePointScriptSmallE),Parenthesis::Builder(Rational::Builder(3))))); assert_parsed_layout_is(l, e); } + +QUIZ_CASE(poincare_layouts_comparison) { + Layout e0 = CodePointLayout::Builder('a'); + Layout e1 = CodePointLayout::Builder('a'); + Layout e2 = CodePointLayout::Builder('b'); + quiz_assert(e0.isIdenticalTo(e1)); + quiz_assert(!e0.isIdenticalTo(e2)); + + Layout e3 = EmptyLayout::Builder(); + Layout e4 = EmptyLayout::Builder(); + quiz_assert(e3.isIdenticalTo(e4)); + quiz_assert(!e3.isIdenticalTo(e0)); + + Layout e5 = NthRootLayout::Builder(e0); + Layout e6 = NthRootLayout::Builder(e1); + Layout e7 = NthRootLayout::Builder(e2); + quiz_assert(e5.isIdenticalTo(e6)); + quiz_assert(!e5.isIdenticalTo(e7)); + quiz_assert(!e5.isIdenticalTo(e0)); + + Layout e8 = VerticalOffsetLayout::Builder(e5, VerticalOffsetLayoutNode::Position::Superscript); + Layout e9 = VerticalOffsetLayout::Builder(e6, VerticalOffsetLayoutNode::Position::Superscript); + Layout e10 = VerticalOffsetLayout::Builder(NthRootLayout::Builder(CodePointLayout::Builder('a')), VerticalOffsetLayoutNode::Position::Subscript); + quiz_assert(e8.isIdenticalTo(e9)); + quiz_assert(!e8.isIdenticalTo(e10)); + quiz_assert(!e8.isIdenticalTo(e0)); + + Layout e11 = SumLayout::Builder(e0, e3, e6, e2); + Layout e12 = SumLayout::Builder(CodePointLayout::Builder('a'), EmptyLayout::Builder(), NthRootLayout::Builder(CodePointLayout::Builder('a')), CodePointLayout::Builder('b')); + Layout e13 = ProductLayout::Builder(CodePointLayout::Builder('a'), EmptyLayout::Builder(), NthRootLayout::Builder(CodePointLayout::Builder('a')), CodePointLayout::Builder('b')); + quiz_assert(e11.isIdenticalTo(e12)); + quiz_assert(!e11.isIdenticalTo(e13)); +} diff --git a/poincare/test/power.cpp b/poincare/test/power.cpp index d9d8692b0..bc54ba21e 100644 --- a/poincare/test/power.cpp +++ b/poincare/test/power.cpp @@ -106,5 +106,6 @@ QUIZ_CASE(poincare_power_simplify) { assert_parsed_expression_simplify_to("(-1)^(1/3)", "1/2+√(3)/2×𝐢"); assert_parsed_expression_simplify_to("R(-x)", "R(-x)"); assert_parsed_expression_simplify_to("√(x)^2", "x", User, Radian, Cartesian); - assert_parsed_expression_simplify_to("√(x)^2", "√(x)^2", User, Radian, Real); + assert_parsed_expression_simplify_to("√(-3)^2", "unreal", User, Radian, Real); + assert_parsed_expression_simplify_to("1+((8+√(6))^(1/2))^-1+(8+√(6))^(1/2)", "(√(√(6)+8)+√(6)+9)/√(√(6)+8)", User, Radian, Real); } diff --git a/poincare/test/random.cpp b/poincare/test/random.cpp index 6a40f8bd8..b8dd06c3a 100644 --- a/poincare/test/random.cpp +++ b/poincare/test/random.cpp @@ -20,12 +20,8 @@ QUIZ_CASE(poincare_random_simplify) { } QUIZ_CASE(poincare_randint_simplify) { - assert_parsed_expression_simplify_to("1/randint(3,10)+1/3+1/4", "1/randint(3,10)+7/12"); - assert_parsed_expression_simplify_to("randint(3,10)+randint(3,10)", "randint(3,10)+randint(3,10)"); - assert_parsed_expression_simplify_to("randint(3,10)-randint(3,10)", "-randint(3,10)+randint(3,10)"); - assert_parsed_expression_simplify_to("1/randint(3,10)+1/3+1/4+1/randint(3,10)", "1/randint(3,10)+1/randint(3,10)+7/12"); - assert_parsed_expression_simplify_to("randint(3,10)×randint(3,10)", "randint(3,10)×randint(3,10)"); - assert_parsed_expression_simplify_to("randint(3,10)/randint(3,10)", "randint(3,10)/randint(3,10)"); - assert_parsed_expression_simplify_to("3^randint(3,10)×3^randint(3,10)", "3^randint(3,10)×3^randint(3,10)"); - assert_parsed_expression_simplify_to("randint(3,10)×ln(2)×3+randint(3,10)×ln(2)×5", "5×ln(2)×randint(3,10)+3×ln(2)×randint(3,10)"); + assert_parsed_expression_simplify_to("1/randint(2,2)+1/2", "1"); + assert_parsed_expression_simplify_to("randint(1, inf)", "undef"); + assert_parsed_expression_simplify_to("randint(-inf, 3)", "undef"); + assert_parsed_expression_simplify_to("randint(4, 3)", "undef"); } diff --git a/poincare/test/vertical_offset_layout.cpp b/poincare/test/vertical_offset_layout.cpp index d30b58ee2..7da0bbae7 100644 --- a/poincare/test/vertical_offset_layout.cpp +++ b/poincare/test/vertical_offset_layout.cpp @@ -11,7 +11,7 @@ QUIZ_CASE(poincare_vertical_offset_layout_serialize) { CodePointLayout::Builder('2'), VerticalOffsetLayout::Builder( LayoutHelper::String("x+5", 3), - VerticalOffsetLayoutNode::Type::Superscript + VerticalOffsetLayoutNode::Position::Superscript ) ); assert(UCodePointLeftSuperscript == '\x12'); diff --git a/python/port/helpers.cpp b/python/port/helpers.cpp index b30cc00b5..5f772751f 100644 --- a/python/port/helpers.cpp +++ b/python/port/helpers.cpp @@ -42,3 +42,7 @@ bool micropython_port_interrupt_if_needed() { } return false; } + +int micropython_port_random() { + return Ion::random(); +} diff --git a/python/port/helpers.h b/python/port/helpers.h index 32215ff80..761f96a7d 100644 --- a/python/port/helpers.h +++ b/python/port/helpers.h @@ -12,6 +12,7 @@ extern "C" { bool micropython_port_vm_hook_loop(); bool micropython_port_interruptible_msleep(uint32_t delay); bool micropython_port_interrupt_if_needed(); +int micropython_port_random(); #ifdef __cplusplus } diff --git a/python/port/mpconfigport.h b/python/port/mpconfigport.h index 681b3b6b5..dae99b6f2 100644 --- a/python/port/mpconfigport.h +++ b/python/port/mpconfigport.h @@ -83,12 +83,15 @@ // Whether to provide "sys" module #define MICROPY_PY_SYS (0) -// Wether to provide the "urandom" module +// Whether to provide the "urandom" module #define MICROPY_PY_URANDOM (1) // Whether to include: randrange, randint, choice, random, uniform #define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) +// Function to seed URANDOM with on init +#define MICROPY_PY_URANDOM_SEED_INIT_FUNC micropython_port_random() + // Make a pointer to RAM callable (eg set lower bit for Thumb code) // (This scheme won't work if we want to mix Thumb and normal ARM code.) #define MICROPY_MAKE_POINTER_CALLABLE(p) (p) diff --git a/python/src/extmod/modurandom.c b/python/src/extmod/modurandom.c index 1512a3fd4..a8d087c49 100644 --- a/python/src/extmod/modurandom.c +++ b/python/src/extmod/modurandom.c @@ -200,8 +200,20 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_urandom_uniform_obj, mod_urandom_uniform); #endif // MICROPY_PY_URANDOM_EXTRA_FUNCS + +#ifdef MICROPY_PY_URANDOM_SEED_INIT_FUNC +STATIC mp_obj_t mod_urandom___init__() { + mod_urandom_seed(MP_OBJ_NEW_SMALL_INT(MICROPY_PY_URANDOM_SEED_INIT_FUNC)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mod_urandom___init___obj, mod_urandom___init__); +#endif + STATIC const mp_rom_map_elem_t mp_module_urandom_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_urandom) }, + #ifdef MICROPY_PY_URANDOM_SEED_INIT_FUNC + { MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&mod_urandom___init___obj) }, + #endif { MP_ROM_QSTR(MP_QSTR_getrandbits), MP_ROM_PTR(&mod_urandom_getrandbits_obj) }, { MP_ROM_QSTR(MP_QSTR_seed), MP_ROM_PTR(&mod_urandom_seed_obj) }, #if MICROPY_PY_URANDOM_EXTRA_FUNCS