mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-18 16:27:34 +01:00
361 lines
12 KiB
C++
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));
|
|
}
|
|
|
|
}
|