Files
Upsilon/apps/variable_box_controller.cpp
Léa Saviot eea56488e6 [apps] Fix VarBoxController::viewDidDisappear parent method call order
Before, we tidied the memoization before calling the parent's
viewDidDisappear method. As the parent method needed some cell heights,
it used the VarBoxController memoization, thus re-creating layouts that
would stay in the pool after closing the app.

Scenario:
Go in the Graph app, create f(x) = 1, empty g(x) and h(x), create p(x)
and while editing go in the variable box controller, all the way down,
then hit the Home button.
2018-11-23 12:04:06 +01:00

259 lines
9.2 KiB
C++

#include "variable_box_controller.h"
#include "shared/global_context.h"
#include "shared/poincare_helpers.h"
#include "shared/storage_function.h"
#include "shared/storage_cartesian_function.h"
#include "graph/storage_cartesian_function_store.h"
#include "constant.h"
#include <escher/metric.h>
#include <assert.h>
#include <poincare/matrix_layout.h>
#include <poincare/layout_helper.h>
using namespace Poincare;
using namespace Shared;
using namespace Ion;
VariableBoxController::VariableBoxController() :
NestedMenuController(nullptr, I18n::Message::Variables),
m_currentPage(Page::RootMenu),
m_lockPageDelete(Page::RootMenu),
m_firstMemoizedLayoutIndex(0)
{
}
void VariableBoxController::viewWillAppear() {
NestedMenuController::viewWillAppear();
m_currentPage = Page::RootMenu;
m_selectableTableView.reloadData();
}
void VariableBoxController::viewDidDisappear() {
if (isDisplayingEmptyController()) {
pop();
}
NestedMenuController::viewDidDisappear();
/* NestedMenuController::viewDidDisappear might need cell heights, which would
* use the VariableBoxController cell heights memoization. We thus reset the
* VariableBoxController layouts only after calling the parent's
* viewDidDisappear. */
// Tidy the layouts displayed in the VariableBoxController to clean TreePool
for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) {
m_leafCells[i].setLayout(Layout());
m_leafCells[i].setAccessoryLayout(Layout());
}
resetMemoization();
}
bool VariableBoxController::handleEvent(Ion::Events::Event event) {
/* We do not want to handle backspace event if:
* - On the root menu page
* The deletion on the current page is locked
* - The empty controller is displayed
*/
if (event == Ion::Events::Backspace && m_currentPage != Page::RootMenu && m_lockPageDelete != m_currentPage && !isDisplayingEmptyController()) {
int rowIndex = selectedRow();
m_selectableTableView.deselectTable();
Storage::Record record = recordAtIndex(rowIndex);
record.destroy();
int newSelectedRow = rowIndex >= numberOfRows() ? numberOfRows()-1 : rowIndex;
selectCellAtLocation(selectedColumn(), newSelectedRow);
m_selectableTableView.reloadData();
displayEmptyController();
return true;
}
return NestedMenuController::handleEvent(event);
}
int VariableBoxController::numberOfRows() {
switch (m_currentPage) {
case Page::RootMenu:
return k_numberOfMenuRows;
case Page::Expression:
return Storage::sharedStorage()->numberOfRecordsWithExtension(GlobalContext::expExtension);
case Page::Function:
return Storage::sharedStorage()->numberOfRecordsWithExtension(GlobalContext::funcExtension);
default:
return 0;
}
}
int VariableBoxController::reusableCellCount(int type) {
assert(type < 2);
if (type == 0) {
return k_maxNumberOfDisplayedRows;
}
return k_numberOfMenuRows;
}
void VariableBoxController::willDisplayCellForIndex(HighlightCell * cell, int index) {
if (m_currentPage == Page::RootMenu) {
I18n::Message label = nodeLabelAtIndex(index);
MessageTableCell * myCell = (MessageTableCell *)cell;
myCell->setMessage(label);
return;
}
ExpressionTableCellWithExpression * myCell = (ExpressionTableCellWithExpression *)cell;
Storage::Record record = recordAtIndex(index);
assert(Shared::StorageFunction::k_maxNameWithArgumentSize > SymbolAbstract::k_maxNameSize);
char symbolName[Shared::StorageFunction::k_maxNameWithArgumentSize];
size_t symbolLength = 0;
if (m_currentPage == Page::Expression) {
symbolLength = SymbolAbstract::TruncateExtension(symbolName, record.fullName(), SymbolAbstract::k_maxNameSize);
} else {
assert(m_currentPage == Page::Function);
StorageCartesianFunction f(record);
symbolLength = f.nameWithArgument(
symbolName,
Shared::StorageFunction::k_maxNameWithArgumentSize,
Graph::StorageCartesianFunctionStore::Symbol());
}
Layout symbolLayout = LayoutHelper::String(symbolName, symbolLength);
myCell->setLayout(symbolLayout);
myCell->setAccessoryLayout(expressionLayoutForRecord(record, index));
myCell->reloadCell();
}
KDCoordinate VariableBoxController::rowHeight(int index) {
if (m_currentPage != Page::RootMenu) {
Layout layoutR = expressionLayoutForRecord(recordAtIndex(index), index);
if (!layoutR.isUninitialized()) {
return max(layoutR.layoutSize().height()+k_leafMargin, Metric::ToolboxRowHeight);
}
}
return NestedMenuController::rowHeight(index);
}
int VariableBoxController::typeAtLocation(int i, int j) {
if (m_currentPage == Page::RootMenu) {
return 1;
}
return 0;
}
ExpressionTableCellWithExpression * VariableBoxController::leafCellAtIndex(int index) {
assert(index >= 0 && index < k_maxNumberOfDisplayedRows);
return &m_leafCells[index];
}
MessageTableCellWithChevron * VariableBoxController::nodeCellAtIndex(int index) {
assert(index >= 0 && index < k_numberOfMenuRows);
return &m_nodeCells[index];
}
VariableBoxController::Page VariableBoxController::pageAtIndex(int index) {
Page pages[2] = {Page::Expression, Page::Function};
return pages[index];
}
bool VariableBoxController::selectSubMenu(int selectedRow) {
m_selectableTableView.deselectTable();
m_currentPage = pageAtIndex(selectedRow);
bool selectSubMenu = NestedMenuController::selectSubMenu(selectedRow);
if (displayEmptyController()) {
return true;
}
return selectSubMenu;
}
bool VariableBoxController::returnToPreviousMenu() {
if (isDisplayingEmptyController()) {
pop();
} else {
m_selectableTableView.deselectTable();
resetMemoization();
}
m_currentPage = Page::RootMenu;
return NestedMenuController::returnToPreviousMenu();
}
bool VariableBoxController::selectLeaf(int selectedRow) {
if (isDisplayingEmptyController()) {
/* We do not want to handle OK/EXE events in that case. */
return false;
}
// Deselect the table
assert(selectedRow >= 0 && selectedRow < numberOfRows());
m_selectableTableView.deselectTable();
// Get the name text to insert
Storage::Record record = recordAtIndex(selectedRow);
assert(Shared::StorageFunction::k_maxNameWithArgumentSize > 0);
assert(Shared::StorageFunction::k_maxNameWithArgumentSize > SymbolAbstract::k_maxNameSize);
size_t nameToHandleMaxSize = Shared::StorageFunction::k_maxNameWithArgumentSize - 1;
char nameToHandle[nameToHandleMaxSize];
size_t nameLength = SymbolAbstract::TruncateExtension(nameToHandle, record.fullName(), nameToHandleMaxSize);
if (m_currentPage == Page::Function) {
// Add parentheses to a function name
assert(nameLength < nameToHandleMaxSize);
nameToHandle[nameLength++] = '(';
assert(nameLength < nameToHandleMaxSize);
nameToHandle[nameLength++] = ')';
assert(nameLength < nameToHandleMaxSize);
nameToHandle[nameLength] = 0;
}
// Handle the text
sender()->handleEventWithText(nameToHandle);
app()->dismissModalViewController();
return true;
}
I18n::Message VariableBoxController::nodeLabelAtIndex(int index) {
assert(m_currentPage == Page::RootMenu);
I18n::Message labels[2] = {I18n::Message::Expressions, I18n::Message::Functions};
return labels[index];
}
Layout VariableBoxController::expressionLayoutForRecord(Storage::Record record, int index) {
assert(m_currentPage != Page::RootMenu);
if (index >= m_firstMemoizedLayoutIndex+k_maxNumberOfDisplayedRows || index < m_firstMemoizedLayoutIndex) {
// Change range of layout memoization
int deltaIndex = index >= m_firstMemoizedLayoutIndex + k_maxNumberOfDisplayedRows ? index - k_maxNumberOfDisplayedRows + 1 - m_firstMemoizedLayoutIndex : index - m_firstMemoizedLayoutIndex;
for (int i = 0; i < k_maxNumberOfDisplayedRows-1; i++) {
int j = deltaIndex + i;
m_layouts[i] = j >= 0 && j < k_maxNumberOfDisplayedRows ? m_layouts[j] : Layout();
}
m_firstMemoizedLayoutIndex += deltaIndex;
}
assert(index-m_firstMemoizedLayoutIndex < k_maxNumberOfDisplayedRows);
if (m_layouts[index-m_firstMemoizedLayoutIndex].isUninitialized()) {
m_layouts[index-m_firstMemoizedLayoutIndex] = GlobalContext::ExpressionFromRecord(record).createLayout(Poincare::Preferences::sharedPreferences()->displayMode(), Constant::ShortNumberOfSignificantDigits);
}
return m_layouts[index-m_firstMemoizedLayoutIndex];
}
const char * VariableBoxController::extension() const {
assert(m_currentPage != Page::RootMenu);
return m_currentPage == Page::Function ? GlobalContext::funcExtension : GlobalContext::expExtension;
}
Storage::Record VariableBoxController::recordAtIndex(int rowIndex) {
assert(m_currentPage != Page::RootMenu);
assert(!Storage::sharedStorage()->recordWithExtensionAtIndex(extension(), rowIndex).isNull());
return Storage::sharedStorage()->recordWithExtensionAtIndex(extension(), rowIndex);
}
bool VariableBoxController::displayEmptyController() {
assert(!isDisplayingEmptyController());
/* If the content is empty, we push above an empty controller. */
if (numberOfRows() == 0) {
m_emptyViewController.setType((VariableBoxEmptyController::Type)m_currentPage);
push(&m_emptyViewController);
return true;
}
return false;
}
void VariableBoxController::resetMemoization() {
for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) {
m_layouts[i] = Layout();
}
m_firstMemoizedLayoutIndex = 0;
}