#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 #include #include #include 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(); } // Tidy the layouts used to display the VariableBoxController to clean TreePool for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) { m_leafCells[i].setLayout(Layout()); m_leafCells[i].setAccessoryLayout(Layout()); m_layouts[i] = Layout(); } m_firstMemoizedLayoutIndex = 0; NestedMenuController::viewDidDisappear(); } 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()) { m_selectableTableView.deselectTable(); Storage::Record record = recordAtIndex(selectedRow()); record.destroy(); int newSelectedRow = selectedRow() >= numberOfRows() ? numberOfRows()-1 : selectedRow(); 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)); } 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; } void VariableBoxController::lockDeleteEvent(Page page) { m_lockPageDelete = page; } 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(); } m_selectableTableView.deselectTable(); 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; }