Files
Upsilon/apps/shared/values_controller.cpp
2020-04-16 14:04:56 +02:00

361 lines
12 KiB
C++

#include "values_controller.h"
#include "function_app.h"
#include <poincare/preferences.h>
#include <assert.h>
#include <limits.h>
#include <algorithm>
using namespace Poincare;
namespace Shared {
constexpr int ValuesController::k_maxNumberOfDisplayableRows;
// TODO: use std::abs
static inline int absInt(int x) { return x < 0 ? -x : x; }
// Constructor and helpers
ValuesController::ValuesController(Responder * parentResponder, ButtonRowController * header) :
EditableCellTableViewController(parentResponder),
ButtonRowDelegate(header, nullptr),
m_numberOfColumns(0),
m_numberOfColumnsNeedUpdate(true),
m_firstMemoizedColumn(INT_MAX),
m_firstMemoizedRow(INT_MAX),
m_abscissaParameterController(this)
{
}
void ValuesController::setupSelectableTableViewAndCells(InputEventHandlerDelegate * inputEventHandlerDelegate) {
selectableTableView()->setVerticalCellOverlap(0);
selectableTableView()->setMargins(k_margin, k_scrollBarMargin, k_scrollBarMargin, k_margin);
selectableTableView()->setBackgroundColor(Palette::BackgroundAppsSecondary);
int numberOfAbscissaCells = abscissaCellsCount();
for (int i = 0; i < numberOfAbscissaCells; i++) {
EvenOddEditableTextCell * c = abscissaCells(i);
c->setParentResponder(selectableTableView());
c->editableTextCell()->textField()->setDelegates(inputEventHandlerDelegate, this);
c->editableTextCell()->textField()->setFont(k_font);
}
int numberOfAbscissaTitleCells = abscissaTitleCellsCount();
for (int i = 0; i < numberOfAbscissaTitleCells; i++) {
EvenOddMessageTextCell * c = abscissaTitleCells(i);
c->setMessageFont(k_font);
}
}
// View Controller
const char * ValuesController::title() {
return I18n::translate(I18n::Message::ValuesTab);
}
void ValuesController::viewWillAppear() {
// Reset memoization before any call to willDisplayCellAtLocation
resetMemoization();
EditableCellTableViewController::viewWillAppear();
header()->setSelectedButton(-1);
}
void ValuesController::viewDidDisappear() {
m_numberOfColumnsNeedUpdate = true;
EditableCellTableViewController::viewDidDisappear();
}
// Responder
bool ValuesController::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::Down) {
if (selectedRow() == -1) {
header()->setSelectedButton(-1);
selectableTableView()->selectCellAtLocation(0,0);
Container::activeApp()->setFirstResponder(selectableTableView());
return true;
}
return false;
}
if (event == Ion::Events::Up) {
if (selectedRow() == -1) {
header()->setSelectedButton(-1);
Container::activeApp()->setFirstResponder(tabController());
return true;
}
selectableTableView()->deselectTable();
header()->setSelectedButton(0);
return true;
}
if (event == Ion::Events::Backspace && selectedRow() > 0 &&
selectedRow() <= numberOfElementsInColumn(selectedColumn())) {
int row = selectedRow();
int column = selectedColumn();
intervalAtColumn(column)->deleteElementAtIndex(row-1);
// Reload memoization
for (int i = row; i < numberOfElementsInColumn(column)+1; i++) {
didChangeCell(column, i);
}
selectableTableView()->reloadData();
return true;
}
if (selectedRow() == -1) {
return header()->handleEvent(event);
}
if ((event == Ion::Events::OK || event == Ion::Events::EXE) && selectedRow() == 0) {
ViewController * parameterController = nullptr;
if (typeAtLocation(selectedColumn(), 0) == k_abscissaTitleCellType) {
m_abscissaParameterController.setPageTitle(valuesParameterMessageAtColumn(selectedColumn()));
intervalParameterController()->setInterval(intervalAtColumn(selectedColumn()));
setStartEndMessages(intervalParameterController(), selectedColumn());
parameterController = &m_abscissaParameterController;
} else {
parameterController = functionParameterController();
}
if (parameterController) {
stackController()->push(parameterController);
}
return true;
}
return false;
}
void ValuesController::didBecomeFirstResponder() {
EditableCellTableViewController::didBecomeFirstResponder();
if (selectedRow() == -1) {
selectableTableView()->deselectTable();
header()->setSelectedButton(0);
} else {
header()->setSelectedButton(-1);
}
}
void ValuesController::willExitResponderChain(Responder * nextFirstResponder) {
if (nextFirstResponder == tabController()) {
assert(tabController() != nullptr);
selectableTableView()->deselectTable();
selectableTableView()->scrollToCell(0,0);
header()->setSelectedButton(-1);
}
}
// TableViewDataSource
int ValuesController::numberOfColumns() const {
if (m_numberOfColumnsNeedUpdate) {
updateNumberOfColumns();
m_numberOfColumnsNeedUpdate = false;
}
return m_numberOfColumns;
}
void ValuesController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) {
willDisplayCellAtLocationWithDisplayMode(cell, i, j, Preferences::sharedPreferences()->displayMode());
// The cell is not a title cell and not editable
if (typeAtLocation(i,j) == k_notEditableValueCellType) {
// Special case: last row
if (j == numberOfElementsInColumn(i) + 1) {
static_cast<EvenOddBufferTextCell *>(cell)->setText("");
} else {
static_cast<EvenOddBufferTextCell *>(cell)->setText(memoizedBufferForCell(i, j));
}
}
}
HighlightCell * ValuesController::reusableCell(int index, int type) {
assert(0 <= index && index < reusableCellCount(type));
switch (type) {
case k_abscissaTitleCellType:
return abscissaTitleCells(index);
case k_functionTitleCellType:
return functionTitleCells(index);
case k_editableValueCellType:
return abscissaCells(index);
case k_notEditableValueCellType:
return floatCells(index);
default:
assert(false);
return nullptr;
}
}
int ValuesController::reusableCellCount(int type) {
switch (type) {
case k_abscissaTitleCellType:
return abscissaTitleCellsCount();
case k_functionTitleCellType:
return maxNumberOfFunctions();
case k_editableValueCellType:
return abscissaCellsCount();
case k_notEditableValueCellType:
return maxNumberOfCells();
default:
assert(false);
return 0;
}
}
int ValuesController::typeAtLocation(int i, int j) {
return (i > 0) + 2 * (j > 0);
}
// ButtonRowDelegate
int ValuesController::numberOfButtons(ButtonRowController::Position) const {
if (isEmpty()) {
return 0;
}
return 1;
}
// AlternateEmptyViewDelegate
bool ValuesController::isEmpty() const {
if (functionStore()->numberOfActiveFunctions() == 0) {
return true;
}
return false;
}
Responder * ValuesController::defaultController() {
return tabController();
}
// EditableCellTableViewController
bool ValuesController::setDataAtLocation(double floatBody, int columnIndex, int rowIndex) {
intervalAtColumn(columnIndex)->setElement(rowIndex-1, floatBody);
return true;
}
bool ValuesController::cellAtLocationIsEditable(int columnIndex, int rowIndex) {
return typeAtLocation(columnIndex, rowIndex) == k_editableValueCellType;
}
double ValuesController::dataAtLocation(int columnIndex, int rowIndex) {
return intervalAtColumn(columnIndex)->element(rowIndex-1);
}
void ValuesController::didChangeCell(int column, int row) {
/* Update the row memoization if it exists */
// the first row is never reloaded as it corresponds to title row
assert(row > 0);
// Conversion of coordinates from absolute table to values table
int memoizedRow = valuesRowForAbsoluteRow(row) - m_firstMemoizedRow;
if (0 > memoizedRow || memoizedRow >= k_maxNumberOfDisplayableRows) {
// The changed row is out of the memoized table
return;
}
// Find the abscissa column corresponding to column
int abscissaColumn = 0;
int nbOfColumns = numberOfColumnsForAbscissaColumn(abscissaColumn);
while (column >= nbOfColumns) {
abscissaColumn = nbOfColumns;
nbOfColumns += numberOfColumnsForAbscissaColumn(abscissaColumn);
}
// Update the memoization of rows linked to the changed cell
int nbOfMemoizedColumns = numberOfMemoizedColumn();
int nbOfColumnsForAbscissa = numberOfColumnsForAbscissaColumn(abscissaColumn);
for (int i = abscissaColumn+1; i < abscissaColumn+nbOfColumnsForAbscissa; i++) {
int memoizedI = valuesColumnForAbsoluteColumn(i) - m_firstMemoizedColumn;
if (memoizedI < 0 || memoizedI >= nbOfMemoizedColumns) {
// The changed column is out of the memoized table
continue;
}
fillMemoizedBuffer(i, row, nbOfMemoizedColumns*memoizedRow+memoizedI);
}
}
int ValuesController::numberOfElementsInColumn(int columnIndex) const {
return const_cast<ValuesController *>(this)->intervalAtColumn(columnIndex)->numberOfElements();
}
// Parent controller getters
Responder * ValuesController::tabController() const {
return (parentResponder()->parentResponder()->parentResponder()->parentResponder());
}
StackViewController * ValuesController::stackController() const {
return (StackViewController *)(parentResponder()->parentResponder()->parentResponder());
}
// Model getters
Ion::Storage::Record ValuesController::recordAtColumn(int i) {
assert(typeAtLocation(i, 0) == k_functionTitleCellType);
return functionStore()->activeRecordAtIndex(i-1);
}
// Number of columns memoization
void ValuesController::updateNumberOfColumns() const {
m_numberOfColumns = 1+functionStore()->numberOfActiveFunctions();
}
FunctionStore * ValuesController::functionStore() const {
return FunctionApp::app()->functionStore();
}
// Function evaluation memoization
void ValuesController::resetMemoization() {
m_firstMemoizedColumn = INT_MAX;
m_firstMemoizedRow = INT_MAX;
}
char * ValuesController::memoizedBufferForCell(int i, int j) {
const int nbOfMemoizedColumns = numberOfMemoizedColumn();
// Conversion of coordinates from absolute table to values table
int valuesI = valuesColumnForAbsoluteColumn(i);
int valuesJ = valuesRowForAbsoluteRow(j);
/* Compute the required offset to apply to the memoized table in order to
* display cell (i,j) */
int offsetI = 0;
int offsetJ = 0;
if (valuesI < m_firstMemoizedColumn) {
offsetI = valuesI - m_firstMemoizedColumn;
} else if (valuesI >= m_firstMemoizedColumn + nbOfMemoizedColumns) {
offsetI = valuesI - nbOfMemoizedColumns - m_firstMemoizedColumn + 1;
}
if (valuesJ < m_firstMemoizedRow) {
offsetJ = valuesJ - m_firstMemoizedRow;
} else if (valuesJ >= m_firstMemoizedRow + k_maxNumberOfDisplayableRows) {
offsetJ = valuesJ - k_maxNumberOfDisplayableRows - m_firstMemoizedRow + 1;
}
int offset = -offsetJ*nbOfMemoizedColumns-offsetI;
// Apply the offset
if (offset != 0) {
m_firstMemoizedColumn = m_firstMemoizedColumn + offsetI;
m_firstMemoizedRow = m_firstMemoizedRow + offsetJ;
// Shift already memoized cells
const int numberOfMemoizedCell = k_maxNumberOfDisplayableRows * nbOfMemoizedColumns;
size_t moveLength = (numberOfMemoizedCell - absInt(offset))*valuesCellBufferSize()*sizeof(char);
if (offset > 0 && offset < numberOfMemoizedCell) {
memmove(memoizedBufferAtIndex(offset), memoizedBufferAtIndex(0), moveLength);
} else if (offset < 0 && offset > -numberOfMemoizedCell) {
memmove(memoizedBufferAtIndex(0), memoizedBufferAtIndex(-offset), moveLength);
}
// Compute the buffer of the new cells of the memoized table
int maxI = numberOfValuesColumns() - m_firstMemoizedColumn;
for (int ii = 0; ii < std::min(nbOfMemoizedColumns, maxI); ii++) {
int maxJ = numberOfElementsInColumn(absoluteColumnForValuesColumn(ii+m_firstMemoizedColumn)) - m_firstMemoizedRow;
for (int jj = 0; jj < std::min(k_maxNumberOfDisplayableRows, maxJ); jj++) {
// Escape if already filled
if (ii >= -offsetI && ii < -offsetI + nbOfMemoizedColumns && jj >= -offsetJ && jj < -offsetJ + k_maxNumberOfDisplayableRows) {
continue;
}
fillMemoizedBuffer(absoluteColumnForValuesColumn(m_firstMemoizedColumn + ii),
absoluteRowForValuesRow(m_firstMemoizedRow + jj),
jj * nbOfMemoizedColumns + ii);
}
}
}
return memoizedBufferAtIndex((valuesJ-m_firstMemoizedRow)*nbOfMemoizedColumns + (valuesI-m_firstMemoizedColumn));
}
}