mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-19 00:37:25 +01:00
AbstractScrollableExactApproximateExpressionsView children classes reload scroll after reloading the subview selection when entering the responder chain and when cell becomes first responder. We don't reload scroll when setting content of cells as this is done every time we relayout - when scrolling in the table for instance.
300 lines
11 KiB
C++
300 lines
11 KiB
C++
#include "solutions_controller.h"
|
|
#include "app.h"
|
|
#include "../shared/poincare_helpers.h"
|
|
#include <assert.h>
|
|
#include <limits.h>
|
|
#include <poincare/layout_helper.h>
|
|
#include <poincare/code_point_layout.h>
|
|
#include <poincare/horizontal_layout.h>
|
|
#include <poincare/preferences.h>
|
|
#include <poincare/symbol_abstract.h>
|
|
#include <poincare/vertical_offset_layout.h>
|
|
|
|
using namespace Poincare;
|
|
using namespace Shared;
|
|
|
|
namespace Solver {
|
|
|
|
static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { return x > y ? x : y; }
|
|
|
|
SolutionsController::ContentView::ContentView(SolutionsController * controller) :
|
|
m_warningMessageView0(KDFont::SmallFont, I18n::Message::Default, 0.5f, 0.5f, KDColorBlack, Palette::WallScreenDark),
|
|
m_warningMessageView1(KDFont::SmallFont, I18n::Message::Default, 0.5f, 0.5f, KDColorBlack, Palette::WallScreenDark),
|
|
m_selectableTableView(controller),
|
|
m_displayWarningMoreSolutions(false)
|
|
{
|
|
m_selectableTableView.setBackgroundColor(Palette::WallScreenDark);
|
|
m_selectableTableView.setVerticalCellOverlap(0);
|
|
}
|
|
|
|
void SolutionsController::ContentView::drawRect(KDContext * ctx, KDRect rect) const {
|
|
if (m_displayWarningMoreSolutions) {
|
|
ctx->fillRect(KDRect(0, 0, bounds().width(), k_topMargin), Palette::WallScreenDark);
|
|
}
|
|
}
|
|
|
|
void SolutionsController::ContentView::setWarning(bool warning) {
|
|
m_displayWarningMoreSolutions = warning;
|
|
m_selectableTableView.setTopMargin(m_displayWarningMoreSolutions ? 0 : Metric::CommonTopMargin);
|
|
layoutSubviews();
|
|
}
|
|
|
|
void SolutionsController::ContentView::setWarningMessages(I18n::Message message0, I18n::Message message1) {
|
|
m_warningMessageView0.setMessage(message0);
|
|
m_warningMessageView1.setMessage(message1);
|
|
}
|
|
|
|
int SolutionsController::ContentView::numberOfSubviews() const {
|
|
return 1+2*m_displayWarningMoreSolutions;
|
|
}
|
|
|
|
View * SolutionsController::ContentView::subviewAtIndex(int index) {
|
|
assert(index >= 0 && index < 1+2*m_displayWarningMoreSolutions);
|
|
if (index == 0 && m_displayWarningMoreSolutions) {
|
|
return &m_warningMessageView0;
|
|
}
|
|
if (index == 1 && m_displayWarningMoreSolutions) {
|
|
return &m_warningMessageView1;
|
|
}
|
|
return &m_selectableTableView;
|
|
}
|
|
|
|
void SolutionsController::ContentView::layoutSubviews(bool force) {
|
|
if (m_displayWarningMoreSolutions) {
|
|
KDCoordinate textHeight = KDFont::SmallFont->glyphSize().height();
|
|
m_warningMessageView0.setFrame(KDRect(0, k_topMargin/2-textHeight, bounds().width(), textHeight), force);
|
|
m_warningMessageView1.setFrame(KDRect(0, k_topMargin/2, bounds().width(), textHeight), force);
|
|
m_selectableTableView.setFrame(KDRect(0, k_topMargin, bounds().width(), bounds().height()-k_topMargin), force);
|
|
} else {
|
|
m_selectableTableView.setFrame(bounds(), force);
|
|
}
|
|
}
|
|
|
|
SolutionsController::SolutionsController(Responder * parentResponder, EquationStore * equationStore) :
|
|
ViewController(parentResponder),
|
|
m_equationStore(equationStore),
|
|
m_deltaCell(0.5f, 0.5f),
|
|
m_delta2Layout(),
|
|
m_contentView(this)
|
|
{
|
|
m_delta2Layout = HorizontalLayout::Builder(VerticalOffsetLayout::Builder(CodePointLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Position::Superscript), LayoutHelper::String("-4ac", 4, KDFont::SmallFont));
|
|
const char * deltaB = "Δ=b";
|
|
static_cast<HorizontalLayout&>(m_delta2Layout).addOrMergeChildAtIndex(LayoutHelper::String(deltaB, strlen(deltaB), KDFont::SmallFont), 0, false);
|
|
for (int i = 0; i < EquationStore::k_maxNumberOfExactSolutions; i++) {
|
|
m_exactValueCells[i].setParentResponder(m_contentView.selectableTableView());
|
|
}
|
|
for (int i = 0; i < EquationStore::k_maxNumberOfApproximateSolutions; i++) {
|
|
m_approximateValueCells[i].setFont(KDFont::LargeFont);
|
|
}
|
|
for (int i = 0; i < EquationStore::k_maxNumberOfSolutions; i++) {
|
|
m_symbolCells[i].setAlignment(0.5f, 0.5f);
|
|
}
|
|
}
|
|
|
|
/* ViewController */
|
|
const char * SolutionsController::title() {
|
|
if (m_equationStore->type() == EquationStore::Type::Monovariable) {
|
|
return I18n::translate(I18n::Message::ApproximateSolution);
|
|
}
|
|
return I18n::translate(I18n::Message::Solution);
|
|
}
|
|
|
|
View * SolutionsController::view() {
|
|
return &m_contentView;
|
|
}
|
|
|
|
void SolutionsController::viewWillAppear() {
|
|
ViewController::viewWillAppear();
|
|
bool requireWarning = false;
|
|
if (m_equationStore->type() == EquationStore::Type::Monovariable) {
|
|
m_contentView.setWarningMessages(I18n::Message::OnlyFirstSolutionsDisplayed0, I18n::Message::OnlyFirstSolutionsDisplayed1);
|
|
requireWarning = m_equationStore->haveMoreApproximationSolutions(App::app()->localContext());
|
|
} else if (m_equationStore->type() == EquationStore::Type::PolynomialMonovariable && m_equationStore->numberOfSolutions() == 1) {
|
|
assert(Preferences::sharedPreferences()->complexFormat() == Preferences::ComplexFormat::Real);
|
|
m_contentView.setWarningMessages(I18n::Message::PolynomeHasNoRealSolution0, I18n::Message::PolynomeHasNoRealSolution1);
|
|
requireWarning = true;
|
|
}
|
|
m_contentView.setWarning(requireWarning);
|
|
m_contentView.selectableTableView()->reloadData();
|
|
if (selectedRow() < 0) {
|
|
selectCellAtLocation(0, 0);
|
|
}
|
|
}
|
|
|
|
void SolutionsController::didEnterResponderChain(Responder * previousFirstResponder) {
|
|
// Select the most left present subview on all cells and reinitialize scroll
|
|
for (int i = 0; i < EquationStore::k_maxNumberOfExactSolutions; i++) {
|
|
m_exactValueCells[i].reinitSelection();
|
|
}
|
|
}
|
|
|
|
/* AlternateEmptyRowDelegate */
|
|
|
|
bool SolutionsController::isEmpty() const {
|
|
if (m_equationStore->numberOfDefinedModels() == 0 || m_equationStore->numberOfSolutions() == 0 || m_equationStore->numberOfSolutions() == INT_MAX) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
I18n::Message SolutionsController::emptyMessage() {
|
|
if (m_equationStore->numberOfSolutions() == INT_MAX) {
|
|
return I18n::Message::InfiniteNumberOfSolutions;
|
|
}
|
|
if (m_equationStore->type() == EquationStore::Type::Monovariable) {
|
|
return I18n::Message::NoSolutionInterval;
|
|
}
|
|
if (m_equationStore->numberOfDefinedModels() <= 1) {
|
|
return I18n::Message::NoSolutionEquation;
|
|
}
|
|
return I18n::Message::NoSolutionSystem;
|
|
}
|
|
|
|
Responder * SolutionsController::defaultController() {
|
|
return parentResponder()->parentResponder();
|
|
}
|
|
|
|
/* TableViewDataSource */
|
|
|
|
int SolutionsController::numberOfRows() const {
|
|
return m_equationStore->numberOfSolutions();
|
|
}
|
|
|
|
int SolutionsController::numberOfColumns() const {
|
|
return 2;
|
|
}
|
|
|
|
void SolutionsController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) {
|
|
if (i == 0) {
|
|
// Name of the variable or discriminant
|
|
if (m_equationStore->type() == EquationStore::Type::PolynomialMonovariable && j == m_equationStore->numberOfSolutions()-1) {
|
|
// Discriminant
|
|
EvenOddExpressionCell * deltaCell = static_cast<EvenOddExpressionCell *>(cell);
|
|
deltaCell->setLayout(m_delta2Layout);
|
|
} else {
|
|
EvenOddBufferTextCell * symbolCell = static_cast<EvenOddBufferTextCell *>(cell);
|
|
symbolCell->setFont(KDFont::LargeFont);
|
|
char bufferSymbol[Poincare::SymbolAbstract::k_maxNameSize+1]; // Hold at maximum Delta = b^2-4ac or the variable name + a digit
|
|
switch (m_equationStore->type()) {
|
|
case EquationStore::Type::LinearSystem:
|
|
/* The system has more than one variable: the cell text is the
|
|
* variable name */
|
|
strlcpy(bufferSymbol, m_equationStore->variableAtIndex(j), Poincare::SymbolAbstract::k_maxNameSize);
|
|
break;
|
|
default:
|
|
/* The system has one variable but might have many solutions: the cell
|
|
* text is variableX, with X the row index + 1 (e.g. x1, x2,...) */
|
|
int length = strlcpy(bufferSymbol, m_equationStore->variableAtIndex(0), Poincare::SymbolAbstract::k_maxNameSize);
|
|
bufferSymbol[length++] = j+'1';
|
|
bufferSymbol[length] = 0;
|
|
break;
|
|
}
|
|
symbolCell->setText(bufferSymbol);
|
|
}
|
|
} else {
|
|
// Value of the variable or discriminant
|
|
if (m_equationStore->type() == EquationStore::Type::Monovariable) {
|
|
EvenOddBufferTextCell * valueCell = static_cast<EvenOddBufferTextCell *>(cell);
|
|
constexpr int precision = Preferences::LargeNumberOfSignificantDigits;
|
|
constexpr int bufferSize = PrintFloat::charSizeForFloatsWithPrecision(precision);
|
|
char bufferValue[bufferSize];
|
|
PoincareHelpers::ConvertFloatToText<double>(m_equationStore->approximateSolutionAtIndex(j), bufferValue, bufferSize, precision);
|
|
valueCell->setText(bufferValue);
|
|
} else {
|
|
Shared::ScrollableExactApproximateExpressionsCell * valueCell = static_cast<ScrollableExactApproximateExpressionsCell *>(cell);
|
|
Poincare::Layout exactLayout = m_equationStore->exactSolutionLayoutsAtIndexAreIdentical(j) ? Poincare::Layout() : m_equationStore->exactSolutionLayoutAtIndex(j, true);
|
|
valueCell->setLayouts(exactLayout, m_equationStore->exactSolutionLayoutAtIndex(j, false));
|
|
if (!exactLayout.isUninitialized()) {
|
|
valueCell->setEqualMessage(m_equationStore->exactSolutionLayoutsAtIndexAreEqual(j) ? I18n::Message::Equal : I18n::Message::AlmostEqual);
|
|
}
|
|
}
|
|
}
|
|
EvenOddCell * evenOddCell = static_cast<EvenOddCell *>(cell);
|
|
evenOddCell->setEven(j%2 == 0);
|
|
}
|
|
|
|
KDCoordinate SolutionsController::columnWidth(int i) {
|
|
if (i == 0) {
|
|
return k_symbolCellWidth;
|
|
}
|
|
return k_valueCellWidth;
|
|
}
|
|
|
|
KDCoordinate SolutionsController::rowHeight(int j) {
|
|
if (m_equationStore->type() == EquationStore::Type::Monovariable) {
|
|
return k_defaultCellHeight;
|
|
}
|
|
Poincare::Layout exactLayout = m_equationStore->exactSolutionLayoutAtIndex(j, true);
|
|
Poincare::Layout approximateLayout = m_equationStore->exactSolutionLayoutAtIndex(j, false);
|
|
KDCoordinate exactLayoutHeight = exactLayout.layoutSize().height();
|
|
KDCoordinate approximateLayoutHeight = approximateLayout.layoutSize().height();
|
|
KDCoordinate layoutHeight = maxCoordinate(exactLayout.baseline(), approximateLayout.baseline()) + maxCoordinate(exactLayoutHeight-exactLayout.baseline(), approximateLayoutHeight-approximateLayout.baseline());
|
|
return layoutHeight + 2 * Metric::CommonSmallMargin;
|
|
}
|
|
|
|
KDCoordinate SolutionsController::cumulatedWidthFromIndex(int i) {
|
|
switch (i) {
|
|
case 0:
|
|
return 0;
|
|
case 1:
|
|
return k_symbolCellWidth;
|
|
case 2:
|
|
return k_symbolCellWidth+k_valueCellWidth;
|
|
default:
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int SolutionsController::indexFromCumulatedWidth(KDCoordinate offsetX) {
|
|
if (offsetX <= k_symbolCellWidth) {
|
|
return 0;
|
|
} else {
|
|
if (offsetX <= k_symbolCellWidth+k_valueCellWidth)
|
|
return 1;
|
|
else {
|
|
return 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
HighlightCell * SolutionsController::reusableCell(int index, int type) {
|
|
if (type == 0) {
|
|
return &m_symbolCells[index];
|
|
} else if (type == 1) {
|
|
return &m_deltaCell;
|
|
} else if (type == 2) {
|
|
return &m_exactValueCells[index];
|
|
}
|
|
return &m_approximateValueCells[index];
|
|
}
|
|
|
|
int SolutionsController::reusableCellCount(int type) {
|
|
switch (type) {
|
|
case 0:
|
|
return EquationStore::k_maxNumberOfSolutions;
|
|
case 1:
|
|
return 1;
|
|
case 2:
|
|
return EquationStore::k_maxNumberOfExactSolutions;
|
|
default:
|
|
return EquationStore::k_maxNumberOfApproximateSolutions;
|
|
}
|
|
}
|
|
|
|
int SolutionsController::typeAtLocation(int i, int j) {
|
|
if (i == 0) {
|
|
if (m_equationStore->type() == EquationStore::Type::PolynomialMonovariable && j == m_equationStore->numberOfSolutions()-1) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
return m_equationStore->type() == EquationStore::Type::Monovariable ? 3 : 2;
|
|
}
|
|
|
|
void SolutionsController::didBecomeFirstResponder() {
|
|
Container::activeApp()->setFirstResponder(m_contentView.selectableTableView());
|
|
}
|
|
|
|
}
|