Files
Upsilon/apps/variable_box_controller.cpp
Émilie Feral 2d36f02053 [apps] VariableBoxController: fix bug in right layouts of variable cells.
When the last dislayed page of VariableBoxController was 'Function', the
next time we displayed the symbol page, the expressions on the right of
cells were false the one of functions.
2018-11-23 12:04:08 +01:00

266 lines
9.5 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() {
assert(m_currentPage == Page::RootMenu);
NestedMenuController::viewWillAppear();
}
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());
}
/* We reset the page when view disappears rather than when it appears because
* subview layout is done before viewWillAppear. If the page at that point is
* wrong, the memoized layouts are going be filled with wrong layouts. */
setPage(Page::RootMenu);
}
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);
myCell->reloadCell();
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];
}
void VariableBoxController::setPage(Page page) {
m_currentPage = page;
resetMemoization();
}
bool VariableBoxController::selectSubMenu(int selectedRow) {
m_selectableTableView.deselectTable();
setPage(pageAtIndex(selectedRow));
bool selectSubMenu = NestedMenuController::selectSubMenu(selectedRow);
if (displayEmptyController()) {
return true;
}
return selectSubMenu;
}
bool VariableBoxController::returnToPreviousMenu() {
if (isDisplayingEmptyController()) {
pop();
} else {
m_selectableTableView.deselectTable();
}
setPage(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;
}