From 52ae25a04c8bbf3733a55c85cc2a6b2c6b2a533e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 19 Feb 2020 10:52:39 +0100 Subject: [PATCH 01/11] [apps] Use willExitApp parameter in dismissModalViewController This prevents relayouting and thus accessing to expressions/ayotus possibly deleted by a pool exception --- apps/shared/function_app.cpp | 2 +- apps/solver/app.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/shared/function_app.cpp b/apps/shared/function_app.cpp index 5a80460b5..7cede0ee8 100644 --- a/apps/shared/function_app.cpp +++ b/apps/shared/function_app.cpp @@ -26,7 +26,7 @@ void FunctionApp::Snapshot::storageDidChangeForRecord(const Ion::Storage::Record void FunctionApp::willBecomeInactive() { if (m_modalViewController.isDisplayingModal()) { - m_modalViewController.dismissModalViewController(); + m_modalViewController.dismissModalViewController(true); } if (inputViewController()->isDisplayingModal()) { inputViewController()->abortEditionAndDismiss(); diff --git a/apps/solver/app.cpp b/apps/solver/app.cpp index c461403a8..c97ee1048 100644 --- a/apps/solver/app.cpp +++ b/apps/solver/app.cpp @@ -56,7 +56,7 @@ App::App(Snapshot * snapshot) : void App::willBecomeInactive() { if (m_modalViewController.isDisplayingModal()) { - m_modalViewController.dismissModalViewController(); + m_modalViewController.dismissModalViewController(true); } if (inputViewController()->isDisplayingModal()) { inputViewController()->abortEditionAndDismiss(); From f7c38979a1c9274b901e4328da7736f2d51cff23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 19 Feb 2020 11:17:58 +0100 Subject: [PATCH 02/11] [apps/var_box] Create dummy layout when the real layout makes pool error --- apps/variable_box_controller.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/variable_box_controller.cpp b/apps/variable_box_controller.cpp index 7d5943674..36b9845c3 100644 --- a/apps/variable_box_controller.cpp +++ b/apps/variable_box_controller.cpp @@ -3,6 +3,7 @@ #include "shared/continuous_function.h" #include #include +#include #include #include #include @@ -235,10 +236,20 @@ Layout VariableBoxController::expressionLayoutForRecord(Storage::Record record, assert(m_firstMemoizedLayoutIndex >= 0); } assert(index >= m_firstMemoizedLayoutIndex && index < m_firstMemoizedLayoutIndex + k_maxNumberOfDisplayedRows); + Layout result; if (m_layouts[index-m_firstMemoizedLayoutIndex].isUninitialized()) { - m_layouts[index-m_firstMemoizedLayoutIndex] = GlobalContext::LayoutForRecord(record); + /* Creating the layout of a very long variable might throw a pool exception. + * We want to catch it and return a dummy layout instead, otherwise the user + * won't be able to open the variable box again, until she deletes the + * problematic variable -> and she has no help to remember its name, as she + * can't open the variable box. */ + Poincare::ExceptionCheckpoint ecp; + if (ExceptionRun(ecp)) { + result = GlobalContext::LayoutForRecord(record); + } } - return m_layouts[index-m_firstMemoizedLayoutIndex]; + m_layouts[index-m_firstMemoizedLayoutIndex] = result; + return result; } const char * VariableBoxController::extension() const { From cf37e5c45a1be8cb767b77d220d334ad5ebbce10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 19 Feb 2020 13:48:38 +0100 Subject: [PATCH 03/11] [apps/code] Fix input(), that did not return the input --- apps/code/console_controller.cpp | 30 ++++++++++++++++++++++++++---- apps/code/console_edit_cell.cpp | 17 +++++++++++++++++ apps/code/console_edit_cell.h | 2 ++ escher/include/escher/text_field.h | 27 +++++++++++++++++---------- 4 files changed, 62 insertions(+), 14 deletions(-) diff --git a/apps/code/console_controller.cpp b/apps/code/console_controller.cpp index 48f07816d..ee013f0c5 100644 --- a/apps/code/console_controller.cpp +++ b/apps/code/console_controller.cpp @@ -133,7 +133,27 @@ const char * ConsoleController::inputText(const char * prompt) { const char * previousPrompt = m_editCell.promptText(); m_editCell.setPrompt(promptText); - m_editCell.setText(""); + + /* The user will input some text that is stored in the edit cell. When the + * input is finished, we want to clear that cell and return the input text. + * We choose to shift the input in the edit cell and put a null char in first + * position, so that the cell seems cleared but we can still use it to store + * the input. + * To do so, we need to reduce the cell buffer size by one, so that the input + * can be shifted afterwards, even if it has maxSize. + * + * Illustration of a input sequence: + * | | | | | | | | | <- the edit cell buffer + * |0| | | | | | |X| <- clear and reduce the size + * |a|0| | | | | |X| <- user input + * |a|b|0| | | | |X| <- user input + * |a|b|c|0| | | |X| <- user input + * |a|b|c|d|0| | |X| <- last user input + * | |a|b|c|d|0| | | <- increase the buffer size and shift the user input by one + * |0|a|b|c|d|0| | | <- put a zero in first position: the edit cell seems empty + */ + + m_editCell.clearAndReduceSize(); // Reload the history m_selectableTableView.reloadData(); @@ -146,16 +166,18 @@ const char * ConsoleController::inputText(const char * prompt) { return c->inputRunLoopActive(); }, this); - // Handle the input text + // Print the prompt and the input text if (promptText != nullptr) { printText(promptText, s - promptText); } const char * text = m_editCell.text(); - printText(text, strlen(text)); + size_t textSize = strlen(text); + printText(text, textSize); flushOutputAccumulationBufferToStore(); + // Clear the edit cell and return the input + text = m_editCell.shiftCurrentTextAndClear(); m_editCell.setPrompt(previousPrompt); - m_editCell.setText(""); refreshPrintOutput(); return text; diff --git a/apps/code/console_edit_cell.cpp b/apps/code/console_edit_cell.cpp index e5a4a2708..30e0472fb 100644 --- a/apps/code/console_edit_cell.cpp +++ b/apps/code/console_edit_cell.cpp @@ -55,4 +55,21 @@ bool ConsoleEditCell::insertText(const char * text) { return m_textField.handleEventWithText(text); } +void ConsoleEditCell::clearAndReduceSize() { + setText(""); + size_t previousBufferSize = m_textField.draftTextBufferSize(); + assert(previousBufferSize > 1); + m_textField.setDraftTextBufferSize(previousBufferSize - 1); +} + +const char * ConsoleEditCell::shiftCurrentTextAndClear() { + size_t previousBufferSize = m_textField.draftTextBufferSize(); + m_textField.setDraftTextBufferSize(previousBufferSize + 1); + char * textFieldBuffer = m_textField.draftTextBuffer(); + char * newTextPosition = textFieldBuffer + 1; + strlcpy(newTextPosition, textFieldBuffer, previousBufferSize); + textFieldBuffer[0] = 0; + return newTextPosition; +} + } diff --git a/apps/code/console_edit_cell.h b/apps/code/console_edit_cell.h index 739cb40f2..3c076f9a1 100644 --- a/apps/code/console_edit_cell.h +++ b/apps/code/console_edit_cell.h @@ -33,6 +33,8 @@ public: bool insertText(const char * text); void setPrompt(const char * prompt); const char * promptText() const { return m_promptView.text(); } + void clearAndReduceSize(); + const char * shiftCurrentTextAndClear(); private: PointerTextView m_promptView; TextField m_textField; diff --git a/escher/include/escher/text_field.h b/escher/include/escher/text_field.h index 1fab85d6b..af995328c 100644 --- a/escher/include/escher/text_field.h +++ b/escher/include/escher/text_field.h @@ -28,6 +28,8 @@ public: void reinitDraftTextBuffer() { m_contentView.reinitDraftTextBuffer(); } bool isEditing() const override; char * draftTextBuffer() const { return const_cast(m_contentView.editedText()); } + void setDraftTextBufferSize(size_t size) { m_contentView.setDraftTextBufferSize(size); } + size_t draftTextBufferSize() const { return m_contentView.draftTextBufferSize(); } size_t draftTextLength() const; void setText(const char * text); void setEditing(bool isEditing) override { m_contentView.setEditing(isEditing); } @@ -42,8 +44,19 @@ public: bool shouldFinishEditing(Ion::Events::Event event) override; const KDFont * font() const { return m_contentView.font(); } protected: + class ContentView : public TextInput::ContentView { public: + /* In some app (ie Calculation), text fields record expression results whose + * lengths can reach 70 (ie + * [[1.234567e-123*e^(1.234567e-123*i), 1.234567e-123*e^(1.234567e-123*i)]]). + * In order to be able to record those output text, k_maxBufferSize must be + * over 70. + * Furthermore, we want ot be able to write an adjacency matrix of size 10 + * so we need at least 2 brackets + 10 * (2 brackets + 10 digits + 9 commas) + * = 212 characters. */ + constexpr static int k_maxBufferSize = 220; + ContentView(char * textBuffer, size_t textBufferSize, size_t draftTextBufferSize, const KDFont * font, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor backgroundColor); void setBackgroundColor(KDColor backgroundColor); KDColor backgroundColor() const { return m_backgroundColor; } @@ -56,7 +69,8 @@ protected: void setText(const char * text); void setEditing(bool isEditing); void reinitDraftTextBuffer(); - void setDraftTextBufferSize(size_t size) { m_draftTextBufferSize = size; } + void setDraftTextBufferSize(size_t size) { assert(size <= k_maxBufferSize); m_draftTextBufferSize = size; } + size_t draftTextBufferSize() const { return m_draftTextBufferSize; } /* If the text to be appended is too long to be added without overflowing the * buffer, nothing is done (not even adding few letters from the text to reach * the maximum buffer capacity) and false is returned. */ @@ -67,15 +81,6 @@ protected: void willModifyTextBuffer(); void didModifyTextBuffer(); size_t deleteSelection() override; - /* In some app (ie Calculation), text fields record expression results whose - * lengths can reach 70 (ie - * [[1.234567e-123*e^(1.234567e-123*i), 1.234567e-123*e^(1.234567e-123*i)]]). - * In order to be able to record those output text, k_maxBufferSize must be - * over 70. - * Furthermore, we want ot be able to write an adjacency matrix of size 10 - * so we need at least 2 brackets + 10 * (2 brackets + 10 digits + 9 commas) - * = 212 characters. */ - constexpr static int k_maxBufferSize = 220; private: void layoutSubviews(bool force = false) override; KDRect glyphFrameAtPosition(const char * buffer, const char * position) const override; @@ -87,8 +92,10 @@ protected: KDColor m_textColor; KDColor m_backgroundColor; }; + const ContentView * nonEditableContentView() const override { return &m_contentView; } ContentView m_contentView; + private: bool privateHandleEvent(Ion::Events::Event event); bool privateHandleMoveEvent(Ion::Events::Event event); From 24d9f372059f3d3990f0656e6b43297a7815de0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 19 Feb 2020 17:59:00 +0100 Subject: [PATCH 04/11] [apps/calculation] Clean how cell subtype is selected/ This fixes crashes: indeed, in the way it was done before, we called scrollToSubviewOfTypeOfCellAtLocation after setting the new selected subtype and before reloading the data. However, selecting a new subtype might expand the selected cell which can temper with the cell repartition. If so, we need to reload the data to be able to call 'selectedCell' for instance. --- apps/calculation/history_controller.cpp | 4 ++++ apps/calculation/history_view_cell.cpp | 3 --- apps/calculation/selectable_table_view.cpp | 17 ++++++----------- escher/include/escher/selectable_table_view.h | 3 +-- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index 22d4bf8e9..74b548e1a 100644 --- a/apps/calculation/history_controller.cpp +++ b/apps/calculation/history_controller.cpp @@ -231,6 +231,10 @@ void HistoryController::historyViewCellDidChangeSelection(HistoryViewCell ** cel m_selectableTableView.reloadData(); } + // It might be necessary to scroll to the sub type if the cell overflows the screen + if (selectedRow() >= 0) { + m_selectableTableView.scrollToSubviewOfTypeOfCellAtLocation(type, m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); + } // Fill the selected cell and the previous selected cell because cells repartition might have changed *cell = static_cast(m_selectableTableView.selectedCell()); *previousCell = static_cast(m_selectableTableView.cellAtLocation(previousSelectedCellX, previousSelectedCellY)); diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index 50c494371..e706d847f 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -302,9 +302,6 @@ bool HistoryViewCell::handleEvent(Ion::Events::Event event) { otherSubviewType = HistoryViewCellDataSource::SubviewType::Output; } m_dataSource->setSelectedSubviewType(otherSubviewType, true); - CalculationSelectableTableView * tableView = (CalculationSelectableTableView *)parentResponder(); - tableView->scrollToSubviewOfTypeOfCellAtLocation(otherSubviewType, tableView->selectedColumn(), tableView->selectedRow()); - Container::activeApp()->setFirstResponder(this); return true; } return false; diff --git a/apps/calculation/selectable_table_view.cpp b/apps/calculation/selectable_table_view.cpp index c00bed4b1..717d727fe 100644 --- a/apps/calculation/selectable_table_view.cpp +++ b/apps/calculation/selectable_table_view.cpp @@ -44,10 +44,8 @@ void CalculationSelectableTableView::scrollToSubviewOfTypeOfCellAtLocation(Histo } /* As we scroll, the selected calculation does not use the same history view * cell, thus, we want to deselect the previous used history view cell. */ - if (selectedRow() >= 0) { - HighlightCell * previousCell = selectedCell(); - previousCell->setHighlighted(false); - } + unhighlightSelectedCell(); + /* Main part of the scroll */ KDCoordinate contentOffsetX = contentOffset().x(); KDCoordinate contentOffsetY = dataSource()->cumulatedHeightFromIndex(j+1) - maxContentHeightDisplayableWithoutScrolling(); @@ -58,16 +56,13 @@ void CalculationSelectableTableView::scrollToSubviewOfTypeOfCellAtLocation(Histo contentOffsetY = dataSource()->cumulatedHeightFromIndex(j); } } - /* For the same reason, we have to rehighlight the new history view cell and - * inform the delegate which history view cell is highlighted even if the - * selected calculation has not changed. */ setContentOffset(KDPoint(contentOffsetX, contentOffsetY)); - HighlightCell * cell = cellAtLocation(i, j); + /* For the same reason, we have to rehighlight the new history view cell and + * reselect the first responder. */ + HistoryViewCell * cell = (HistoryViewCell *)(selectedCell()); assert(cell); cell->setHighlighted(true); - if (m_delegate) { - m_delegate->tableViewDidChangeSelection(this, selectedColumn(), selectedRow()); - } + Container::activeApp()->setFirstResponder(cell); } diff --git a/escher/include/escher/selectable_table_view.h b/escher/include/escher/selectable_table_view.h index 462041a37..f10a606be 100644 --- a/escher/include/escher/selectable_table_view.h +++ b/escher/include/escher/selectable_table_view.h @@ -32,10 +32,9 @@ public: bool selectCellAtLocation(int i, int j, bool setFirstResponder = true, bool withinTemporarySelection = false); HighlightCell * selectedCell(); protected: + void unhighlightSelectedCell(); SelectableTableViewDataSource * m_selectionDataSource; SelectableTableViewDelegate * m_delegate; -private: - void unhighlightSelectedCell(); }; #endif From 94daf465c4d3c1b4a7199499b853e3984b7b8c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 20 Feb 2020 10:16:56 +0100 Subject: [PATCH 05/11] [escher][apps/calculation] Improve ScrollView::scrollToContentRect to scroll "smartly" when scrolling to a too-big-to-be-displayed rect This fixes calculation history navigation on big cells (that are bigger than the displayed table) --- apps/calculation/selectable_table_view.cpp | 12 --------- escher/src/scroll_view.cpp | 29 ++++++++++++++++++++-- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/apps/calculation/selectable_table_view.cpp b/apps/calculation/selectable_table_view.cpp index 717d727fe..e2d81e53c 100644 --- a/apps/calculation/selectable_table_view.cpp +++ b/apps/calculation/selectable_table_view.cpp @@ -24,18 +24,6 @@ void CalculationSelectableTableView::scrollToCell(int i, int j) { KDCoordinate contentOffsetY = dataSource()->cumulatedHeightFromIndex(dataSource()->numberOfRows()) - maxContentHeightDisplayableWithoutScrolling(); setContentOffset(KDPoint(contentOffsetX, contentOffsetY)); } - if (dataSource()->numberOfRows() > j && dataSource()->numberOfColumns() > i && dataSource()->rowHeight(j) > bounds().height()) { - KDCoordinate contentOffsetX = contentOffset().x(); - KDCoordinate contentOffsetY = contentOffset().y(); - if (contentOffsetY > dataSource()->cumulatedHeightFromIndex(j) && contentOffsetY > dataSource()->cumulatedHeightFromIndex(j+1)) { - // Let's scroll the tableView to align the top of the cell to the top - contentOffsetY = dataSource()->cumulatedHeightFromIndex(j); - } else { - // Let's scroll the tableView to align the bottom of the cell to the bottom - contentOffsetY = dataSource()->cumulatedHeightFromIndex(j+1) - maxContentHeightDisplayableWithoutScrolling(); - } - setContentOffset(KDPoint(contentOffsetX, contentOffsetY)); - } } void CalculationSelectableTableView::scrollToSubviewOfTypeOfCellAtLocation(HistoryViewCellDataSource::SubviewType subviewType, int i, int j) { diff --git a/escher/src/scroll_view.cpp b/escher/src/scroll_view.cpp index 834aae7c0..d85e0dbcc 100644 --- a/escher/src/scroll_view.cpp +++ b/escher/src/scroll_view.cpp @@ -81,8 +81,33 @@ void ScrollView::scrollToContentPoint(KDPoint p, bool allowOverscroll) { } void ScrollView::scrollToContentRect(KDRect rect, bool allowOverscroll) { - scrollToContentPoint(rect.topLeft(), allowOverscroll); - scrollToContentPoint(rect.bottomRight(), allowOverscroll); + KDPoint tl = rect.topLeft(); + KDPoint br = rect.bottomRight(); + KDRect visibleRect = visibleContentRect(); + /* We first check that we can display the whole rect. If we can't, we focus + * the croll to the closest part of the rect. */ + if (visibleRect.height() < rect.height()) { + // The visible rect is too small to display 'rect' + if (rect.top() >= visibleRect.top()) { + // We scroll to display the top part of rect + br = KDPoint(br.x(), rect.top() + visibleRect.height()); + } else { + // We scroll to display the bottom part of rect + tl = KDPoint(tl.x(), rect.bottom() - visibleRect.height()); + } + } + if (visibleRect.width() < rect.width()) { + // The visible rect is too small to display 'rect' + if (rect.left() >= visibleRect.left()) { + // We scroll to display the left part of rect + br = KDPoint(rect.left() + visibleRect.width(), br.y()); + } else { + // We scroll to display the right part of rect + tl = KDPoint(rect.right() - visibleRect.width(), tl.y()); + } + } + scrollToContentPoint(tl, allowOverscroll); + scrollToContentPoint(br, allowOverscroll); } KDRect ScrollView::visibleContentRect() { From 99e88df284f95070d8f1e1ff6e22a04a4b13e082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 20 Feb 2020 10:24:43 +0100 Subject: [PATCH 06/11] [apps/calculation] Additional outputs: Clean cells when the pop-up disappears to avoid uselessly overloading the Poincare pool --- .../additional_outputs/expressions_list_controller.cpp | 7 +++++++ .../additional_outputs/expressions_list_controller.h | 1 + .../additional_outputs/illustrated_list_controller.cpp | 4 ++++ .../scrollable_three_expressions_cell.cpp | 6 +++++- .../additional_outputs/scrollable_three_expressions_cell.h | 2 ++ 5 files changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/calculation/additional_outputs/expressions_list_controller.cpp b/apps/calculation/additional_outputs/expressions_list_controller.cpp index 515ec3b4a..be7a5ed36 100644 --- a/apps/calculation/additional_outputs/expressions_list_controller.cpp +++ b/apps/calculation/additional_outputs/expressions_list_controller.cpp @@ -24,6 +24,13 @@ int ExpressionsListController::reusableCellCount(int type) { return k_maxNumberOfCells; } +void ExpressionsListController::viewDidDisappear() { + // Reset cell memoization to avoid taking extra space in the pool + for (int i = 0; i < k_maxNumberOfCells; i++) { + m_cells[i].setLayout(Layout()); + } +} + HighlightCell * ExpressionsListController::reusableCell(int index, int type) { return &m_cells[index]; } diff --git a/apps/calculation/additional_outputs/expressions_list_controller.h b/apps/calculation/additional_outputs/expressions_list_controller.h index 64152f6be..afc59e9c1 100644 --- a/apps/calculation/additional_outputs/expressions_list_controller.h +++ b/apps/calculation/additional_outputs/expressions_list_controller.h @@ -13,6 +13,7 @@ public: ExpressionsListController(Responder * parentResponder, EditExpressionController * editExpressionController); // Responder + void viewDidDisappear() override; void didEnterResponderChain(Responder * previousFirstResponder) override; //ListViewDataSource diff --git a/apps/calculation/additional_outputs/illustrated_list_controller.cpp b/apps/calculation/additional_outputs/illustrated_list_controller.cpp index 67bc939e6..53bfeb41c 100644 --- a/apps/calculation/additional_outputs/illustrated_list_controller.cpp +++ b/apps/calculation/additional_outputs/illustrated_list_controller.cpp @@ -42,6 +42,10 @@ void IllustratedListController::viewDidDisappear() { Poincare::Symbol s = Poincare::Symbol::Builder(expressionSymbol()); context->setExpressionForSymbolAbstract(m_savedExpression, s); } + // Reset cell memoization to avoid taking extra space in the pool + for (int i = 0; i < k_maxNumberOfAdditionalCalculations; i++) { + m_additionalCalculationCells[i].resetMemoization(); + } } int IllustratedListController::numberOfRows() const { diff --git a/apps/calculation/additional_outputs/scrollable_three_expressions_cell.cpp b/apps/calculation/additional_outputs/scrollable_three_expressions_cell.cpp index e5bc0b540..d90fc628d 100644 --- a/apps/calculation/additional_outputs/scrollable_three_expressions_cell.cpp +++ b/apps/calculation/additional_outputs/scrollable_three_expressions_cell.cpp @@ -4,11 +4,15 @@ namespace Calculation { +void ScrollableThreeExpressionsView::resetMemoization() { + setLayouts(Poincare::Layout(), Poincare::Layout(), Poincare::Layout()); +} + void ScrollableThreeExpressionsView::setCalculation(Calculation * calculation) { Poincare::Context * context = App::app()->localContext(); // Clean the layouts to make room in the pool - setLayouts(Poincare::Layout(), Poincare::Layout(), Poincare::Layout()); + resetMemoization(); // Create the input layout Poincare::Layout inputLayout = calculation->createInputLayout(); diff --git a/apps/calculation/additional_outputs/scrollable_three_expressions_cell.h b/apps/calculation/additional_outputs/scrollable_three_expressions_cell.h index 6bb8155f1..42c18becb 100644 --- a/apps/calculation/additional_outputs/scrollable_three_expressions_cell.h +++ b/apps/calculation/additional_outputs/scrollable_three_expressions_cell.h @@ -14,6 +14,7 @@ public: setMargins(Metric::CommonSmallMargin, Metric::CommonSmallMargin, Metric::CommonSmallMargin, Metric::CommonSmallMargin); // Left Right margins are already added by TableCell setBackgroundColor(KDColorWhite); } + void resetMemoization(); void setCalculation(Calculation * calculation); private: class ContentCell : public Shared::AbstractScrollableMultipleExpressionsView::ContentCell { @@ -50,6 +51,7 @@ public: View * labelView() const override { return (View *)&m_view; } void setHighlighted(bool highlight) override { m_view.evenOddCell()->setHighlighted(highlight); } + void resetMemoization() { m_view.resetMemoization(); } void setCalculation(Calculation * calculation); void setDisplayCenter(bool display); ScrollableThreeExpressionsView::SubviewPosition selectedSubviewPosition() { return m_view.selectedSubviewPosition(); } From a2a47bb8aee1d4b4c098eca6bff5c4fb1b25fef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 20 Feb 2020 10:25:55 +0100 Subject: [PATCH 07/11] [apps/calculation] Additional outputs: dismiss the pop-up before doing any Poincare computations to optimize the available space in the Poincare pool --- apps/calculation/additional_outputs/list_controller.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/calculation/additional_outputs/list_controller.cpp b/apps/calculation/additional_outputs/list_controller.cpp index c709b9f2d..3ea43cf6e 100644 --- a/apps/calculation/additional_outputs/list_controller.cpp +++ b/apps/calculation/additional_outputs/list_controller.cpp @@ -32,8 +32,12 @@ bool ListController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK || event == Ion::Events::EXE) { char buffer[Constant::MaxSerializedExpressionSize]; textAtIndex(buffer, Constant::MaxSerializedExpressionSize, selectedRow()); - m_editExpressionController->insertTextBody(buffer); + /* The order is important here: we dismiss the pop-up first because it + * clears the Poincare pool from the layouts used to display the pop-up. + * Thereby it frees memory to do Poincare computations required by + * insertTextBody. */ Container::activeApp()->dismissModalViewController(); + m_editExpressionController->insertTextBody(buffer); Container::activeApp()->setFirstResponder(m_editExpressionController); return true; } From c3d8e09ceb68f4a74ed86f57d8e25317291bb149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 20 Feb 2020 10:40:50 +0100 Subject: [PATCH 08/11] [apps/calculation] Calculation: invalid heights memoization when forcing the display output --- apps/calculation/calculation.cpp | 6 ++++++ apps/calculation/calculation.h | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index a5fe1e80b..ae3367ff0 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -274,6 +274,12 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) { return m_displayOutput; } +void Calculation::forceDisplayOutput(DisplayOutput d) { + m_displayOutput = d; + // Reset heights memoization as it might have changed when we modify the display output + m_height = -1; + m_expandedHeight = -1; +} bool Calculation::shouldOnlyDisplayExactOutput() { /* If the input is a "store in a function", do not display the approximate * result. This prevents x->f(x) from displaying x = undef. */ diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index 98c352f21..4dbe4fc08 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -88,7 +88,7 @@ public: // Displayed output DisplayOutput displayOutput(Poincare::Context * context); - void forceDisplayOutput(DisplayOutput d) { m_displayOutput = d; } + void forceDisplayOutput(DisplayOutput d); bool shouldOnlyDisplayExactOutput(); EqualSign exactAndApproximateDisplayedOutputsAreEqual(Poincare::Context * context); From 9bc0776a6abfde616e4895306eda3c89e58196d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 19 Feb 2020 14:32:37 +0100 Subject: [PATCH 09/11] [apps/code] Allow interruption of infinite print loops while (True): print("hello") is now interruptible when the user presses Back --- apps/code/console_controller.cpp | 33 ++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/apps/code/console_controller.cpp b/apps/code/console_controller.cpp index ee013f0c5..697872131 100644 --- a/apps/code/console_controller.cpp +++ b/apps/code/console_controller.cpp @@ -415,21 +415,26 @@ void ConsoleController::printText(const char * text, size_t length) { /* If there is no new line in text, just append it to the output * accumulation buffer. */ appendTextToOutputAccumulationBuffer(text, length); - return; + } else { + if (textCutIndex < length - 1) { + /* If there is a new line in the middle of the text, we have to store at + * least two new console lines in the console store. */ + printText(text, textCutIndex + 1); + printText(&text[textCutIndex+1], length - (textCutIndex + 1)); + return; + } + /* There is a new line at the end of the text, we have to store the line in + * the console store. */ + assert(textCutIndex == length - 1); + appendTextToOutputAccumulationBuffer(text, length-1); + flushOutputAccumulationBufferToStore(); + micropython_port_vm_hook_refresh_print(); } - if (textCutIndex < length - 1) { - /* If there is a new line in the middle of the text, we have to store at - * least two new console lines in the console store. */ - printText(text, textCutIndex + 1); - printText(&text[textCutIndex+1], length - (textCutIndex + 1)); - return; - } - /* There is a new line at the end of the text, we have to store the line in - * the console store. */ - assert(textCutIndex == length - 1); - appendTextToOutputAccumulationBuffer(text, length-1); - flushOutputAccumulationBufferToStore(); - micropython_port_vm_hook_refresh_print(); + /* micropython_port_vm_hook_loop is not enough to detect user interruptions, + * because it calls micropython_port_interrupt_if_needed every 20000 + * operations, and a print operation is quite long. We thus explicitely call + * micropython_port_interrupt_if_needed here. */ + micropython_port_interrupt_if_needed(); } void ConsoleController::autoImportScript(Script script, bool force) { From 0d8cb0123b01af198fb819f67f6b7bf801450dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 20 Feb 2020 11:23:13 +0100 Subject: [PATCH 10/11] [python/port] Fix user interruption char set up print can be user interrupted, so the interruption char needs to be set for the whole runCode method --- python/port/port.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/python/port/port.cpp b/python/port/port.cpp index a1405ae6e..5c56064cc 100644 --- a/python/port/port.cpp +++ b/python/port/port.cpp @@ -33,6 +33,10 @@ void MicroPython::ExecutionEnvironment::runCode(const char * str) { assert(sCurrentExecutionEnvironment == nullptr); sCurrentExecutionEnvironment = this; + /* Set the user interruption now, as it is needed for the normal execution and + * for the exception handling (because of print). */ + mp_hal_set_interrupt_char((int)Ion::Keyboard::Key::Back); + nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { mp_lexer_t *lex = mp_lexer_new_from_str_len(0, str, strlen(str), false); @@ -41,9 +45,7 @@ void MicroPython::ExecutionEnvironment::runCode(const char * str) { // TODO: add a parameter when other input types (file, eval) are required mp_parse_tree_t pt = mp_parse(lex, MP_PARSE_SINGLE_INPUT); mp_obj_t module_fun = mp_compile(&pt, lex->source_name, MP_EMIT_OPT_NONE, true); - mp_hal_set_interrupt_char((int)Ion::Keyboard::Key::Back); mp_call_function_0(module_fun); - mp_hal_set_interrupt_char(-1); // Disable interrupt nlr_pop(); } else { // Uncaught exception /* mp_obj_print_exception is supposed to handle error printing. However, @@ -81,6 +83,9 @@ void MicroPython::ExecutionEnvironment::runCode(const char * str) { /* End of mp_obj_print_exception. */ } + // Disable the user interruption + mp_hal_set_interrupt_char(-1); + assert(sCurrentExecutionEnvironment == this); sCurrentExecutionEnvironment = nullptr; } From 80c03220dd10ddf1d2bdadc55b240b8a4dc03c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 20 Feb 2020 11:38:02 +0100 Subject: [PATCH 11/11] [poincare/function] Fix getVariables Faulty scenario: Equation x+y=f(0) without defining any variable --- poincare/src/function.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poincare/src/function.cpp b/poincare/src/function.cpp index 8f0a325b5..90ec61731 100644 --- a/poincare/src/function.cpp +++ b/poincare/src/function.cpp @@ -39,7 +39,7 @@ int FunctionNode::getVariables(Context * context, isVariableTest isVariable, cha Function f(this); Expression e = SymbolAbstract::Expand(f, context, true); if (e.isUninitialized()) { - return 0; + return nextVariableIndex; } return e.node()->getVariables(context, isVariable, variables, maxSizeVariable, nextVariableIndex); }