Files
Upsilon/apps/solver/solutions_controller.cpp
Émilie Feral 87e4836196 [apps/calculation][apps/shared]
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.
2020-02-12 15:13:22 +01:00

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());
}
}