diff --git a/apps/calculation/Makefile b/apps/calculation/Makefile index b5362a517..6cb7d73a6 100644 --- a/apps/calculation/Makefile +++ b/apps/calculation/Makefile @@ -9,9 +9,7 @@ app_objs += $(addprefix apps/calculation/,\ expression_field.o\ history_view_cell.o\ history_controller.o\ - output_expressions_view.o\ scrollable_expression_view.o\ - scrollable_output_expressions_view.o\ selectable_table_view.o\ ) diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index 16ed91733..a38f4ff36 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -199,16 +199,7 @@ Calculation::EqualSign Calculation::exactAndApproximateDisplayedOutputsAreEqual( if (m_equalSign != EqualSign::Unknown) { return m_equalSign; } - char buffer[k_printedExpressionSize]; - approximateOutput(context)->writeTextInBuffer(buffer, k_printedExpressionSize, Preferences::sharedPreferences()->numberOfSignificantDigits()); - /* Warning: we cannot use directly the m_approximateOutputText but we have to - * re-serialize the approximateOutput because the number of stored - * significative numbers and the number of displayed significative numbers - * are not identical. (For example, 0.000025 might be displayed "0.00003" - * which requires in an approximative equal) */ - Expression * approximateOutput = Expression::ParseAndSimplify(buffer, *context); - m_equalSign = approximateOutput->isIdenticalTo(exactOutput(context)) ? EqualSign::Equal : EqualSign::Approximation; - delete approximateOutput; + m_equalSign = exactOutput(context)->isEqualToItsApproximationLayout(approximateOutput(context), k_printedExpressionSize, Preferences::sharedPreferences()->numberOfSignificantDigits(), *context) ? EqualSign::Equal : EqualSign::Approximation; return m_equalSign; } diff --git a/apps/calculation/calculation_store.cpp b/apps/calculation/calculation_store.cpp index 47d1cfe6b..0eb708bff 100644 --- a/apps/calculation/calculation_store.cpp +++ b/apps/calculation/calculation_store.cpp @@ -95,15 +95,15 @@ Expression * CalculationStore::ansExpression(Context * context) { return &defaultExpression; } Calculation * lastCalculation = calculationAtIndex(numberOfCalculations()-1); - /* Special case: the exact output is a Store expression. - * Remark: Store expressions are always reduced but if the simplification - * process was interrupted, the exact output is identical to the input. - * To avoid turning 'ans->A' in '2->A->A' (which cannot be parsed), ans - * is replaced by the approximation output in that special case.*/ - bool exactOuptutInvolvesStore = lastCalculation->exactOutput(context)->recursivelyMatches([](const Expression * e, Context & context) { - return e->type() == Expression::Type::Store; + /* Special case: the exact output is a Store/Equal expression. + * Store/Equal expression must be final root of an expression. + * To avoid turning 'ans->A' in '2->A->A' (or 2->A=A) which cannot be parsed), + * ans is replaced by the approximation output in when any Store or Equal + * expression appears.*/ + bool exactOuptutInvolvesStoreEqual = lastCalculation->exactOutput(context)->recursivelyMatches([](const Expression * e, Context & context) { + return e->type() == Expression::Type::Store || e->type() == Expression::Type::Equal; }, *context); - if (lastCalculation->input()->isApproximate(*context) || exactOuptutInvolvesStore) { + if (lastCalculation->input()->isApproximate(*context) || exactOuptutInvolvesStoreEqual) { return lastCalculation->approximateOutput(context); } return lastCalculation->exactOutput(context); diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index a30e82b8c..431c9635c 100644 --- a/apps/calculation/history_controller.cpp +++ b/apps/calculation/history_controller.cpp @@ -3,6 +3,8 @@ #include "../apps_container.h" #include +using namespace Shared; + namespace Calculation { HistoryController::HistoryController(Responder * parentResponder, CalculationStore * calculationStore) : @@ -47,8 +49,8 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { if (subviewType == HistoryViewCell::SubviewType::Input) { editController->insertTextBody(calculation->inputText()); } else { - OutputExpressionsView::SubviewType outputSubviewType = selectedCell->outputView()->selectedSubviewType(); - if (outputSubviewType == OutputExpressionsView::SubviewType::ExactOutput) { + ScrollableExactApproximateExpressionsView::SubviewType outputSubviewType = selectedCell->outputView()->selectedSubviewType(); + if (outputSubviewType == ScrollableExactApproximateExpressionsView::SubviewType::ExactOutput) { editController->insertTextBody(calculation->exactOutputText()); } else { editController->insertTextBody(calculation->approximateOutputText()); @@ -94,23 +96,6 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { app()->setFirstResponder(editController); return true; } - if (event == Ion::Events::Copy) { - HistoryViewCell * selectedCell = (HistoryViewCell *)selectableTableView()->selectedCell(); - HistoryViewCell::SubviewType subviewType = selectedCell->selectedSubviewType(); - int focusRow = selectedRow(); - Calculation * calculation = m_calculationStore->calculationAtIndex(focusRow); - if (subviewType == HistoryViewCell::SubviewType::Input) { - Clipboard::sharedClipboard()->store(calculation->inputText()); - } else { - OutputExpressionsView::SubviewType outputSubviewType = selectedCell->outputView()->selectedSubviewType(); - if (outputSubviewType == OutputExpressionsView::SubviewType::ExactOutput) { - Clipboard::sharedClipboard()->store(calculation->exactOutputText()); - } else { - Clipboard::sharedClipboard()->store(calculation->approximateOutputText()); - } - } - return true; - } return false; } diff --git a/apps/calculation/history_view_cell.cpp b/apps/calculation/history_view_cell.cpp index 7e38a02f8..eea40852e 100644 --- a/apps/calculation/history_view_cell.cpp +++ b/apps/calculation/history_view_cell.cpp @@ -33,32 +33,40 @@ HistoryViewCell::~HistoryViewCell() { } } -OutputExpressionsView * HistoryViewCell::outputView() { - return m_scrollableOutputView.outputView(); +Shared::ScrollableExactApproximateExpressionsView * HistoryViewCell::outputView() { + return &m_scrollableOutputView; } void HistoryViewCell::setEven(bool even) { EvenOddCell::setEven(even); m_inputView.setBackgroundColor(backgroundColor()); - m_scrollableOutputView.outputView()->setEven(even); + m_scrollableOutputView.evenOddCell()->setEven(even); } void HistoryViewCell::setHighlighted(bool highlight) { m_highlighted = highlight; m_inputView.setBackgroundColor(backgroundColor()); - m_scrollableOutputView.outputView()->setHighlighted(false); + m_scrollableOutputView.evenOddCell()->setHighlighted(false); if (isHighlighted()) { if (m_selectedSubviewType == SubviewType::Input) { m_inputView.setBackgroundColor(Palette::Select); } else { - m_scrollableOutputView.outputView()->setHighlighted(true); + m_scrollableOutputView.evenOddCell()->setHighlighted(true); } } reloadScroll(); } +Poincare::ExpressionLayout * HistoryViewCell::expressionLayout() const { + if (m_selectedSubviewType == SubviewType::Input) { + return m_inputLayout; + } else { + return m_scrollableOutputView.expressionLayout(); + } +} + void HistoryViewCell::reloadCell() { - m_scrollableOutputView.outputView()->reloadCell(); + m_scrollableOutputView.evenOddCell()->reloadCell(); layoutSubviews(); reloadScroll(); } @@ -69,7 +77,7 @@ void HistoryViewCell::reloadScroll() { } KDColor HistoryViewCell::backgroundColor() const { - KDColor background = m_even ? Palette::WallScreen : KDColorWhite; + KDColor background = m_even ? KDColorWhite : Palette::WallScreen; return background; } @@ -107,9 +115,9 @@ void HistoryViewCell::setCalculation(Calculation * calculation) { m_inputLayout = calculation->createInputLayout(); m_inputView.setExpressionLayout(m_inputLayout); App * calculationApp = (App *)app(); - /* Both output expressions have to be updated at the same time. The - * outputView points to deleted layouts and a call to - * outputView()->layoutSubviews() is going to fail. */ + /* Both output expressions have to be updated at the same time. Otherwise, + * when updating one layout, if the second one still points to a deleted + * layout, calling to layoutSubviews() would fail. */ if (m_exactOutputLayout) { delete m_exactOutputLayout; m_exactOutputLayout = nullptr; @@ -122,9 +130,9 @@ void HistoryViewCell::setCalculation(Calculation * calculation) { } m_approximateOutputLayout = calculation->createApproximateOutputLayout(calculationApp->localContext()); Poincare::ExpressionLayout * outputExpressions[2] = {m_approximateOutputLayout, m_exactOutputLayout}; - m_scrollableOutputView.outputView()->setExpressions(outputExpressions); + m_scrollableOutputView.setExpressions(outputExpressions); I18n::Message equalMessage = calculation->exactAndApproximateDisplayedOutputsAreEqual(calculationApp->localContext()) == Calculation::EqualSign::Equal ? I18n::Message::Equal : I18n::Message::AlmostEqual; - m_scrollableOutputView.outputView()->setEqualMessage(equalMessage); + m_scrollableOutputView.setEqualMessage(equalMessage); } void HistoryViewCell::didBecomeFirstResponder() { diff --git a/apps/calculation/history_view_cell.h b/apps/calculation/history_view_cell.h index d99063038..c581c3e21 100644 --- a/apps/calculation/history_view_cell.h +++ b/apps/calculation/history_view_cell.h @@ -4,7 +4,7 @@ #include #include "calculation.h" #include "scrollable_expression_view.h" -#include "scrollable_output_expressions_view.h" +#include "../shared/scrollable_exact_approximate_expressions_view.h" namespace Calculation { @@ -23,6 +23,7 @@ public: Responder * responder() override { return this; } + Poincare::ExpressionLayout * expressionLayout() const override; KDColor backgroundColor() const override; void setCalculation(Calculation * calculation); int numberOfSubviews() const override; @@ -33,14 +34,14 @@ public: constexpr static KDCoordinate k_digitVerticalMargin = 5; SubviewType selectedSubviewType(); void setSelectedSubviewType(HistoryViewCell::SubviewType subviewType); - OutputExpressionsView * outputView(); + Shared::ScrollableExactApproximateExpressionsView * outputView(); private: constexpr static KDCoordinate k_resultWidth = 80; Poincare::ExpressionLayout * m_inputLayout; Poincare::ExpressionLayout * m_exactOutputLayout; Poincare::ExpressionLayout * m_approximateOutputLayout; ScrollableExpressionView m_inputView; - ScrollableOutputExpressionsView m_scrollableOutputView; + Shared::ScrollableExactApproximateExpressionsView m_scrollableOutputView; SubviewType m_selectedSubviewType; }; diff --git a/apps/calculation/output_expressions_view.h b/apps/calculation/output_expressions_view.h deleted file mode 100644 index dcce33de8..000000000 --- a/apps/calculation/output_expressions_view.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef CALCULATION_OUTPUT_EXPRESSIONS_VIEW_H -#define CALCULATION_OUTPUT_EXPRESSIONS_VIEW_H - -#include - -namespace Calculation { - -class OutputExpressionsView : public EvenOddCell, public Responder { -public: - enum class SubviewType { - ExactOutput, - ApproximativeOutput - }; - OutputExpressionsView(Responder * parentResponder); - void setExpressions(Poincare::ExpressionLayout ** expressionsLayout); - void setEqualMessage(I18n::Message equalSignMessage); - KDColor backgroundColor() const override; - void setHighlighted(bool highlight) override; - Responder * responder() override { - return this; - } - void reloadCell() override; - KDSize minimalSizeForOptimalDisplay() const override; - void didBecomeFirstResponder() override; - bool handleEvent(Ion::Events::Event event) override; - SubviewType selectedSubviewType(); - void setSelectedSubviewType(SubviewType subviewType); -private: - int numberOfSubviews() const override; - View * subviewAtIndex(int index) override; - void layoutSubviews() override; - constexpr static KDCoordinate k_digitHorizontalMargin = 10; - ExpressionView m_approximateExpressionView; - MessageTextView m_approximateSign; - ExpressionView m_exactExpressionView; - SubviewType m_selectedSubviewType; -}; - -} - -#endif diff --git a/apps/calculation/scrollable_output_expressions_view.cpp b/apps/calculation/scrollable_output_expressions_view.cpp deleted file mode 100644 index e6b30dc04..000000000 --- a/apps/calculation/scrollable_output_expressions_view.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "scrollable_output_expressions_view.h" -#include -using namespace Poincare; - -namespace Calculation { - -ScrollableOutputExpressionsView::ScrollableOutputExpressionsView(Responder * parentResponder) : - ScrollableView(parentResponder, &m_outputView, this), - m_outputView(this) -{ -} - -OutputExpressionsView * ScrollableOutputExpressionsView::outputView() { - return &m_outputView; -} - -void ScrollableOutputExpressionsView::didBecomeFirstResponder() { - app()->setFirstResponder(&m_outputView); -} - -KDSize ScrollableOutputExpressionsView::minimalSizeForOptimalDisplay() const { - return m_outputView.minimalSizeForOptimalDisplay(); -} - -KDPoint ScrollableOutputExpressionsView::manualScrollingOffset() const { - return m_manualScrollingOffset; -} - -} diff --git a/apps/calculation/scrollable_output_expressions_view.h b/apps/calculation/scrollable_output_expressions_view.h deleted file mode 100644 index dc01cad51..000000000 --- a/apps/calculation/scrollable_output_expressions_view.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef CALCULATION_SCROLLABLE_OUTPUT_EXPRESSIONS_VIEW_H -#define CALCULATION_SCROLLABLE_OUTPUT_EXPRESSIONS_VIEW_H - -#include -#include "output_expressions_view.h" - -namespace Calculation { - -class ScrollableOutputExpressionsView : public ScrollableView, public ScrollViewDataSource { -public: - ScrollableOutputExpressionsView(Responder * parentResponder); - OutputExpressionsView * outputView(); - void didBecomeFirstResponder() override; - KDSize minimalSizeForOptimalDisplay() const override; - KDPoint manualScrollingOffset() const; -private: - OutputExpressionsView m_outputView; -}; - -} - -#endif diff --git a/apps/code/console_controller.cpp b/apps/code/console_controller.cpp index 0bc922854..3505b69cb 100644 --- a/apps/code/console_controller.cpp +++ b/apps/code/console_controller.cpp @@ -153,8 +153,6 @@ bool ConsoleController::handleEvent(Ion::Events::Event event) { app()->setFirstResponder(&m_editCell); return m_editCell.insertText(text); } - } else if (event == Ion::Events::Copy) { - return copyCurrentLineToClipboard(); } else if (event == Ion::Events::Clear) { m_selectableTableView.deselectTable(); m_consoleStore.clear(); @@ -392,13 +390,4 @@ StackViewController * ConsoleController::stackViewController() { return static_cast(parentResponder()); } -bool ConsoleController::copyCurrentLineToClipboard() { - int row = m_selectableTableView.selectedRow(); - if (row < m_consoleStore.numberOfLines()) { - Clipboard::sharedClipboard()->store(m_consoleStore.lineAtIndex(row).text()); - return true; - } - return false; -} - } diff --git a/apps/code/console_controller.h b/apps/code/console_controller.h index 0ec6bd690..525ffc038 100644 --- a/apps/code/console_controller.h +++ b/apps/code/console_controller.h @@ -88,7 +88,6 @@ private: void emptyOutputAccumulationBuffer(); size_t firstNewLineCharIndex(const char * text, size_t length); StackViewController * stackViewController(); - bool copyCurrentLineToClipboard(); int m_rowHeight; bool m_importScriptsWhenViewAppears; ConsoleStore m_consoleStore; diff --git a/apps/code/console_edit_cell.h b/apps/code/console_edit_cell.h index 921e1d7c3..536be0b43 100644 --- a/apps/code/console_edit_cell.h +++ b/apps/code/console_edit_cell.h @@ -28,7 +28,7 @@ public: // Edit cell void setEditing(bool isEditing, bool reinitDraftBuffer = false); - const char * text() const { return m_textField.text(); } + const char * text() const override { return m_textField.text(); } void setText(const char * text); bool insertText(const char * text); void setPrompt(const char * prompt); diff --git a/apps/code/console_line_cell.h b/apps/code/console_line_cell.h index 80efdacbe..e192e82cc 100644 --- a/apps/code/console_line_cell.h +++ b/apps/code/console_line_cell.h @@ -24,7 +24,9 @@ public: Responder * responder() override { return this; } - + const char * text() const override { + return m_line.text(); + } /* View */ int numberOfSubviews() const override; View * subviewAtIndex(int index) override; diff --git a/apps/code/menu_controller.cpp b/apps/code/menu_controller.cpp index 46c4488e1..fe2ff2793 100644 --- a/apps/code/menu_controller.cpp +++ b/apps/code/menu_controller.cpp @@ -12,7 +12,7 @@ MenuController::MenuController(Responder * parentResponder, ScriptStore * script ViewController(parentResponder), ButtonRowDelegate(nullptr, footer), m_scriptStore(scriptStore), - m_addNewScriptCell(I18n::Message::AddScript), + m_addNewScriptCell(), m_consoleButton(this, I18n::Message::Console, Invocation([](void * context, void * sender) { MenuController * menu = (MenuController *)context; if (menu->consoleController()->loadPythonEnvironment()) { @@ -29,6 +29,7 @@ MenuController::MenuController(Responder * parentResponder, ScriptStore * script { m_selectableTableView.setMargins(0); m_selectableTableView.setShowsIndicators(false); + m_addNewScriptCell.setMessage(I18n::Message::AddScript); for (int i = 0; i < k_maxNumberOfDisplayableScriptCells; i++) { m_scriptCells[i].setParentResponder(&m_selectableTableView); m_scriptCells[i].editableTextCell()->textField()->setDelegate(this); diff --git a/apps/code/menu_controller.h b/apps/code/menu_controller.h index a1665a3f0..61b5f565a 100644 --- a/apps/code/menu_controller.h +++ b/apps/code/menu_controller.h @@ -2,7 +2,6 @@ #define CODE_MENU_CONTROLLER_H #include -#include #include "console_controller.h" #include "editor_controller.h" #include "script_parameter_controller.h" @@ -99,7 +98,7 @@ private: * constructor of an EvenOddEditableTextCell. */ char m_draftTextBuffer[TextField::maxBufferSize()]; EvenOddCellWithEllipsis m_scriptParameterCells[k_maxNumberOfDisplayableScriptCells]; - Shared::NewFunctionCell m_addNewScriptCell; + EvenOddMessageTextCell m_addNewScriptCell; EvenOddCell m_emptyCell; Button m_consoleButton; SelectableTableView m_selectableTableView; diff --git a/apps/code/script_node_cell.h b/apps/code/script_node_cell.h index 4cc32f9ea..b885b92ac 100644 --- a/apps/code/script_node_cell.h +++ b/apps/code/script_node_cell.h @@ -21,6 +21,7 @@ public: /* HighlightCell */ void setHighlighted(bool highlight) override; void reloadCell() override; + const char * text() const override { return m_scriptNodeView.text(); } constexpr static char k_parentheses[] = "()"; constexpr static char k_parenthesesWithEmpty[] = {'(', Ion::Charset::Empty, ')', 0}; @@ -32,6 +33,9 @@ protected: void setScriptStore(ScriptStore * scriptStore); void drawRect(KDContext * ctx, KDRect rect) const override; virtual KDSize minimalSizeForOptimalDisplay() const override; + const char * text() const override { + return m_scriptStore->scriptAtIndex(m_scriptNode->scriptIndex()).name(); + } private: constexpr static KDText::FontSize k_fontSize = KDText::FontSize::Small; constexpr static KDCoordinate k_verticalMargin = 7; diff --git a/apps/graph/cartesian_function.cpp b/apps/graph/cartesian_function.cpp index 1b86d64be..0112e2649 100644 --- a/apps/graph/cartesian_function.cpp +++ b/apps/graph/cartesian_function.cpp @@ -41,315 +41,24 @@ double CartesianFunction::sumBetweenBounds(double start, double end, Poincare::C return integral.approximateToScalar(*context); } -CartesianFunction::Point CartesianFunction::nextMinimumFrom(double start, double step, double max, Context * context) const { - return nextMinimumOfFunction(start, step, max, [](double x, Context * context, const Shared::Function * function0, const Shared::Function * function1 = nullptr) { - return function0->evaluateAtAbscissa(x, context); - }, context); +Expression::Coordinate2D CartesianFunction::nextMinimumFrom(double start, double step, double max, Context * context) const { + return expression(context)->nextMinimum(symbol(), start, step, max, *context); } -CartesianFunction::Point CartesianFunction::nextMaximumFrom(double start, double step, double max, Context * context) const { - Point minimumOfOpposite = nextMinimumOfFunction(start, step, max, [](double x, Context * context, const Shared::Function * function0, const Shared::Function * function1 = nullptr) { - return -function0->evaluateAtAbscissa(x, context); - }, context); - return {.abscissa = minimumOfOpposite.abscissa, .value = -minimumOfOpposite.value}; +Expression::Coordinate2D CartesianFunction::nextMaximumFrom(double start, double step, double max, Context * context) const { + return expression(context)->nextMaximum(symbol(), start, step, max, *context); } double CartesianFunction::nextRootFrom(double start, double step, double max, Context * context) const { - return nextIntersectionWithFunction(start, step, max, [](double x, Context * context, const Shared::Function * function0, const Shared::Function * function1 = nullptr) { - return function0->evaluateAtAbscissa(x, context); - }, context, nullptr); + return expression(context)->nextRoot(symbol(), start, step, max, *context); } -CartesianFunction::Point CartesianFunction::nextIntersectionFrom(double start, double step, double max, Poincare::Context * context, const Shared::Function * function) const { - double resultAbscissa = nextIntersectionWithFunction(start, step, max, [](double x, Context * context, const Shared::Function * function0, const Shared::Function * function1) { - return function0->evaluateAtAbscissa(x, context)-function1->evaluateAtAbscissa(x, context); - }, context, function); - CartesianFunction::Point result = {.abscissa = resultAbscissa, .value = evaluateAtAbscissa(resultAbscissa, context)}; - if (std::fabs(result.value) < step*k_precision) { - result.value = 0.0; - } - return result; -} - -CartesianFunction::Point CartesianFunction::nextMinimumOfFunction(double start, double step, double max, Evaluation evaluate, Context * context, const Shared::Function * function, bool lookForRootMinimum) const { - double bracket[3]; - Point result = {.abscissa = NAN, .value = NAN}; - double x = start; - bool endCondition = false; - do { - bracketMinimum(x, step, max, bracket, evaluate, context, function); - result = brentMinimum(bracket[0], bracket[2], evaluate, context, function); - x = bracket[1]; - endCondition = std::isnan(result.abscissa) && (step > 0.0 ? x <= max : x >= max); - if (lookForRootMinimum) { - endCondition |= std::fabs(result.value) >= k_sqrtEps && (step > 0.0 ? x <= max : x >= max); - } - } while (endCondition); - - if (std::fabs(result.abscissa) < step*k_precision) { - result.abscissa = 0; - result.value = evaluate(0, context, this, function); - } - if (std::fabs(result.value) < step*k_precision) { - result.value = 0; - } - if (lookForRootMinimum) { - result.abscissa = std::fabs(result.value) >= k_sqrtEps ? NAN : result.abscissa; - } - return result; -} - -void CartesianFunction::bracketMinimum(double start, double step, double max, double result[3], Evaluation evaluate, Context * context, const Shared::Function * function) const { - Point p[3]; - p[0] = {.abscissa = start, .value = evaluate(start, context, this, function)}; - p[1] = {.abscissa = start+step, .value = evaluate(start+step, context, this, function)}; - double x = start+2.0*step; - while (step > 0.0 ? x <= max : x >= max) { - p[2] = {.abscissa = x, .value = evaluate(x, context, this, function)}; - if (p[0].value > p[1].value && p[2].value > p[1].value) { - result[0] = p[0].abscissa; - result[1] = p[1].abscissa; - result[2] = p[2].abscissa; - return; - } - if (p[0].value > p[1].value && p[1].value == p[2].value) { - } else { - p[0] = p[1]; - p[1] = p[2]; - } - x += step; - } - result[0] = NAN; - result[1] = NAN; - result[2] = NAN; +Expression::Coordinate2D CartesianFunction::nextIntersectionFrom(double start, double step, double max, Poincare::Context * context, const Shared::Function * function) const { + return expression(context)->nextIntersection(symbol(), start, step, max, *context, function->expression(context)); } char CartesianFunction::symbol() const { return 'x'; } -CartesianFunction::Point CartesianFunction::brentMinimum(double ax, double bx, Evaluation evaluate, Context * context, const Shared::Function * function) const { - /* Bibliography: R. P. Brent, Algorithms for finding zeros and extrema of - * functions without calculating derivatives */ - if (ax > bx) { - return brentMinimum(bx, ax, evaluate, context, function); - } - double e = 0.0; - double a = ax; - double b = bx; - double x = a+k_goldenRatio*(b-a); - double v = x; - double w = x; - double fx = evaluate(x, context, this, function); - double fw = fx; - double fv = fw; - - double d = NAN; - double u, fu; - - for (int i = 0; i < 100; i++) { - double m = 0.5*(a+b); - double tol1 = k_sqrtEps*std::fabs(x)+1E-10; - double tol2 = 2.0*tol1; - if (std::fabs(x-m) <= tol2-0.5*(b-a)) { - double middleFax = evaluate((x+a)/2.0, context, this, function); - double middleFbx = evaluate((x+b)/2.0, context, this, function); - double fa = evaluate(a, context, this, function); - double fb = evaluate(b, context, this, function); - if (middleFax-fa <= k_sqrtEps && fx-middleFax <= k_sqrtEps && fx-middleFbx <= k_sqrtEps && middleFbx-fb <= k_sqrtEps) { - Point result = {.abscissa = x, .value = fx}; - return result; - } - } - double p = 0; - double q = 0; - double r = 0; - if (std::fabs(e) > tol1) { - r = (x-w)*(fx-fv); - q = (x-v)*(fx-fw); - p = (x-v)*q -(x-w)*r; - q = 2.0*(q-r); - if (q>0.0) { - p = -p; - } else { - q = -q; - } - r = e; - e = d; - } - if (std::fabs(p) < std::fabs(0.5*q*r) && p= tol1 ? d : (d>0 ? tol1 : -tol1)); - fu = evaluate(u, context, this, function); - if (fu <= fx) { - if (u 0.0 ? x <= max : x >= max)); - - double extremumMax = std::isnan(result) ? max : result; - Point resultExtremum[2] = { - nextMinimumOfFunction(start, step, extremumMax, [](double x, Context * context, const Shared::Function * function0, const Shared::Function * function1) { - if (function1) { - return function0->evaluateAtAbscissa(x, context)-function1->evaluateAtAbscissa(x, context); - } else { - return function0->evaluateAtAbscissa(x, context); - } - }, context, function, true), - nextMinimumOfFunction(start, step, extremumMax, [](double x, Context * context, const Shared::Function * function0, const Shared::Function * function1) { - if (function1) { - return function1->evaluateAtAbscissa(x, context)-function0->evaluateAtAbscissa(x, context); - } else { - return -function0->evaluateAtAbscissa(x, context); - } - }, context, function, true)}; - for (int i = 0; i < 2; i++) { - if (!std::isnan(resultExtremum[i].abscissa) && (std::isnan(result) || std::fabs(result - start) > std::fabs(resultExtremum[i].abscissa - start))) { - result = resultExtremum[i].abscissa; - } - } - if (std::fabs(result) < step*k_precision) { - result = 0; - } - return result; -} - -void CartesianFunction::bracketRoot(double start, double step, double max, double result[2], Evaluation evaluation, Context * context, const Shared::Function * function) const { - double a = start; - double b = start+step; - while (step > 0.0 ? b <= max : b >= max) { - double fa = evaluation(a, context, this, function); - double fb = evaluation(b, context, this, function); - if (fa*fb <= 0) { - result[0] = a; - result[1] = b; - return; - } - a = b; - b = b+step; - } - result[0] = NAN; - result[1] = NAN; -} - - -double CartesianFunction::brentRoot(double ax, double bx, double precision, Evaluation evaluation, Poincare::Context * context, const Shared::Function * function) const { - if (ax > bx) { - return brentRoot(bx, ax, precision, evaluation, context, function); - } - double a = ax; - double b = bx; - double c = bx; - double d = b-a; - double e = b-a; - double fa = evaluation(a, context, this, function); - double fb = evaluation(b, context, this, function); - double fc = fb; - for (int i = 0; i < 100; i++) { - if ((fb > 0.0 && fc > 0.0) || (fb < 0.0 && fc < 0.0)) { - c = a; - fc = fa; - e = b-a; - d = b-a; - } - if (std::fabs(fc) < std::fabs(fb)) { - a = b; - b = c; - c = a; - fa = fb; - fb = fc; - fc = fa; - } - double tol1 = 2.0*DBL_EPSILON*std::fabs(b)+0.5*precision; - double xm = 0.5*(c-b); - if (std::fabs(xm) <= tol1 || fb == 0.0) { - double fbcMiddle = evaluation(0.5*(b+c), context, this, function); - double isContinuous = (fb <= fbcMiddle && fbcMiddle <= fc) || (fc <= fbcMiddle && fbcMiddle <= fb); - if (isContinuous) { - return b; - } - } - if (std::fabs(e) >= tol1 && std::fabs(fa) > std::fabs(b)) { - double s = fb/fa; - double p = 2.0*xm*s; - double q = 1.0-s; - if (a != c) { - q = fa/fc; - double r = fb/fc; - p = s*(2.0*xm*q*(q-r)-(b-a)*(r-1.0)); - q = (q-1.0)*(r-1.0)*(s-1.0); - } - q = p > 0.0 ? -q : q; - p = std::fabs(p); - double min1 = 3.0*xm*q-std::fabs(tol1*q); - double min2 = std::fabs(e*q); - if (2.0*p < (min1 < min2 ? min1 : min2)) { - e = d; - d = p/q; - } else { - d = xm; - e =d; - } - } else { - d = xm; - e = d; - } - a = b; - fa = fb; - if (std::fabs(d) > tol1) { - b += d; - } else { - b += xm > 0.0 ? tol1 : tol1; - } - fb = evaluation(b, context, this, function); - } - return NAN; -} - } diff --git a/apps/graph/cartesian_function.h b/apps/graph/cartesian_function.h index 0b4bb4393..9015ad7dd 100644 --- a/apps/graph/cartesian_function.h +++ b/apps/graph/cartesian_function.h @@ -13,26 +13,12 @@ public: void setDisplayDerivative(bool display); double approximateDerivative(double x, Poincare::Context * context) const; double sumBetweenBounds(double start, double end, Poincare::Context * context) const override; - struct Point { - double abscissa; - double value; - }; - Point nextMinimumFrom(double start, double step, double max, Poincare::Context * context) const; - Point nextMaximumFrom(double start, double step, double max, Poincare::Context * context) const; + Poincare::Expression::Coordinate2D nextMinimumFrom(double start, double step, double max, Poincare::Context * context) const; + Poincare::Expression::Coordinate2D nextMaximumFrom(double start, double step, double max, Poincare::Context * context) const; double nextRootFrom(double start, double step, double max, Poincare::Context * context) const; - Point nextIntersectionFrom(double start, double step, double max, Poincare::Context * context, const Shared::Function * function) const; + Poincare::Expression::Coordinate2D nextIntersectionFrom(double start, double step, double max, Poincare::Context * context, const Shared::Function * function) const; char symbol() const override; private: - constexpr static double k_precision = 1.0E-5; - constexpr static double k_sqrtEps = 1.4901161193847656E-8; // sqrt(DBL_EPSILON) - constexpr static double k_goldenRatio = 0.381966011250105151795413165634361882279690820194237137864; // (3-sqrt(5))/2 - typedef double (*Evaluation)(double abscissa, Poincare::Context * context, const Shared::Function * function0, const Shared::Function * function1); - Point nextMinimumOfFunction(double start, double step, double max, Evaluation evaluation, Poincare::Context * context, const Shared::Function * function = nullptr, bool lookForRootMinimum = false) const; - void bracketMinimum(double start, double step, double max, double result[3], Evaluation evaluation, Poincare::Context * context, const Shared::Function * function= nullptr) const; - Point brentMinimum(double ax, double bx, Evaluation evaluation, Poincare::Context * context, const Shared::Function * function = nullptr) const; - double nextIntersectionWithFunction(double start, double step, double max, Evaluation evaluation, Poincare::Context * context, const Shared::Function * function) const; - void bracketRoot(double start, double step, double max, double result[2], Evaluation evaluation, Poincare::Context * context, const Shared::Function * function) const; - double brentRoot(double ax, double bx, double precision, Evaluation evaluation, Poincare::Context * context, const Shared::Function * function) const; bool m_displayDerivative; }; diff --git a/apps/graph/cartesian_function_store.cpp b/apps/graph/cartesian_function_store.cpp index d7acf19c9..70b84f62b 100644 --- a/apps/graph/cartesian_function_store.cpp +++ b/apps/graph/cartesian_function_store.cpp @@ -14,7 +14,7 @@ constexpr const char * CartesianFunctionStore::k_functionNames[k_maxNumberOfFunc CartesianFunctionStore::CartesianFunctionStore() : Shared::FunctionStore() { - addEmptyFunction(); + addEmptyModel(); } uint32_t CartesianFunctionStore::storeChecksum() { @@ -27,91 +27,30 @@ uint32_t CartesianFunctionStore::storeChecksum() { return Ion::crc32((uint32_t *)checksums, dataLengthInBytes/sizeof(uint32_t)); } -CartesianFunction * CartesianFunctionStore::functionAtIndex(int i) { - assert(i>=0 && i=0 && i=0 && i(e)); } } diff --git a/apps/graph/cartesian_function_store.h b/apps/graph/cartesian_function_store.h index ad2e0a497..3e60c41b3 100644 --- a/apps/graph/cartesian_function_store.h +++ b/apps/graph/cartesian_function_store.h @@ -12,24 +12,31 @@ class CartesianFunctionStore : public Shared::FunctionStore { public: CartesianFunctionStore(); uint32_t storeChecksum() override; - CartesianFunction * functionAtIndex(int i) override; - CartesianFunction * activeFunctionAtIndex(int i) override; - CartesianFunction * definedFunctionAtIndex(int i) override; - CartesianFunction * addEmptyFunction() override; - void removeFunction(Shared::Function * f) override; - int maxNumberOfFunctions() override; + CartesianFunction * modelAtIndex(int i) override { return &m_functions[i]; } + CartesianFunction * activeFunctionAtIndex(int i) override { return (CartesianFunction *)Shared::FunctionStore::activeFunctionAtIndex(i); } + CartesianFunction * definedFunctionAtIndex(int i) override { return (CartesianFunction *)Shared::FunctionStore::definedFunctionAtIndex(i); } + int maxNumberOfModels() const override { + return k_maxNumberOfFunctions; + } char symbol() const override; void removeAll() override; static constexpr int k_maxNumberOfFunctions = 4; private: - const char * firstAvailableName() override; - const KDColor firstAvailableColor() override; static constexpr KDColor k_defaultColors[k_maxNumberOfFunctions] = { Palette::Red, Palette::Blue, Palette::Green, Palette::YellowDark, }; static constexpr const char * k_functionNames[k_maxNumberOfFunctions] = { "f", "g", "h", "p", }; + CartesianFunction * emptyModel() override; + CartesianFunction * nullModel() override; + void setModelAtIndex(Shared::ExpressionModel * f, int i) override; + const char * firstAvailableName() override { + return firstAvailableAttribute(k_functionNames, FunctionStore::name); + } + const KDColor firstAvailableColor() override { + return firstAvailableAttribute(k_defaultColors, FunctionStore::color); + } CartesianFunction m_functions[k_maxNumberOfFunctions]; }; diff --git a/apps/graph/function_title_cell.cpp b/apps/graph/function_title_cell.cpp index e4036acb5..ed336ee18 100644 --- a/apps/graph/function_title_cell.cpp +++ b/apps/graph/function_title_cell.cpp @@ -42,7 +42,7 @@ View * FunctionTitleCell::subviewAtIndex(int index) { void FunctionTitleCell::layoutSubviews() { KDRect textFrame(0, k_colorIndicatorThickness, bounds().width(), bounds().height() - k_colorIndicatorThickness); if (m_orientation == Orientation::VerticalIndicator){ - textFrame = KDRect(k_colorIndicatorThickness, 0, bounds().width() - k_colorIndicatorThickness, bounds().height()-k_separatorThickness); + textFrame = KDRect(k_colorIndicatorThickness, 0, bounds().width() - k_colorIndicatorThickness-k_separatorThickness, bounds().height()-k_separatorThickness); } m_bufferTextView.setFrame(textFrame); } diff --git a/apps/graph/function_title_cell.h b/apps/graph/function_title_cell.h index 6d10b179a..bbc9142a9 100644 --- a/apps/graph/function_title_cell.h +++ b/apps/graph/function_title_cell.h @@ -12,10 +12,13 @@ public: void setHighlighted(bool highlight) override; void setColor(KDColor color) override; void setText(const char * textContent); + const char * text() const override { + return m_bufferTextView.text(); + } +private: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; void layoutSubviews() override; -private: EvenOddBufferTextCell m_bufferTextView; }; diff --git a/apps/graph/graph/calculation_graph_controller.cpp b/apps/graph/graph/calculation_graph_controller.cpp index 7c98b284d..de9ef00f1 100644 --- a/apps/graph/graph/calculation_graph_controller.cpp +++ b/apps/graph/graph/calculation_graph_controller.cpp @@ -24,7 +24,7 @@ View * CalculationGraphController::view() { void CalculationGraphController::viewWillAppear() { assert(m_function != nullptr); - CartesianFunction::Point pointOfInterest = computeNewPointOfInteresetFromAbscissa(m_graphRange->xMin(), 1); + Expression::Coordinate2D pointOfInterest = computeNewPointOfInteresetFromAbscissa(m_graphRange->xMin(), 1); if (std::isnan(pointOfInterest.abscissa)) { m_isActive = false; m_graphView->setCursorView(nullptr); @@ -67,7 +67,7 @@ void CalculationGraphController::reloadBannerView() { } bool CalculationGraphController::moveCursor(int direction) { - CartesianFunction::Point newPointOfInterest = computeNewPointOfInteresetFromAbscissa(m_cursor->x(), direction); + Expression::Coordinate2D newPointOfInterest = computeNewPointOfInteresetFromAbscissa(m_cursor->x(), direction); if (std::isnan(newPointOfInterest.abscissa)) { return false; } @@ -76,7 +76,7 @@ bool CalculationGraphController::moveCursor(int direction) { return true; } -CartesianFunction::Point CalculationGraphController::computeNewPointOfInteresetFromAbscissa(double start, int direction) { +Expression::Coordinate2D CalculationGraphController::computeNewPointOfInteresetFromAbscissa(double start, int direction) { TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app(); double step = m_graphRange->xGridUnit()/10.0; step = direction < 0 ? -step : step; diff --git a/apps/graph/graph/calculation_graph_controller.h b/apps/graph/graph/calculation_graph_controller.h index b277faffe..ffc26e45c 100644 --- a/apps/graph/graph/calculation_graph_controller.h +++ b/apps/graph/graph/calculation_graph_controller.h @@ -23,8 +23,8 @@ protected: BannerView * bannerView() override { return m_bannerView; } virtual void reloadBannerView(); bool moveCursor(int direction); - CartesianFunction::Point computeNewPointOfInteresetFromAbscissa(double start, int direction); - virtual CartesianFunction::Point computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) = 0; + Poincare::Expression::Coordinate2D computeNewPointOfInteresetFromAbscissa(double start, int direction); + virtual Poincare::Expression::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) = 0; GraphView * m_graphView; BannerView * m_bannerView; Shared::InteractiveCurveViewRange * m_graphRange; diff --git a/apps/graph/graph/extremum_graph_controller.cpp b/apps/graph/graph/extremum_graph_controller.cpp index 1273fa756..b2c855e95 100644 --- a/apps/graph/graph/extremum_graph_controller.cpp +++ b/apps/graph/graph/extremum_graph_controller.cpp @@ -15,7 +15,7 @@ const char * MinimumGraphController::title() { return I18n::translate(I18n::Message::Minimum); } -CartesianFunction::Point MinimumGraphController::computeNewPointOfInterest(double start, double step, double max, Context * context) { +Expression::Coordinate2D MinimumGraphController::computeNewPointOfInterest(double start, double step, double max, Context * context) { return m_function->nextMinimumFrom(start, step, max, context); } @@ -28,7 +28,7 @@ const char * MaximumGraphController::title() { return I18n::translate(I18n::Message::Maximum); } -CartesianFunction::Point MaximumGraphController::computeNewPointOfInterest(double start, double step, double max, Context * context) { +Expression::Coordinate2D MaximumGraphController::computeNewPointOfInterest(double start, double step, double max, Context * context) { return m_function->nextMaximumFrom(start, step, max, context); } diff --git a/apps/graph/graph/extremum_graph_controller.h b/apps/graph/graph/extremum_graph_controller.h index dc1f5bc30..3fd55107c 100644 --- a/apps/graph/graph/extremum_graph_controller.h +++ b/apps/graph/graph/extremum_graph_controller.h @@ -10,7 +10,7 @@ public: MinimumGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor); const char * title() override; private: - CartesianFunction::Point computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; + Poincare::Expression::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; }; class MaximumGraphController : public CalculationGraphController { @@ -18,7 +18,7 @@ public: MaximumGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor); const char * title() override; private: - CartesianFunction::Point computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; + Poincare::Expression::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; }; } diff --git a/apps/graph/graph/graph_controller.cpp b/apps/graph/graph/graph_controller.cpp index 02f5279db..8bf5c0b7e 100644 --- a/apps/graph/graph/graph_controller.cpp +++ b/apps/graph/graph/graph_controller.cpp @@ -19,7 +19,7 @@ GraphController::GraphController(Responder * parentResponder, CartesianFunctionS } I18n::Message GraphController::emptyMessage() { - if (m_functionStore->numberOfDefinedFunctions() == 0) { + if (m_functionStore->numberOfDefinedModels() == 0) { return I18n::Message::NoFunction; } return I18n::Message::NoActivatedFunction; diff --git a/apps/graph/graph/intersection_graph_controller.cpp b/apps/graph/graph/intersection_graph_controller.cpp index 94d5f6335..86168890d 100644 --- a/apps/graph/graph/intersection_graph_controller.cpp +++ b/apps/graph/graph/intersection_graph_controller.cpp @@ -36,12 +36,12 @@ void IntersectionGraphController::reloadBannerView() { bannerView()->setLegendAtIndex(buffer, 1); } -CartesianFunction::Point IntersectionGraphController::computeNewPointOfInterest(double start, double step, double max, Context * context) { - CartesianFunction::Point result = {.abscissa = NAN, .value = NAN}; +Expression::Coordinate2D IntersectionGraphController::computeNewPointOfInterest(double start, double step, double max, Context * context) { + Expression::Coordinate2D result = {.abscissa = NAN, .value = NAN}; for (int i = 0; i < m_functionStore->numberOfActiveFunctions(); i++) { Function * f = m_functionStore->activeFunctionAtIndex(i); if (f != m_function) { - CartesianFunction::Point intersection = m_function->nextIntersectionFrom(start, step, max, context, f); + Expression::Coordinate2D intersection = m_function->nextIntersectionFrom(start, step, max, context, f); if ((std::isnan(result.abscissa) || std::fabs(intersection.abscissa-start) < std::fabs(result.abscissa-start)) && !std::isnan(intersection.abscissa)) { m_intersectedFunction = f; result = (std::isnan(result.abscissa) || std::fabs(intersection.abscissa-start) < std::fabs(result.abscissa-start)) ? intersection : result; diff --git a/apps/graph/graph/intersection_graph_controller.h b/apps/graph/graph/intersection_graph_controller.h index 097de1377..8e7042081 100644 --- a/apps/graph/graph/intersection_graph_controller.h +++ b/apps/graph/graph/intersection_graph_controller.h @@ -11,7 +11,7 @@ public: const char * title() override; private: void reloadBannerView() override; - CartesianFunction::Point computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; + Poincare::Expression::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; Shared::Function * m_intersectedFunction; CartesianFunctionStore * m_functionStore; }; diff --git a/apps/graph/graph/root_graph_controller.cpp b/apps/graph/graph/root_graph_controller.cpp index ec1d379ff..07bb99147 100644 --- a/apps/graph/graph/root_graph_controller.cpp +++ b/apps/graph/graph/root_graph_controller.cpp @@ -15,7 +15,7 @@ const char * RootGraphController::title() { return I18n::translate(I18n::Message::Zeros); } -CartesianFunction::Point RootGraphController::computeNewPointOfInterest(double start, double step, double max, Context * context) { +Expression::Coordinate2D RootGraphController::computeNewPointOfInterest(double start, double step, double max, Context * context) { return {.abscissa = m_function->nextRootFrom(start, step, max, context), .value = 0.0}; } diff --git a/apps/graph/graph/root_graph_controller.h b/apps/graph/graph/root_graph_controller.h index 2d53898e7..4db493145 100644 --- a/apps/graph/graph/root_graph_controller.h +++ b/apps/graph/graph/root_graph_controller.h @@ -10,7 +10,7 @@ public: RootGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor); const char * title() override; private: - CartesianFunction::Point computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; + Poincare::Expression::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override; }; } diff --git a/apps/graph/list/list_controller.cpp b/apps/graph/list/list_controller.cpp index 9c84175d6..97f0df353 100644 --- a/apps/graph/list/list_controller.cpp +++ b/apps/graph/list/list_controller.cpp @@ -9,7 +9,7 @@ using namespace Shared; namespace Graph { ListController::ListController(Responder * parentResponder, CartesianFunctionStore * functionStore, ButtonRowController * header, ButtonRowController * footer) : - Shared::ListController(parentResponder, functionStore, header, footer, I18n::Message::AddFunction), + Shared::FunctionListController(parentResponder, functionStore, header, footer, I18n::Message::AddFunction), m_functionTitleCells{}, m_expressionCells{}, m_parameterController(this, functionStore, I18n::Message::FunctionColor, I18n::Message::DeleteFunction) @@ -20,45 +20,6 @@ const char * ListController::title() { return I18n::translate(I18n::Message::FunctionTab); } -int ListController::numberOfRows() { - if (m_functionStore->numberOfFunctions() == m_functionStore->maxNumberOfFunctions()) { - return m_functionStore->numberOfFunctions(); - } - return 1 + m_functionStore->numberOfFunctions(); -}; - -KDCoordinate ListController::rowHeight(int j) { - if (m_functionStore->numberOfFunctions() < m_functionStore->maxNumberOfFunctions() && j == numberOfRows() - 1) { - return Metric::StoreRowHeight; - } - Function * function = m_functionStore->functionAtIndex(j); - if (function->layout() == nullptr) { - return Metric::StoreRowHeight; - } - KDCoordinate functionSize = function->layout()->size().height(); - return functionSize + Metric::StoreRowHeight - KDText::charSize().height(); -} - -void ListController::editExpression(Function * function, Ion::Events::Event event) { - char * initialText = nullptr; - char initialTextContent[TextField::maxBufferSize()]; - if (event == Ion::Events::OK || event == Ion::Events::EXE) { - strlcpy(initialTextContent, function->text(), sizeof(initialTextContent)); - initialText = initialTextContent; - } - App * myApp = (App *)app(); - InputViewController * inputController = myApp->inputViewController(); - inputController->edit(this, event, function, initialText, - [](void * context, void * sender){ - Shared::Function * myFunction = (Shared::Function *)context; - InputViewController * myInputViewController = (InputViewController *)sender; - const char * textBody = myInputViewController->textBody(); - myFunction->setContent(textBody); - }, - [](void * context, void * sender){ - }); -} - ListParameterController * ListController::parameterController() { return &m_parameterController; } @@ -80,7 +41,7 @@ HighlightCell * ListController::expressionCells(int index) { void ListController::willDisplayTitleCellAtIndex(HighlightCell * cell, int j) { FunctionTitleCell * myFunctionCell = (FunctionTitleCell *)cell; - CartesianFunction * function = ((CartesianFunctionStore *)m_functionStore)->functionAtIndex(j); + CartesianFunction * function = ((CartesianFunctionStore *)m_functionStore)->modelAtIndex(j); char bufferName[5] = {*function->name(),'(', m_functionStore->symbol(),')', 0}; myFunctionCell->setText(bufferName); KDColor functionNameColor = function->isActive() ? function->color() : Palette::GreyDark; @@ -88,18 +49,17 @@ void ListController::willDisplayTitleCellAtIndex(HighlightCell * cell, int j) { } void ListController::willDisplayExpressionCellAtIndex(HighlightCell * cell, int j) { + Shared::FunctionListController::willDisplayExpressionCellAtIndex(cell, j); FunctionExpressionCell * myCell = (FunctionExpressionCell *)cell; - Function * f = m_functionStore->functionAtIndex(j); - myCell->setExpressionLayout(f->layout()); + Function * f = m_functionStore->modelAtIndex(j); bool active = f->isActive(); KDColor textColor = active ? KDColorBlack : Palette::GreyDark; myCell->setTextColor(textColor); } -bool ListController::removeFunctionRow(Function * function) { - if (m_functionStore->numberOfFunctions() > 1) { - m_functionStore->removeFunction(function); - return true; +bool ListController::removeModelRow(ExpressionModel * model) { + if (m_functionStore->numberOfModels() > 1) { + return Shared::FunctionListController::removeModelRow(model); } return false; } @@ -108,8 +68,9 @@ View * ListController::loadView() { for (int i = 0; i < k_maxNumberOfRows; i++) { m_functionTitleCells[i] = new FunctionTitleCell(FunctionTitleCell::Orientation::VerticalIndicator); m_expressionCells[i] = new FunctionExpressionCell(); + m_expressionCells[i]->setMargin(k_expressionMargin); } - return Shared::ListController::loadView(); + return Shared::FunctionListController::loadView(); } void ListController::unloadView(View * view) { @@ -119,7 +80,7 @@ void ListController::unloadView(View * view) { delete m_expressionCells[i]; m_expressionCells[i] = nullptr; } - Shared::ListController::unloadView(view); + Shared::FunctionListController::unloadView(view); } } diff --git a/apps/graph/list/list_controller.h b/apps/graph/list/list_controller.h index 2eb9df238..2d4460dc0 100644 --- a/apps/graph/list/list_controller.h +++ b/apps/graph/list/list_controller.h @@ -3,29 +3,25 @@ #include #include "../function_title_cell.h" -#include "../../shared/function_expression_cell.h" #include "../cartesian_function_store.h" -#include "../../shared/new_function_cell.h" -#include "../../shared/list_controller.h" +#include "../../shared/function_expression_cell.h" +#include "../../shared/function_list_controller.h" #include "../../shared/list_parameter_controller.h" namespace Graph { -class ListController : public Shared::ListController { +class ListController : public Shared::FunctionListController { public: ListController(Responder * parentResponder, CartesianFunctionStore * functionStore, ButtonRowController * header, ButtonRowController * footer); const char * title() override; - int numberOfRows() override; - KDCoordinate rowHeight(int j) override; private: - void editExpression(Shared::Function * function, Ion::Events::Event event) override; Shared::ListParameterController * parameterController() override; int maxNumberOfRows() override; HighlightCell * titleCells(int index) override; HighlightCell * expressionCells(int index) override; void willDisplayTitleCellAtIndex(HighlightCell * cell, int j) override; void willDisplayExpressionCellAtIndex(HighlightCell * cell, int j) override; - bool removeFunctionRow(Shared::Function * function) override; + bool removeModelRow(Shared::ExpressionModel * function) override; View * loadView() override; void unloadView(View * view) override; constexpr static int k_maxNumberOfRows = 5; diff --git a/apps/graph/values/values_controller.cpp b/apps/graph/values/values_controller.cpp index 278eb5d40..5fdc157ab 100644 --- a/apps/graph/values/values_controller.cpp +++ b/apps/graph/values/values_controller.cpp @@ -55,7 +55,7 @@ void ValuesController::willDisplayCellAtLocation(HighlightCell * cell, int i, in } I18n::Message ValuesController::emptyMessage() { - if (m_functionStore->numberOfDefinedFunctions() == 0) { + if (m_functionStore->numberOfDefinedModels() == 0) { return I18n::Message::NoFunction; } return I18n::Message::NoActivatedFunction; @@ -68,7 +68,7 @@ IntervalParameterController * ValuesController::intervalParameterController() { CartesianFunction * ValuesController::functionAtColumn(int i) { assert(i > 0); int index = 1; - for (int k = 0; k < m_functionStore->numberOfDefinedFunctions(); k++) { + for (int k = 0; k < m_functionStore->numberOfDefinedModels(); k++) { if (m_functionStore->definedFunctionAtIndex(k)->isActive()) { if (i == index) { return m_functionStore->definedFunctionAtIndex(k); @@ -89,7 +89,7 @@ CartesianFunction * ValuesController::functionAtColumn(int i) { bool ValuesController::isDerivativeColumn(int i) { assert(i >= 1); int index = 1; - for (int k = 0; k < m_functionStore->numberOfDefinedFunctions(); k++) { + for (int k = 0; k < m_functionStore->numberOfDefinedModels(); k++) { if (m_functionStore->definedFunctionAtIndex(k)->isActive()) { if (i == index) { return false; diff --git a/apps/home/app_cell.cpp b/apps/home/app_cell.cpp index 1b5f24280..77388e0e0 100644 --- a/apps/home/app_cell.cpp +++ b/apps/home/app_cell.cpp @@ -45,7 +45,6 @@ void AppCell::setVisible(bool visible) { } void AppCell::reloadCell() { - HighlightCell::reloadCell(); m_nameView.setTextColor(isHighlighted() ? KDColorWhite : KDColorBlack); m_nameView.setBackgroundColor(isHighlighted() ? Palette::YellowDark : KDColorWhite); } diff --git a/apps/home/app_cell.h b/apps/home/app_cell.h index c614b7cb9..19dabf6fe 100644 --- a/apps/home/app_cell.h +++ b/apps/home/app_cell.h @@ -18,11 +18,11 @@ public: void reloadCell() override; void setAppDescriptor(::App::Descriptor * appDescriptor); private: - static constexpr KDCoordinate k_iconMargin = 18; + static constexpr KDCoordinate k_iconMargin = 22; static constexpr KDCoordinate k_iconWidth = 55; static constexpr KDCoordinate k_iconHeight = 56; static constexpr KDCoordinate k_nameWidthMargin = 4; - static constexpr KDCoordinate k_nameHeightMargin = 2; + static constexpr KDCoordinate k_nameHeightMargin = 1; ImageView m_iconView; MessageTextView m_nameView; bool m_visible; diff --git a/apps/home/controller.cpp b/apps/home/controller.cpp index 2b4963953..dca0d0780 100644 --- a/apps/home/controller.cpp +++ b/apps/home/controller.cpp @@ -10,10 +10,9 @@ Controller::ContentView::ContentView(Controller * controller, SelectableTableVie m_selectableTableView(controller, controller, selectionDataSource, controller) { m_selectableTableView.setVerticalCellOverlap(0); - m_selectableTableView.setMargins(0, k_sideMargin, 0, k_sideMargin); + m_selectableTableView.setMargins(0, k_sideMargin, k_bottomMargin, k_sideMargin); m_selectableTableView.setColorsBackground(false); m_selectableTableView.setIndicatorThickness(k_indicatorThickness); - m_selectableTableView.horizontalScrollIndicator()->setMargin(k_indicatorMargin); m_selectableTableView.verticalScrollIndicator()->setMargin(k_indicatorMargin); } @@ -53,13 +52,20 @@ Controller::Controller(Responder * parentResponder, ::AppsContainer * container, bool Controller::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK || event == Ion::Events::EXE) { - m_container->switchTo(m_container->appSnapshotAtIndex(m_selectionDataSource->selectedColumn()*k_numberOfRows+m_selectionDataSource->selectedRow()+1)); + m_container->switchTo(m_container->appSnapshotAtIndex(m_selectionDataSource->selectedRow()*k_numberOfColumns+m_selectionDataSource->selectedColumn()+1)); return true; } if (event == Ion::Events::Home || event == Ion::Events::Back) { return m_view.selectableTableView()->selectCellAtLocation(0,0); - } + } + + if (event == Ion::Events::Right && m_selectionDataSource->selectedRow() < numberOfRows()) { + return m_view.selectableTableView()->selectCellAtLocation(0, m_selectionDataSource->selectedRow()+1); + } + if (event == Ion::Events::Left && m_selectionDataSource->selectedRow() > 0) { + return m_view.selectableTableView()->selectCellAtLocation(numberOfColumns()-1, m_selectionDataSource->selectedRow()-1); + } return false; } @@ -81,11 +87,11 @@ View * Controller::view() { } int Controller::numberOfRows() { - return k_numberOfRows; + return ((numberOfIcons()-1)/k_numberOfColumns)+1; } int Controller::numberOfColumns() { - return ((numberOfIcons()-1)/k_numberOfRows)+1; + return k_numberOfColumns; } KDCoordinate Controller::cellHeight() { @@ -106,7 +112,7 @@ int Controller::reusableCellCount() { void Controller::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { AppCell * appCell = (AppCell *)cell; - int appIndex = (i*k_numberOfRows+j)+1; + int appIndex = (j*k_numberOfColumns+i)+1; if (appIndex >= m_container->numberOfApps()) { appCell->setVisible(false); } else { @@ -130,7 +136,7 @@ void Controller::tableViewDidChangeSelection(SelectableTableView * t, int previo * redrawing takes time and is visible at scrolling. Here, we avoid the * background complete redrawing but the code is a bit * clumsy. */ - if (m_container->numberOfApps()%2 == 0 && t->selectedColumn() == numberOfColumns() -1) { + if (m_container->numberOfApps()%2 == 1 && t->selectedRow() == numberOfRows() -1) { m_view.reloadBottomRightCorner(this); } /* To prevent the selectable table view to select cells that are unvisible, @@ -138,7 +144,7 @@ void Controller::tableViewDidChangeSelection(SelectableTableView * t, int previo * unvisible. This trick does not create an endless loop as we ensure not to * stay on a unvisible cell and to initialize the first cell on a visible one * (so the previous one is always visible). */ - int appIndex = (t->selectedColumn()*k_numberOfRows+t->selectedRow())+1; + int appIndex = (t->selectedColumn()+t->selectedRow()*k_numberOfColumns)+1; if (appIndex >= m_container->numberOfApps()) { t->selectCellAtLocation(previousSelectedCellX, previousSelectedCellY); } diff --git a/apps/home/controller.h b/apps/home/controller.h index 0d15f4e12..57dd13d21 100644 --- a/apps/home/controller.h +++ b/apps/home/controller.h @@ -42,11 +42,12 @@ private: }; AppsContainer * m_container; static constexpr KDCoordinate k_sideMargin = 4; - static constexpr KDCoordinate k_indicatorThickness = 28; - static constexpr KDCoordinate k_indicatorMargin = 116; - static constexpr int k_numberOfRows = 2; + static constexpr KDCoordinate k_bottomMargin = 14; + static constexpr KDCoordinate k_indicatorThickness = 15; + static constexpr KDCoordinate k_indicatorMargin = 61; + static constexpr int k_numberOfColumns = 3; static constexpr int k_maxNumberOfCells = 16; - static constexpr int k_cellHeight = 98; + static constexpr int k_cellHeight = 104; static constexpr int k_cellWidth = 104; ContentView m_view; AppCell m_cells[k_maxNumberOfCells]; diff --git a/apps/i18n.py b/apps/i18n.py index 7a6a71ede..da39113c7 100644 --- a/apps/i18n.py +++ b/apps/i18n.py @@ -7,6 +7,7 @@ import argparse import io ion_special_characters = { + u'Δ': "Ion::Charset::CapitalDelta", u'Σ': "Ion::Charset::CapitalSigma", u'λ': "Ion::Charset::SmallLambda", u'μ': "Ion::Charset::SmallMu", diff --git a/apps/probability/calculation_cell.h b/apps/probability/calculation_cell.h index fbb8e00bc..26178b562 100644 --- a/apps/probability/calculation_cell.h +++ b/apps/probability/calculation_cell.h @@ -15,6 +15,9 @@ public: void drawRect(KDContext * ctx, KDRect rect) const override; EditableTextCell * editableTextCell(); MessageTextView * messageTextView(); + const char * text() const override { + return m_calculation.text(); + } private: constexpr static KDCoordinate k_margin = 5; constexpr static KDCoordinate k_minTextFieldWidth = 4*KDText::charSize().width()+TextCursorView::k_width; diff --git a/apps/probability/calculation_controller.cpp b/apps/probability/calculation_controller.cpp index 9146e3612..e889ea174 100644 --- a/apps/probability/calculation_controller.cpp +++ b/apps/probability/calculation_controller.cpp @@ -88,15 +88,6 @@ void CalculationController::didBecomeFirstResponder() { app()->setFirstResponder(&m_selectableTableView); } -bool CalculationController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::Copy && selectedColumn() > 0) { - CalculationCell * myCell = static_cast(m_selectableTableView.selectedCell()); - Clipboard::sharedClipboard()->store(myCell->editableTextCell()->textField()->text()); - return true; - } - return false; -} - View * CalculationController::view() { return &m_contentView; } @@ -141,23 +132,6 @@ KDCoordinate CalculationController::rowHeight(int j) { return ResponderImageCell::k_oneCellHeight; } -KDCoordinate CalculationController::cumulatedWidthFromIndex(int j) { - int result = 0; - for (int k = 0; k < j; k++) { - result += columnWidth(k); - } - return result; -} - -int CalculationController::indexFromCumulatedWidth(KDCoordinate offsetX) { - int result = 0; - int i = 0; - while (result < offsetX && i < numberOfColumns()) { - result += columnWidth(i++); - } - return (result < offsetX || offsetX == 0) ? i : i - 1; -} - KDCoordinate CalculationController::cumulatedHeightFromIndex(int j) { return rowHeight(0) * j; } diff --git a/apps/probability/calculation_controller.h b/apps/probability/calculation_controller.h index 7691f0444..9b2ccb400 100644 --- a/apps/probability/calculation_controller.h +++ b/apps/probability/calculation_controller.h @@ -17,7 +17,6 @@ public: /* Responder */ void didEnterResponderChain(Responder * previousResponder) override; void didBecomeFirstResponder() override; - bool handleEvent(Ion::Events::Event event) override; /* ViewController */ View * view() override; @@ -30,9 +29,7 @@ public: int numberOfColumns() override; KDCoordinate columnWidth(int i) override; KDCoordinate rowHeight(int j) override; - KDCoordinate cumulatedWidthFromIndex(int i) override; KDCoordinate cumulatedHeightFromIndex(int j) override; - int indexFromCumulatedWidth(KDCoordinate offsetX) override; int indexFromCumulatedHeight(KDCoordinate offsetY) override; HighlightCell * reusableCell(int index, int type) override; int reusableCellCount(int type) override; diff --git a/apps/regression/calculation_controller.cpp b/apps/regression/calculation_controller.cpp index 00a7efa23..d8b774acd 100644 --- a/apps/regression/calculation_controller.cpp +++ b/apps/regression/calculation_controller.cpp @@ -42,20 +42,6 @@ bool CalculationController::handleEvent(Ion::Events::Event event) { app()->setFirstResponder(tabController()); return true; } - if (event == Ion::Events::Copy && selectedColumn() == 1 && selectedRow() > 0) { - if (selectedRow() <= k_totalNumberOfDoubleBufferRows) { - EvenOddDoubleBufferTextCell * myCell = (EvenOddDoubleBufferTextCell *)selectableTableView()->selectedCell(); - if (myCell->firstTextSelected()) { - Clipboard::sharedClipboard()->store(myCell->firstText()); - } else { - Clipboard::sharedClipboard()->store(myCell->secondText()); - } - } else { - EvenOddBufferTextCell * myCell = (EvenOddBufferTextCell *)selectableTableView()->selectedCell(); - Clipboard::sharedClipboard()->store(myCell->text()); - } - return true; - } return false; } diff --git a/apps/regression/even_odd_double_buffer_text_cell.cpp b/apps/regression/even_odd_double_buffer_text_cell.cpp index 84776c85c..dd31169b0 100644 --- a/apps/regression/even_odd_double_buffer_text_cell.cpp +++ b/apps/regression/even_odd_double_buffer_text_cell.cpp @@ -48,6 +48,14 @@ void EvenOddDoubleBufferTextCell::setHighlighted(bool highlight) { reloadCell(); } +const char * EvenOddDoubleBufferTextCell::text() const { + if (m_firstTextSelected) { + return m_firstBufferTextView.text(); + } else { + return m_secondBufferTextView.text(); + } +} + void EvenOddDoubleBufferTextCell::setEven(bool even) { m_firstBufferTextView.setEven(even); m_secondBufferTextView.setEven(even); diff --git a/apps/regression/even_odd_double_buffer_text_cell.h b/apps/regression/even_odd_double_buffer_text_cell.h index b3e8b6b2d..8e612e05d 100644 --- a/apps/regression/even_odd_double_buffer_text_cell.h +++ b/apps/regression/even_odd_double_buffer_text_cell.h @@ -13,6 +13,7 @@ public: Responder * responder() override { return this; } + const char * text() const override; void setEven(bool even) override; bool firstTextSelected(); void selectFirstText(bool selectFirstText); diff --git a/apps/sequence/Makefile b/apps/sequence/Makefile index 2c47f04dd..722141570 100644 --- a/apps/sequence/Makefile +++ b/apps/sequence/Makefile @@ -35,6 +35,6 @@ tests += $(addprefix apps/sequence/test/,\ sequence.cpp\ ) test_objs += $(addprefix apps/sequence/, sequence.o sequence_store.o sequence_context.o cache_context.o) -test_objs += $(addprefix apps/shared/, function.o function_store.o) +test_objs += $(addprefix apps/shared/, expression_model.o expression_model_store.o function.o function_store.o) app_images += apps/sequence/sequence_icon.png diff --git a/apps/sequence/graph/graph_controller.cpp b/apps/sequence/graph/graph_controller.cpp index f720cc60e..6cb709f81 100644 --- a/apps/sequence/graph/graph_controller.cpp +++ b/apps/sequence/graph/graph_controller.cpp @@ -19,7 +19,7 @@ GraphController::GraphController(Responder * parentResponder, SequenceStore * se } I18n::Message GraphController::emptyMessage() { - if (m_sequenceStore->numberOfDefinedFunctions() == 0) { + if (m_sequenceStore->numberOfDefinedModels() == 0) { return I18n::Message::NoSequence; } return I18n::Message::NoActivatedSequence; diff --git a/apps/sequence/list/list_controller.cpp b/apps/sequence/list/list_controller.cpp index 23e0ecf2c..832551c12 100644 --- a/apps/sequence/list/list_controller.cpp +++ b/apps/sequence/list/list_controller.cpp @@ -8,7 +8,7 @@ using namespace Poincare; namespace Sequence { ListController::ListController(Responder * parentResponder, SequenceStore * sequenceStore, ButtonRowController * header, ButtonRowController * footer) : - Shared::ListController(parentResponder, sequenceStore, header, footer, I18n::Message::AddSequence), + Shared::FunctionListController(parentResponder, sequenceStore, header, footer, I18n::Message::AddSequence), m_sequenceStore(sequenceStore), m_sequenceTitleCells{}, m_expressionCells{}, @@ -39,23 +39,23 @@ ExpressionFieldDelegateApp * ListController::expressionFieldDelegateApp() { return (App *)app(); } -int ListController::numberOfRows() { +int ListController::numberOfExpressionRows() { int numberOfRows = 0; - for (int i = 0; i < m_sequenceStore->numberOfFunctions(); i++) { - Sequence * sequence = m_sequenceStore->functionAtIndex(i); + for (int i = 0; i < m_sequenceStore->numberOfModels(); i++) { + Sequence * sequence = m_sequenceStore->modelAtIndex(i); numberOfRows += sequence->numberOfElements(); } - if (m_sequenceStore->numberOfFunctions() == m_sequenceStore->maxNumberOfFunctions()) { + if (m_sequenceStore->numberOfModels() == m_sequenceStore->maxNumberOfModels()) { return numberOfRows; } return 1 + numberOfRows; }; -KDCoordinate ListController::rowHeight(int j) { - if (m_sequenceStore->numberOfFunctions() < m_sequenceStore->maxNumberOfFunctions() && j == numberOfRows() - 1) { +KDCoordinate ListController::expressionRowHeight(int j) { + if (m_sequenceStore->numberOfModels() < m_sequenceStore->maxNumberOfModels() && j == numberOfRows() - 1) { return Metric::StoreRowHeight; } - Sequence * sequence = m_sequenceStore->functionAtIndex(functionIndexForRow(j)); + Sequence * sequence = m_sequenceStore->modelAtIndex(modelIndexForRow(j)); KDCoordinate defaultHeight = sequence->type() == Sequence::Type::Explicit ? Metric::StoreRowHeight : k_emptySubRowHeight; ExpressionLayout * layout = sequence->layout(); if (sequenceDefinitionForRow(j) == 1) { @@ -72,9 +72,9 @@ KDCoordinate ListController::rowHeight(int j) { } void ListController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { - Shared::ListController::willDisplayCellAtLocation(cell, i, j); + Shared::FunctionListController::willDisplayCellAtLocation(cell, i, j); EvenOddCell * myCell = (EvenOddCell *)cell; - myCell->setEven(functionIndexForRow(j)%2 == 0); + myCell->setEven(modelIndexForRow(j)%2 == 0); } void ListController::selectPreviousNewSequenceCell() { @@ -87,7 +87,7 @@ Toolbox * ListController::toolboxForSender(Responder * sender) { // Set extra cells int recurrenceDepth = -1; int sequenceDefinition = sequenceDefinitionForRow(selectedRow()); - Sequence * sequence = m_sequenceStore->functionAtIndex(functionIndexForRow(selectedRow())); + Sequence * sequence = m_sequenceStore->modelAtIndex(modelIndexForRow(selectedRow())); if (sequenceDefinition == 0) { recurrenceDepth = sequence->numberOfElements()-1; } @@ -154,8 +154,8 @@ void ListController::editExpression(Sequence * sequence, int sequenceDefinition, } } -bool ListController::removeFunctionRow(Function * function) { - m_functionStore->removeFunction(function); +bool ListController::removeModelRow(ExpressionModel * model) { + Shared::FunctionListController::removeModelRow(model); // Invalidate the sequences context cache static_cast(app())->localContext()->resetCache(); return true; @@ -182,7 +182,7 @@ HighlightCell * ListController::expressionCells(int index) { void ListController::willDisplayTitleCellAtIndex(HighlightCell * cell, int j) { SequenceTitleCell * myCell = (SequenceTitleCell *)cell; - Sequence * sequence = m_sequenceStore->functionAtIndex(functionIndexForRow(j)); + Sequence * sequence = m_sequenceStore->modelAtIndex(modelIndexForRow(j)); if (sequenceDefinitionForRow(j) == 0) { myCell->setExpressionLayout(sequence->definitionName()); } @@ -198,7 +198,7 @@ void ListController::willDisplayTitleCellAtIndex(HighlightCell * cell, int j) { void ListController::willDisplayExpressionCellAtIndex(HighlightCell * cell, int j) { FunctionExpressionCell * myCell = (FunctionExpressionCell *)cell; - Sequence * sequence = m_sequenceStore->functionAtIndex(functionIndexForRow(j)); + Sequence * sequence = m_sequenceStore->modelAtIndex(modelIndexForRow(j)); if (sequenceDefinitionForRow(j) == 0) { myCell->setExpressionLayout(sequence->layout()); } @@ -213,45 +213,32 @@ void ListController::willDisplayExpressionCellAtIndex(HighlightCell * cell, int myCell->setTextColor(textColor); } -int ListController::functionIndexForRow(int j) { +int ListController::modelIndexForRow(int j) { if (j < 0) { return j; } - if (m_sequenceStore->numberOfFunctions() < m_sequenceStore->maxNumberOfFunctions() && - j == numberOfRows() - 1) { - return functionIndexForRow(j-1)+1; + if (isAddEmptyRow(j)) { + return modelIndexForRow(j-1)+1; } int rowIndex = 0; int sequenceIndex = -1; do { sequenceIndex++; - Sequence * sequence = m_sequenceStore->functionAtIndex(sequenceIndex); + Sequence * sequence = m_sequenceStore->modelAtIndex(sequenceIndex); rowIndex += sequence->numberOfElements(); } while (rowIndex <= j); return sequenceIndex; } -const char * ListController::textForRow(int j) { - Sequence * sequence = (Sequence *)m_functionStore->functionAtIndex(functionIndexForRow(j)); - switch (sequenceDefinitionForRow(j)) { - case 0: - return sequence->text(); - case 1: - return sequence->firstInitialConditionText(); - case 2: - return sequence->secondInitialConditionText(); - default: - assert(false); - return nullptr; - } +bool ListController::isAddEmptyRow(int j) { + return m_sequenceStore->numberOfModels() < m_sequenceStore->maxNumberOfModels() && j == numberOfRows() - 1; } int ListController::sequenceDefinitionForRow(int j) { if (j < 0) { return j; } - if (m_sequenceStore->numberOfFunctions() < m_sequenceStore->maxNumberOfFunctions() && - j == numberOfRows() - 1) { + if (isAddEmptyRow(j)) { return 0; } int rowIndex = 0; @@ -259,25 +246,25 @@ int ListController::sequenceDefinitionForRow(int j) { Sequence * sequence = nullptr; do { sequenceIndex++; - sequence = m_sequenceStore->functionAtIndex(sequenceIndex); + sequence = m_sequenceStore->modelAtIndex(sequenceIndex); rowIndex += sequence->numberOfElements(); } while (rowIndex <= j); return sequence->numberOfElements()-rowIndex+j; } -void ListController::addEmptyFunction() { +void ListController::addEmptyModel() { app()->displayModalViewController(&m_typeStackController, 0.f, 0.f, Metric::TabHeight+Metric::ModalTopMargin, Metric::CommonRightMargin, Metric::ModalBottomMargin, Metric::CommonLeftMargin); } -void ListController::editExpression(Shared::Function * function, Ion::Events::Event event) { - Sequence * sequence = (Sequence *)function; +void ListController::editExpression(Shared::ExpressionModel * model, Ion::Events::Event event) { + Sequence * sequence = static_cast(model); editExpression(sequence, sequenceDefinitionForRow(selectedRow()), event); } -void ListController::reinitExpression(Shared::Function * function) { +void ListController::reinitExpression(Shared::ExpressionModel * model) { // Invalidate the sequences context cache static_cast(app())->localContext()->resetCache(); - Sequence * sequence = (Sequence *)function; + Sequence * sequence = static_cast(model); switch (sequenceDefinitionForRow(selectedRow())) { case 1: if (strlen(sequence->firstInitialConditionText()) == 0) { @@ -305,8 +292,9 @@ View * ListController::loadView() { for (int i = 0; i < k_maxNumberOfRows; i++) { m_sequenceTitleCells[i] = new SequenceTitleCell(FunctionTitleCell::Orientation::VerticalIndicator); m_expressionCells[i] = new FunctionExpressionCell(); + m_expressionCells[i]->setMargin(k_expressionMargin); } - return Shared::ListController::loadView(); + return Shared::FunctionListController::loadView(); } void ListController::unloadView(View * view) { @@ -316,7 +304,7 @@ void ListController::unloadView(View * view) { delete m_expressionCells[i]; m_expressionCells[i] = nullptr; } - Shared::ListController::unloadView(view); + Shared::FunctionListController::unloadView(view); } } diff --git a/apps/sequence/list/list_controller.h b/apps/sequence/list/list_controller.h index a6a95bc06..3aa683ec9 100644 --- a/apps/sequence/list/list_controller.h +++ b/apps/sequence/list/list_controller.h @@ -5,8 +5,7 @@ #include "../sequence_title_cell.h" #include "../sequence_store.h" #include "../../shared/function_expression_cell.h" -#include "../../shared/list_controller.h" -#include "../../shared/new_function_cell.h" +#include "../../shared/function_list_controller.h" #include "../../shared/expression_layout_field_delegate.h" #include "../../shared/text_field_delegate.h" #include "list_parameter_controller.h" @@ -15,34 +14,34 @@ namespace Sequence { -class ListController : public Shared::ListController, public Shared::TextFieldDelegate, public Shared::ExpressionLayoutFieldDelegate { +class ListController : public Shared::FunctionListController, public Shared::TextFieldDelegate, public Shared::ExpressionLayoutFieldDelegate { public: ListController(Responder * parentResponder, SequenceStore * sequenceStore, ButtonRowController * header, ButtonRowController * footer); const char * title() override; - int numberOfRows() override; - virtual KDCoordinate rowHeight(int j) override; + int numberOfExpressionRows() override; + KDCoordinate expressionRowHeight(int j) override; void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; Toolbox * toolboxForTextInput(TextInput * textInput) override; Toolbox * toolboxForExpressionLayoutField(ExpressionLayoutField * expressionLayoutField) override; void selectPreviousNewSequenceCell(); + void editExpression(Sequence * sequence, int sequenceDefinitionIndex, Ion::Events::Event event); private: Toolbox * toolboxForSender(Responder * sender); Shared::TextFieldDelegateApp * textFieldDelegateApp() override; Shared::ExpressionFieldDelegateApp * expressionFieldDelegateApp() override; - void editExpression(Sequence * sequence, int sequenceDefinitionIndex, Ion::Events::Event event); ListParameterController * parameterController() override; int maxNumberOfRows() override; HighlightCell * titleCells(int index) override; HighlightCell * expressionCells(int index) override; void willDisplayTitleCellAtIndex(HighlightCell * cell, int j) override; void willDisplayExpressionCellAtIndex(HighlightCell * cell, int j) override; - int functionIndexForRow(int j) override; - const char * textForRow(int j) override; + int modelIndexForRow(int j) override; + bool isAddEmptyRow(int j) override; int sequenceDefinitionForRow(int j); - void addEmptyFunction() override; - void editExpression(Shared::Function * function, Ion::Events::Event event) override; - bool removeFunctionRow(Shared::Function * function) override; - void reinitExpression(Shared::Function * function) override; + void addEmptyModel() override; + void reinitExpression(Shared::ExpressionModel * model) override; + void editExpression(Shared::ExpressionModel * model, Ion::Events::Event event) override; + bool removeModelRow(Shared::ExpressionModel * model) override; View * loadView() override; void unloadView(View * view) override; static constexpr KDCoordinate k_emptySubRowHeight = 30; diff --git a/apps/sequence/list/list_parameter_controller.cpp b/apps/sequence/list/list_parameter_controller.cpp index 89fec88a0..d119bcaad 100644 --- a/apps/sequence/list/list_parameter_controller.cpp +++ b/apps/sequence/list/list_parameter_controller.cpp @@ -61,8 +61,8 @@ bool ListParameterController::handleEvent(Ion::Events::Event event) { #else if (selectedRowIndex == 2+hasAdditionalRow) { #endif - if (m_functionStore->numberOfFunctions() > 0) { - m_functionStore->removeFunction(m_function); + if (m_functionStore->numberOfModels() > 0) { + m_functionStore->removeModel(m_function); static_cast(app())->localContext()->resetCache(); StackViewController * stack = (StackViewController *)(parentResponder()); stack->pop(); diff --git a/apps/sequence/list/type_parameter_controller.cpp b/apps/sequence/list/type_parameter_controller.cpp index 95f9c8918..fe2173425 100644 --- a/apps/sequence/list/type_parameter_controller.cpp +++ b/apps/sequence/list/type_parameter_controller.cpp @@ -82,9 +82,10 @@ bool TypeParameterController::handleEvent(Ion::Events::Event event) { stack->pop(); return true; } - Sequence * newSequence = m_sequenceStore->addEmptyFunction(); + Sequence * newSequence = static_cast(m_sequenceStore->addEmptyModel()); newSequence->setType((Sequence::Type)selectedRow()); app()->dismissModalViewController(); + m_listController->editExpression(newSequence, 0, Ion::Events::OK); return true; } if (event == Ion::Events::Left && m_sequence) { diff --git a/apps/sequence/sequence.cpp b/apps/sequence/sequence.cpp index eab918afb..50f13ea2f 100644 --- a/apps/sequence/sequence.cpp +++ b/apps/sequence/sequence.cpp @@ -185,10 +185,6 @@ Poincare::ExpressionLayout * Sequence::secondInitialConditionLayout() { return m_secondInitialConditionLayout; } -void Sequence::setContent(const char * c) { - Function::setContent(c); -} - void Sequence::setFirstInitialConditionContent(const char * c) { strlcpy(m_firstInitialConditionText, c, sizeof(m_firstInitialConditionText)); if (m_firstInitialConditionExpression != nullptr) { @@ -236,19 +232,19 @@ Poincare::ExpressionLayout * Sequence::definitionName() { if (m_type == Type::Explicit) { m_definitionName = new HorizontalLayout( new CharLayout(name()[0], KDText::FontSize::Large), - new VerticalOffsetLayout(LayoutEngine::createStringLayout("n ", 2, KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), + new VerticalOffsetLayout(LayoutEngine::createStringLayout("n", 1, KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), false); } if (m_type == Type::SingleRecurrence) { m_definitionName = new HorizontalLayout( new CharLayout(name()[0], KDText::FontSize::Large), - new VerticalOffsetLayout(LayoutEngine::createStringLayout("n+1 ", 4, KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), + new VerticalOffsetLayout(LayoutEngine::createStringLayout("n+1", 3, KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), false); } if (m_type == Type::DoubleRecurrence) { m_definitionName = new HorizontalLayout( new CharLayout(name()[0], KDText::FontSize::Large), - new VerticalOffsetLayout(LayoutEngine::createStringLayout("n+2 ", 4, KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), + new VerticalOffsetLayout(LayoutEngine::createStringLayout("n+2", 3, KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), false); } } @@ -340,9 +336,7 @@ T Sequence::approximateToNextRank(int n, SequenceContext * sqctx) const { { ctx.setValueForSymbol(un, &unSymbol); ctx.setValueForSymbol(vn, &vnSymbol); - Poincare::Complex e = Poincare::Complex::Float(n); - ctx.setExpressionForSymbolName(&e, &nSymbol, *sqctx); - return expression(sqctx)->template approximateToScalar(ctx); + return expression(sqctx)->approximateWithValueForSymbol(symbol(), (T)n, ctx); } case Type::SingleRecurrence: { @@ -353,9 +347,7 @@ T Sequence::approximateToNextRank(int n, SequenceContext * sqctx) const { ctx.setValueForSymbol(unm1, &unSymbol); ctx.setValueForSymbol(vn, &vn1Symbol); ctx.setValueForSymbol(vnm1, &vnSymbol); - Poincare::Complex e = Poincare::Complex::Float(n-1); - ctx.setExpressionForSymbolName(&e, &nSymbol, *sqctx); - return expression(sqctx)->template approximateToScalar(ctx); + return expression(sqctx)->approximateWithValueForSymbol(symbol(), (T)(n-1), ctx); } default: { @@ -369,9 +361,7 @@ T Sequence::approximateToNextRank(int n, SequenceContext * sqctx) const { ctx.setValueForSymbol(unm2, &unSymbol); ctx.setValueForSymbol(vnm1, &vn1Symbol); ctx.setValueForSymbol(vnm2, &vnSymbol); - Poincare::Complex e = Poincare::Complex::Float(n-2); - ctx.setExpressionForSymbolName(&e, &nSymbol, *sqctx); - return expression(sqctx)->template approximateToScalar(ctx); + return expression(sqctx)->approximateWithValueForSymbol(symbol(), (T)(n-2), ctx); } } } diff --git a/apps/sequence/sequence.h b/apps/sequence/sequence.h index 12a3ecf44..a40574c4e 100644 --- a/apps/sequence/sequence.h +++ b/apps/sequence/sequence.h @@ -17,7 +17,7 @@ public: Sequence(const char * text = nullptr, KDColor color = KDColorBlack); ~Sequence(); Sequence& operator=(const Sequence& other); - Sequence& operator=(Sequence&& other) = delete; + //Sequence& operator=(Sequence&& other) = delete; Sequence(const Sequence& other) = delete; Sequence(Sequence&& other) = delete; uint32_t checksum() override; @@ -36,7 +36,6 @@ public: * invalidate the cache because the sequences evaluations might have changed. */ void setType(Type type); void setInitialRank(int rank); - void setContent(const char * c) override; void setFirstInitialConditionContent(const char * c); void setSecondInitialConditionContent(const char * c); int numberOfElements(); diff --git a/apps/sequence/sequence_context.cpp b/apps/sequence/sequence_context.cpp index fce83df19..193dc9b98 100644 --- a/apps/sequence/sequence_context.cpp +++ b/apps/sequence/sequence_context.cpp @@ -49,9 +49,9 @@ void TemplatedSequenceContext::step(SequenceStore * sequenceStore, SequenceCo } /* Evaluate new u(n) and v(n) */ - Sequence * u = sequenceStore->numberOfFunctions() > 0 ? sequenceStore->functionAtIndex(0) : nullptr; + Sequence * u = sequenceStore->numberOfModels() > 0 ? sequenceStore->modelAtIndex(0) : nullptr; u = u && u->isDefined() ? u : nullptr; - Sequence * v = sequenceStore->numberOfFunctions() > 1 ? sequenceStore->functionAtIndex(1) : nullptr; + Sequence * v = sequenceStore->numberOfModels() > 1 ? sequenceStore->modelAtIndex(1) : nullptr; v = v && v->isDefined() ? v : nullptr; /* Switch u & v if the name of u is v */ if (u != nullptr && u->name()[0] == SequenceStore::k_sequenceNames[1][0]) { diff --git a/apps/sequence/sequence_store.cpp b/apps/sequence/sequence_store.cpp index 6a51047bc..dd2781701 100644 --- a/apps/sequence/sequence_store.cpp +++ b/apps/sequence/sequence_store.cpp @@ -20,90 +20,24 @@ uint32_t SequenceStore::storeChecksum() { return Ion::crc32((uint32_t *)checksums, dataLengthInBytes/sizeof(uint32_t)); } -Sequence * SequenceStore::functionAtIndex(int i) { - assert(i>=0 && i=0 && i=0 && i(f)); } } diff --git a/apps/sequence/sequence_store.h b/apps/sequence/sequence_store.h index 423bff97d..5ee9f8bc3 100644 --- a/apps/sequence/sequence_store.h +++ b/apps/sequence/sequence_store.h @@ -12,26 +12,33 @@ class SequenceStore : public Shared::FunctionStore { public: using Shared::FunctionStore::FunctionStore; uint32_t storeChecksum() override; - Sequence * functionAtIndex(int i) override; - Sequence * activeFunctionAtIndex(int i) override; - Sequence * definedFunctionAtIndex(int i) override; - Sequence * addEmptyFunction() override; - /* WARNING: after calling removeFunction or removeAll, the sequence context + Sequence * modelAtIndex(int i) override { + assert(i>=0 && i(store.addEmptyModel()); assert(u->name()[0] == 'u'); u->setType(typeU); u->setContent(definitionU); @@ -29,14 +29,14 @@ void check_sequences_defined_by(double result[2][10], Sequence::Type typeU, cons } } if (definitionV) { - if (store.numberOfFunctions() == 0) { - Sequence * tempU = store.addEmptyFunction(); - v = store.addEmptyFunction(); - store.removeFunction(tempU); - v = store.functionAtIndex(0); + if (store.numberOfModels() == 0) { + Sequence * tempU = static_cast(store.addEmptyModel()); + v = static_cast(store.addEmptyModel()); + store.removeModel(tempU); + v = store.modelAtIndex(0); } else { - assert(store.numberOfFunctions() == 1); - v = store.addEmptyFunction(); + assert(store.numberOfModels() == 1); + v = static_cast(store.addEmptyModel()); } v->setType(typeV); v->setContent(definitionV); diff --git a/apps/sequence/values/values_controller.cpp b/apps/sequence/values/values_controller.cpp index 0a1bc8f94..c7a91231f 100644 --- a/apps/sequence/values/values_controller.cpp +++ b/apps/sequence/values/values_controller.cpp @@ -36,7 +36,7 @@ void ValuesController::willDisplayCellAtLocation(HighlightCell * cell, int i, in } I18n::Message ValuesController::emptyMessage() { - if (m_sequenceStore->numberOfDefinedFunctions() == 0) { + if (m_sequenceStore->numberOfDefinedModels() == 0) { return I18n::Message::NoSequence; } return I18n::Message::NoActivatedSequence; diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index 70c127c76..0dfa15f0e 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -11,6 +11,7 @@ DataTab = "Daten" DefaultSetting = "Grundeinstellungen" Deg = "gra" DisplayValues = "Werte anzeigen" +Empty = "Leer" ExitExamMode1 = "Wollen Sie den Testmodus " ExitExamMode2 = "verlassen?" ForbiddenValue = "Verbotener Wert" diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index a1f9ed2e9..adff13752 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -11,6 +11,7 @@ DataTab = "Data" DefaultSetting = "Basic settings" Deg = "deg" DisplayValues = "Display values" +Empty = "Empty" ExitExamMode1 = "Exit the exam " ExitExamMode2 = "mode?" ForbiddenValue = "Forbidden value" diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index c0c02ff46..bf7bc4eac 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -11,6 +11,7 @@ DataTab = "Datos" DefaultSetting = "Ajustes basicos" Deg = "gra" DisplayValues = "Visualizar los valores" +Empty = "Vacio" ExitExamMode1 = "Salir del modo " ExitExamMode2 = "examen ?" ForbiddenValue = "Valor prohibido" diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index 47cd17a61..96a2b3ce2 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -11,6 +11,7 @@ DataTab = "Donnees" DefaultSetting = "Reglages de base" Deg = "deg" DisplayValues = "Afficher les valeurs" +Empty = "Vide" ExitExamMode1 = "Voulez-vous sortir " ExitExamMode2 = "du mode examen ?" ForbiddenValue = "Valeur interdite" diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index e6c7263b2..b336ddeee 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -11,6 +11,7 @@ DataTab = "Dados" DefaultSetting = "Configuracoes basicas" Deg = "gra" DisplayValues = "Exibir os valores" +Empty = "Vacuo" ExitExamMode1 = "Voce quer sair do modo de " ExitExamMode2 = "exame ?" ForbiddenValue = "Valor proibida" diff --git a/apps/shared.universal.i18n b/apps/shared.universal.i18n index 6ee024d51..f15af9174 100644 --- a/apps/shared.universal.i18n +++ b/apps/shared.universal.i18n @@ -19,6 +19,7 @@ DeterminantCommandWithArg = "det(M)" DiffCommandWithArg = "diff(f(x),a)" DimensionCommandWithArg = "dim(M)" DiscreteLegend = "P(X=" +DiscriminantFormulaDegree2 = "Δ=b^2-4ac" Equal = "=" FactorCommandWithArg = "factor(n)" FccId = "FCC ID" diff --git a/apps/shared/Makefile b/apps/shared/Makefile index 01c64faa4..b0bb7ed93 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -7,17 +7,21 @@ app_objs += $(addprefix apps/shared/,\ curve_view_range.o\ editable_cell_table_view_controller.o\ expression_field_delegate_app.o\ + expression_model.o\ + expression_model_list_controller.o\ + expression_model_store.o\ float_pair_store.o\ float_parameter_controller.o\ function.o\ function_app.o\ function_banner_delegate.o\ function_curve_parameter_controller.o\ + function_expression_cell.o\ function_go_to_parameter_controller.o\ function_graph_view.o\ function_graph_controller.o\ + function_list_controller.o\ function_store.o\ - function_expression_cell.o\ function_title_cell.o\ go_to_parameter_controller.o\ initialisation_parameter_controller.o\ @@ -27,16 +31,16 @@ app_objs += $(addprefix apps/shared/,\ interval.o\ interval_parameter_controller.o\ language_controller.o\ - list_controller.o\ list_parameter_controller.o\ memoized_curve_view_range.o\ message_view.o\ - new_function_cell.o\ ok_view.o\ parameter_text_field_delegate.o\ range_parameter_controller.o\ regular_table_view_data_source.o\ round_cursor_view.o\ + scrollable_exact_approximate_expressions_cell.o\ + scrollable_exact_approximate_expressions_view.o\ simple_interactive_curve_view_controller.o\ expression_layout_field_delegate.o\ store_controller.o\ diff --git a/apps/shared/expression_model.cpp b/apps/shared/expression_model.cpp new file mode 100644 index 000000000..991031663 --- /dev/null +++ b/apps/shared/expression_model.cpp @@ -0,0 +1,87 @@ +#include "function.h" +#include +#include +#include + +using namespace Poincare; + +namespace Shared { + +ExpressionModel::ExpressionModel() : + m_text{0}, + m_expression(nullptr), + m_layout(nullptr) +{ +} + +ExpressionModel::~ExpressionModel() { + if (m_layout != nullptr) { + delete m_layout; + m_layout = nullptr; + } + if (m_expression != nullptr) { + delete m_expression; + m_expression = nullptr; + } +} + +ExpressionModel& ExpressionModel::operator=(const ExpressionModel& other) { + // Self-assignment is benign + setContent(other.m_text); + return *this; +} + +const char * ExpressionModel::text() const { + return m_text; +} + +Poincare::Expression * ExpressionModel::expression(Poincare::Context * context) const { + if (m_expression == nullptr) { + m_expression = Expression::ParseAndSimplify(m_text, *context); + } + return m_expression; +} + +Poincare::ExpressionLayout * ExpressionModel::layout() { + if (m_layout == nullptr) { + Expression * nonSimplifiedExpression = Expression::parse(m_text); + if (nonSimplifiedExpression != nullptr) { + m_layout = nonSimplifiedExpression->createLayout(PrintFloat::Mode::Decimal); + delete nonSimplifiedExpression; + } + } + return m_layout; +} + +bool ExpressionModel::isDefined() { + return m_text[0] != 0; +} + +bool ExpressionModel::isEmpty() { + return m_text[0] == 0; +} + +void ExpressionModel::setContent(const char * c) { + strlcpy(m_text, c, sizeof(m_text)); + if (m_layout != nullptr) { + delete m_layout; + m_layout = nullptr; + } + if (m_expression != nullptr) { + delete m_expression; + m_expression = nullptr; + } +} + +void ExpressionModel::tidy() { + if (m_layout != nullptr) { + delete m_layout; + m_layout = nullptr; + } + if (m_expression != nullptr) { + delete m_expression; + m_expression = nullptr; + } +} + +} diff --git a/apps/shared/expression_model.h b/apps/shared/expression_model.h new file mode 100644 index 000000000..be30cb7fb --- /dev/null +++ b/apps/shared/expression_model.h @@ -0,0 +1,39 @@ +#ifndef SHARED_EXPRESSION_MODEL_H +#define SHARED_EXPRESSION_MODEL_H + +#include +#include +#include + +namespace Shared { + +class ExpressionModel { +public: + ExpressionModel(); + virtual ~ExpressionModel(); // Delete expression and layout, if needed + ExpressionModel& operator=(const ExpressionModel& other); + ExpressionModel& operator=(ExpressionModel&& other) = delete; + ExpressionModel(const ExpressionModel& other) = delete; + ExpressionModel(ExpressionModel&& other) = delete; + const char * text() const; + Poincare::Expression * expression(Poincare::Context * context) const; + Poincare::ExpressionLayout * layout(); + virtual bool isDefined(); + virtual bool isEmpty(); + virtual bool shouldBeClearedBeforeRemove() { + return !isEmpty(); + } + virtual void setContent(const char * c); + virtual void tidy(); + constexpr static int k_expressionBufferSize = TextField::maxBufferSize(); +private: + constexpr static size_t k_dataLengthInBytes = (TextField::maxBufferSize())*sizeof(char); + static_assert((k_dataLengthInBytes & 0x3) == 0, "The expression model data size is not a multiple of 4 bytes (cannot compute crc)"); // Assert that dataLengthInBytes is a multiple of 4 + char m_text[k_expressionBufferSize]; + mutable Poincare::Expression * m_expression; + mutable Poincare::ExpressionLayout * m_layout; +}; + +} + +#endif diff --git a/apps/shared/expression_model_list_controller.cpp b/apps/shared/expression_model_list_controller.cpp new file mode 100644 index 000000000..77b374454 --- /dev/null +++ b/apps/shared/expression_model_list_controller.cpp @@ -0,0 +1,133 @@ +#include "expression_model_list_controller.h" +#include + +namespace Shared { + +ExpressionModelListController::ExpressionModelListController(Responder * parentResponder, I18n::Message text) : + DynamicViewController(parentResponder), + m_addNewMessage(text), + m_addNewModel(nullptr) +{ +} + +/* Table Data Source */ +int ExpressionModelListController::numberOfExpressionRows() { + if (modelStore()->numberOfModels() == modelStore()->maxNumberOfModels()) { + return modelStore()->numberOfModels(); + } + return 1 + modelStore()->numberOfModels(); +} + +KDCoordinate ExpressionModelListController::expressionRowHeight(int j) { + if (isAddEmptyRow(j)) { + return Metric::StoreRowHeight; + } + ExpressionModel * m = modelStore()->modelAtIndex(j); + if (m->layout() == nullptr) { + return Metric::StoreRowHeight; + } + KDCoordinate modelSize = m->layout()->size().height(); + return modelSize + Metric::StoreRowHeight - KDText::charSize().height(); +} + +void ExpressionModelListController::willDisplayExpressionCellAtIndex(HighlightCell * cell, int j) { + EvenOddExpressionCell * myCell = (EvenOddExpressionCell *)cell; + ExpressionModel * m = modelStore()->modelAtIndex(j); + myCell->setExpressionLayout(m->layout()); +} + +/* Responder */ + +bool ExpressionModelListController::handleEventOnExpression(Ion::Events::Event event) { + if (selectedRow() < 0) { + return false; + } + if (event == Ion::Events::OK || event == Ion::Events::EXE) { + if (isAddEmptyRow(selectedRow())) { + addEmptyModel(); + return true; + } + ExpressionModel * model = modelStore()->modelAtIndex(modelIndexForRow(selectedRow())); + editExpression(model, event); + return true; + } + if (event == Ion::Events::Backspace && !isAddEmptyRow(selectedRow())) { + ExpressionModel * model = modelStore()->modelAtIndex(modelIndexForRow(selectedRow())); + if (model->shouldBeClearedBeforeRemove()) { + reinitExpression(model); + } else { + if (removeModelRow(model)) { + int newSelectedRow = selectedRow() >= numberOfExpressionRows() ? numberOfExpressionRows()-1 : selectedRow(); + selectCellAtLocation(selectedColumn(), newSelectedRow); + selectableTableView()->reloadData(); + } + } + return true; + } + if ((event.hasText() || event == Ion::Events::XNT || event == Ion::Events::Paste || event == Ion::Events::Toolbox || event == Ion::Events::Var) + && !isAddEmptyRow(selectedRow())) { + ExpressionModel * model = modelStore()->modelAtIndex(modelIndexForRow(selectedRow())); + editExpression(model, event); + return true; + } + return false; +} + +void ExpressionModelListController::addEmptyModel() { + ExpressionModel * e = modelStore()->addEmptyModel(); + selectableTableView()->reloadData(); + editExpression(e, Ion::Events::OK); +} + +void ExpressionModelListController::reinitExpression(ExpressionModel * model) { + model->setContent(""); + selectableTableView()->reloadData(); +} + + +void ExpressionModelListController::editExpression(ExpressionModel * model, Ion::Events::Event event) { + char * initialText = nullptr; + char initialTextContent[TextField::maxBufferSize()]; + if (event == Ion::Events::OK || event == Ion::Events::EXE) { + strlcpy(initialTextContent, model->text(), sizeof(initialTextContent)); + initialText = initialTextContent; + } + inputController()->edit(this, event, model, initialText, + [](void * context, void * sender){ + ExpressionModel * myModel = static_cast(context); + InputViewController * myInputViewController = (InputViewController *)sender; + const char * textBody = myInputViewController->textBody(); + myModel->setContent(textBody); + }, + [](void * context, void * sender){ + }); +} + +bool ExpressionModelListController::removeModelRow(ExpressionModel * model) { + modelStore()->removeModel(model); + return true; +} + +int ExpressionModelListController::modelIndexForRow(int j) { + return j; +} + +bool ExpressionModelListController::isAddEmptyRow(int j) { + return j == modelStore()->numberOfModels(); +} + +SelectableTableView * ExpressionModelListController::selectableTableView() { + return (SelectableTableView *)view(); +} + +void ExpressionModelListController::loadAddModelCell() { + m_addNewModel = new EvenOddMessageTextCell(); + m_addNewModel->setMessage(m_addNewMessage); +} + +void ExpressionModelListController::unloadAddModelCell() { + delete m_addNewModel; + m_addNewModel = nullptr; +} + +} diff --git a/apps/shared/expression_model_list_controller.h b/apps/shared/expression_model_list_controller.h new file mode 100644 index 000000000..bd8385cad --- /dev/null +++ b/apps/shared/expression_model_list_controller.h @@ -0,0 +1,39 @@ +#ifndef SHARED_EXPRESSION_MODEL_LIST_CONTROLLER_H +#define SHARED_EXPRESSION_MODEL_LIST_CONTROLLER_H + +#include +#include "expression_model_store.h" +#include "../i18n.h" + +namespace Shared { + +class ExpressionModelListController : public DynamicViewController, public SelectableTableViewDataSource { +public: + ExpressionModelListController(Responder * parentResponder, I18n::Message text); +protected: + static constexpr KDCoordinate k_expressionMargin = 5; + /* Table Data Source */ + virtual int numberOfExpressionRows(); + virtual KDCoordinate expressionRowHeight(int j); + virtual void willDisplayExpressionCellAtIndex(HighlightCell * cell, int j); + /* Responder */ + bool handleEventOnExpression(Ion::Events::Event event); + virtual void addEmptyModel(); + virtual void reinitExpression(ExpressionModel * model); + virtual void editExpression(ExpressionModel * model, Ion::Events::Event event); + virtual bool removeModelRow(ExpressionModel * function); + virtual int modelIndexForRow(int j); + virtual bool isAddEmptyRow(int j); + /* Dynamic View Controller */ + virtual SelectableTableView * selectableTableView(); + void loadAddModelCell(); + void unloadAddModelCell(); + virtual ExpressionModelStore * modelStore() = 0; + virtual InputViewController * inputController() = 0; + I18n::Message m_addNewMessage; + EvenOddMessageTextCell * m_addNewModel; +}; + +} + +#endif diff --git a/apps/shared/expression_model_store.cpp b/apps/shared/expression_model_store.cpp new file mode 100644 index 000000000..02e98fba6 --- /dev/null +++ b/apps/shared/expression_model_store.cpp @@ -0,0 +1,69 @@ +#include "expression_model_store.h" +#include "function.h" +#include + +namespace Shared { + +ExpressionModelStore::ExpressionModelStore() : + m_numberOfModels(0) +{ +} + +ExpressionModel * ExpressionModelStore::addEmptyModel() { + assert(m_numberOfModels < maxNumberOfModels()); + setModelAtIndex(emptyModel(), m_numberOfModels++); + return modelAtIndex(m_numberOfModels-1); +} + +void ExpressionModelStore::removeModel(ExpressionModel * f) { + int i = 0; + while (modelAtIndex(i) != f && i < m_numberOfModels) { + i++; + } + assert(i>=0 && i=0 && iisDefined()) { + if (i == index) { + return modelAtIndex(k); + } + index++; + } + } + assert(false); + return nullptr; +} + +int ExpressionModelStore::numberOfDefinedModels() { + int result = 0; + for (int i = 0; i < m_numberOfModels; i++) { + if (modelAtIndex(i)->isDefined()) { + result++; + } + } + return result; +} + +void ExpressionModelStore::tidy() { + for (int i = 0; i < m_numberOfModels; i++) { + modelAtIndex(i)->tidy(); + } +} + +} diff --git a/apps/shared/expression_model_store.h b/apps/shared/expression_model_store.h new file mode 100644 index 000000000..e090cc19d --- /dev/null +++ b/apps/shared/expression_model_store.h @@ -0,0 +1,33 @@ +#ifndef SHARED_EXPRESSION_MODEL_STORE_H +#define SHARED_EXPRESSION_MODEL_STORE_H + +#include "expression_model.h" +#include + +namespace Shared { + +/* ExpressionModelStore is a dumb class. + * Its only job is to store model */ + +class ExpressionModelStore { +public: + ExpressionModelStore(); + virtual ExpressionModel * modelAtIndex(int i) = 0; + ExpressionModel * addEmptyModel(); + void removeModel(ExpressionModel * f); + virtual void removeAll(); + int numberOfModels() const { return m_numberOfModels; }; + virtual ExpressionModel * definedModelAtIndex(int i); + int numberOfDefinedModels(); + virtual int maxNumberOfModels() const = 0; + virtual void tidy(); +protected: + virtual ExpressionModel * emptyModel() = 0; + virtual ExpressionModel * nullModel() = 0; + virtual void setModelAtIndex(ExpressionModel * f, int i) = 0; + int m_numberOfModels; +}; + +} + +#endif diff --git a/apps/shared/function.cpp b/apps/shared/function.cpp index 7f612856d..584f83e03 100644 --- a/apps/shared/function.cpp +++ b/apps/shared/function.cpp @@ -8,89 +8,29 @@ using namespace Poincare; namespace Shared { Function::Function(const char * name, KDColor color) : - m_expression(nullptr), - m_text{0}, + ExpressionModel(), m_name(name), m_color(color), - m_layout(nullptr), m_active(true) { } -Function& Function::operator=(const Function& other) { - // Self-assignment is benign - m_color = other.m_color; - m_name = other.m_name; - m_active = other.m_active; - setContent(other.m_text); - return *this; -} - uint32_t Function::checksum() { char data[k_dataLengthInBytes/sizeof(char)] = {}; - strlcpy(data, m_text, TextField::maxBufferSize()); + strlcpy(data, text(), TextField::maxBufferSize()); data[k_dataLengthInBytes-2] = m_name != nullptr ? m_name[0] : 0; data[k_dataLengthInBytes-1] = m_active ? 1 : 0; return Ion::crc32((uint32_t *)data, k_dataLengthInBytes/sizeof(uint32_t)); } -void Function::setContent(const char * c) { - strlcpy(m_text, c, sizeof(m_text)); - if (m_layout != nullptr) { - delete m_layout; - m_layout = nullptr; - } - if (m_expression != nullptr) { - delete m_expression; - m_expression = nullptr; - } -} - void Function::setColor(KDColor color) { m_color = color; } -Function::~Function() { - if (m_layout != nullptr) { - delete m_layout; - m_layout = nullptr; - } - if (m_expression != nullptr) { - delete m_expression; - m_expression = nullptr; - } -} - -const char * Function::text() const { - return m_text; -} - const char * Function::name() const { return m_name; } -Poincare::Expression * Function::expression(Poincare::Context * context) const { - if (m_expression == nullptr) { - m_expression = Expression::ParseAndSimplify(m_text, *context); - } - return m_expression; -} - -Poincare::ExpressionLayout * Function::layout() { - if (m_layout == nullptr) { - Expression * nonSimplifiedExpression = Expression::parse(m_text); - if (nonSimplifiedExpression != nullptr) { - m_layout = nonSimplifiedExpression->createLayout(PrintFloat::Mode::Decimal); - delete nonSimplifiedExpression; - } - } - return m_layout; -} - -bool Function::isDefined() { - return m_text[0] != 0; -} - bool Function::isActive() { return m_active; } @@ -99,28 +39,9 @@ void Function::setActive(bool active) { m_active = active; } -bool Function::isEmpty() { - return m_text[0] == 0; -} - template T Function::templatedApproximateAtAbscissa(T x, Poincare::Context * context) const { - Poincare::VariableContext variableContext = Poincare::VariableContext(symbol(), context); - Poincare::Symbol xSymbol(symbol()); - Poincare::Complex e = Poincare::Complex::Float(x); - variableContext.setExpressionForSymbolName(&e, &xSymbol, variableContext); - return expression(context)->approximateToScalar(variableContext); -} - -void Function::tidy() { - if (m_layout != nullptr) { - delete m_layout; - m_layout = nullptr; - } - if (m_expression != nullptr) { - delete m_expression; - m_expression = nullptr; - } + return expression(context)->approximateWithValueForSymbol(symbol(), x, *context); } } diff --git a/apps/shared/function.h b/apps/shared/function.h index a750ff36d..74e83e418 100644 --- a/apps/shared/function.h +++ b/apps/shared/function.h @@ -1,31 +1,18 @@ #ifndef SHARED_FUNCTION_H #define SHARED_FUNCTION_H -#include -#include -#include +#include "expression_model.h" namespace Shared { -class Function { +class Function : public ExpressionModel { public: Function(const char * name = nullptr, KDColor color = KDColorBlack); - virtual ~Function(); // Delete expression and layout, if needed - Function& operator=(const Function& other); - Function& operator=(Function&& other) = delete; - Function(const Function& other) = delete; - Function(Function&& other) = delete; virtual uint32_t checksum(); - const char * text() const; const char * name() const; KDColor color() const { return m_color; } - Poincare::Expression * expression(Poincare::Context * context) const; - Poincare::ExpressionLayout * layout(); - virtual bool isDefined(); bool isActive(); void setActive(bool active); - virtual bool isEmpty(); - virtual void setContent(const char * c); void setColor(KDColor m_color); virtual float evaluateAtAbscissa(float x, Poincare::Context * context) const { return templatedApproximateAtAbscissa(x, context); @@ -34,17 +21,13 @@ public: return templatedApproximateAtAbscissa(x, context); } virtual double sumBetweenBounds(double start, double end, Poincare::Context * context) const = 0; - virtual void tidy(); private: constexpr static size_t k_dataLengthInBytes = (TextField::maxBufferSize()+2)*sizeof(char)+2; static_assert((k_dataLengthInBytes & 0x3) == 0, "The function data size is not a multiple of 4 bytes (cannot compute crc)"); // Assert that dataLengthInBytes is a multiple of 4 template T templatedApproximateAtAbscissa(T x, Poincare::Context * context) const; virtual char symbol() const = 0; - mutable Poincare::Expression * m_expression; - char m_text[TextField::maxBufferSize()]; const char * m_name; KDColor m_color; - Poincare::ExpressionLayout * m_layout; bool m_active; }; diff --git a/apps/shared/function_expression_cell.cpp b/apps/shared/function_expression_cell.cpp index c9345ee71..af4f4138d 100644 --- a/apps/shared/function_expression_cell.cpp +++ b/apps/shared/function_expression_cell.cpp @@ -1,58 +1,28 @@ #include "function_expression_cell.h" -#include - -using namespace Poincare; namespace Shared { FunctionExpressionCell::FunctionExpressionCell() : - EvenOddCell(), - m_expressionView() + EvenOddExpressionCell() { } -void FunctionExpressionCell::setExpressionLayout(ExpressionLayout * expressionLayout) { - m_expressionView.setExpressionLayout(expressionLayout); -} - -void FunctionExpressionCell::setTextColor(KDColor textColor) { - m_expressionView.setTextColor(textColor); -} - -void FunctionExpressionCell::setEven(bool even) { - EvenOddCell::setEven(even); - m_expressionView.setBackgroundColor(backgroundColor()); -} - -void FunctionExpressionCell::setHighlighted(bool highlight) { - if (highlight != EvenOddCell::isHighlighted()) { - EvenOddCell::setHighlighted(highlight); - m_expressionView.setBackgroundColor(backgroundColor()); - } -} - -int FunctionExpressionCell::numberOfSubviews() const { - return 1; -} - -View * FunctionExpressionCell::subviewAtIndex(int index) { - assert(index == 0); - return &m_expressionView; -} - -void FunctionExpressionCell::layoutSubviews() { - KDRect expressionFrame(k_separatorThickness+k_margin, 0, bounds().width() - k_separatorThickness-k_margin, bounds().height()-k_separatorThickness); - m_expressionView.setFrame(expressionFrame); +KDSize FunctionExpressionCell::minimalSizeForOptimalDisplay() const { + KDSize expressionSize = m_expressionView.minimalSizeForOptimalDisplay(); + return KDSize(m_margin+expressionSize.width(), expressionSize.height()+k_separatorThickness); } void FunctionExpressionCell::drawRect(KDContext * ctx, KDRect rect) const { KDColor separatorColor = m_even ? Palette::WallScreen : KDColorWhite; - // Color the vertical separator - ctx->fillRect(KDRect(0, 0, k_separatorThickness, bounds().height()), Palette::GreyBright); // Color the horizontal separator - ctx->fillRect(KDRect(k_separatorThickness, bounds().height()-k_separatorThickness, bounds().width()-k_separatorThickness, k_separatorThickness), separatorColor); + ctx->fillRect(KDRect(0, bounds().height()-k_separatorThickness, bounds().width(), k_separatorThickness), separatorColor); // Color the margin - ctx->fillRect(KDRect(k_separatorThickness, 0, k_margin, bounds().height()-k_separatorThickness), backgroundColor()); + ctx->fillRect(KDRect(0, 0, m_margin, bounds().height()-k_separatorThickness), backgroundColor()); +} + +void FunctionExpressionCell::layoutSubviews() { + KDRect expressionFrame(m_margin, 0, bounds().width() - m_margin, bounds().height()-k_separatorThickness); + m_expressionView.setFrame(expressionFrame); } } diff --git a/apps/shared/function_expression_cell.h b/apps/shared/function_expression_cell.h index 351bbe360..203e3cb82 100644 --- a/apps/shared/function_expression_cell.h +++ b/apps/shared/function_expression_cell.h @@ -2,25 +2,17 @@ #define SHARED_FUNCTION_EXPRESSION_CELL_H #include -#include "function.h" namespace Shared { -class FunctionExpressionCell : public EvenOddCell { +class FunctionExpressionCell : public EvenOddExpressionCell { public: FunctionExpressionCell(); - void setExpressionLayout(Poincare::ExpressionLayout * expressionLayout); - void setTextColor(KDColor color); - void setEven(bool even) override; - void setHighlighted(bool highlight) override; - int numberOfSubviews() const override; - View * subviewAtIndex(int index) override; - void layoutSubviews() override; + KDSize minimalSizeForOptimalDisplay() const override; void drawRect(KDContext * ctx, KDRect rect) const override; + void layoutSubviews() override; private: constexpr static KDCoordinate k_separatorThickness = 1; - constexpr static KDCoordinate k_margin = 5; - ExpressionView m_expressionView; }; } diff --git a/apps/shared/function_list_controller.cpp b/apps/shared/function_list_controller.cpp new file mode 100644 index 000000000..2568d19f4 --- /dev/null +++ b/apps/shared/function_list_controller.cpp @@ -0,0 +1,232 @@ +#include "function_list_controller.h" +#include + +namespace Shared { + +FunctionListController::FunctionListController(Responder * parentResponder, FunctionStore * functionStore, ButtonRowController * header, ButtonRowController * footer, I18n::Message text) : + ExpressionModelListController(parentResponder, text), + ButtonRowDelegate(header, footer), + m_functionStore(functionStore), + m_emptyCell(nullptr), + m_plotButton(this, I18n::Message::Plot, Invocation([](void * context, void * sender) { + FunctionListController * list = (FunctionListController *)context; + TabViewController * tabController = list->tabController(); + tabController->setActiveTab(1); + }, this), KDText::FontSize::Small, Palette::PurpleBright), + m_valuesButton(this, I18n::Message::DisplayValues, Invocation([](void * context, void * sender) { + FunctionListController * list = (FunctionListController *)context; + TabViewController * tabController = list->tabController(); + tabController->setActiveTab(2); + }, this), KDText::FontSize::Small, Palette::PurpleBright) +{ +} + +int FunctionListController::numberOfColumns() { + return 2; +}; + +KDCoordinate FunctionListController::columnWidth(int i) { + switch (i) { + case 0: + return k_functionNameWidth; + case 1: + return selectableTableView()->bounds().width()-k_functionNameWidth; + default: + assert(false); + return 0; + } +} + +KDCoordinate FunctionListController::cumulatedWidthFromIndex(int i) { + switch (i) { + case 0: + return 0; + case 1: + return k_functionNameWidth; + case 2: + return selectableTableView()->bounds().width(); + default: + assert(false); + return 0; + } +} + +int FunctionListController::indexFromCumulatedWidth(KDCoordinate offsetX) { + if (offsetX <= k_functionNameWidth) { + return 0; + } else { + if (offsetX <= selectableTableView()->bounds().width()) + return 1; + else { + return 2; + } + } +} + +int FunctionListController::typeAtLocation(int i, int j) { + if (isAddEmptyRow(j)){ + return i + 2; + } + return i; +} + +HighlightCell * FunctionListController::reusableCell(int index, int type) { + assert(index >= 0); + assert(index < maxNumberOfRows()); + switch (type) { + case 0: + return titleCells(index); + case 1: + return expressionCells(index); + case 2: + return m_emptyCell; + case 3: + return m_addNewModel; + default: + assert(false); + return nullptr; + } +} + +int FunctionListController::reusableCellCount(int type) { + if (type > 1) { + return 1; + } + return maxNumberOfRows(); +} + +void FunctionListController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { + if (!isAddEmptyRow(j)) { + if (i == 0) { + willDisplayTitleCellAtIndex(cell, j); + } else { + willDisplayExpressionCellAtIndex(cell, j); + } + } + EvenOddCell * myCell = (EvenOddCell *)cell; + myCell->setEven(j%2 == 0); + myCell->setHighlighted(i == selectedColumn() && j == selectedRow()); + myCell->reloadCell(); +} + +int FunctionListController::numberOfButtons(ButtonRowController::Position position) const { + if (position == ButtonRowController::Position::Bottom) { + return 2; + } + return 0; +} + +Button * FunctionListController::buttonAtIndex(int index, ButtonRowController::Position position) const { + if (position == ButtonRowController::Position::Top) { + return nullptr; + } + const Button * buttons[2] = {&m_plotButton, &m_valuesButton}; + return (Button *)buttons[index]; +} + +void FunctionListController::didBecomeFirstResponder() { + if (selectedRow() == -1) { + selectCellAtLocation(1, 0); + } else { + selectCellAtLocation(selectedColumn(), selectedRow()); + } + if (selectedRow() >= numberOfRows()) { + selectCellAtLocation(selectedColumn(), numberOfRows()-1); + } + footer()->setSelectedButton(-1); + app()->setFirstResponder(selectableTableView()); +} + +bool FunctionListController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::Up) { + if (selectedRow() == -1) { + footer()->setSelectedButton(-1); + selectableTableView()->selectCellAtLocation(1, numberOfRows()-1); + app()->setFirstResponder(selectableTableView()); + return true; + } + selectableTableView()->deselectTable(); + assert(selectedRow() == -1); + app()->setFirstResponder(tabController()); + return true; + } + if (event == Ion::Events::Down) { + if (selectedRow() == -1) { + return false; + } + selectableTableView()->deselectTable(); + footer()->setSelectedButton(0); + return true; + } + if (selectedRow() < 0) { + return false; + } + if (selectedColumn() == 1) { + return handleEventOnExpression(event); + } + if (event == Ion::Events::OK || event == Ion::Events::EXE) { + assert(selectedColumn() == 0); + configureFunction(m_functionStore->modelAtIndex(modelIndexForRow(selectedRow()))); + return true; + } + if (event == Ion::Events::Backspace) { + Shared::Function * function = m_functionStore->modelAtIndex(modelIndexForRow(selectedRow())); + if (removeModelRow(function)) { + int newSelectedRow = selectedRow() >= numberOfRows() ? numberOfRows()-1 : selectedRow(); + selectCellAtLocation(selectedColumn(), newSelectedRow); + selectableTableView()->reloadData(); + } + return true; + } + return false; +} + +void FunctionListController::didEnterResponderChain(Responder * previousFirstResponder) { + selectableTableView()->reloadData(); +} + +void FunctionListController::willExitResponderChain(Responder * nextFirstResponder) { + if (nextFirstResponder == tabController()) { + selectableTableView()->deselectTable(); + footer()->setSelectedButton(-1); + } +} + +void FunctionListController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { + if (isAddEmptyRow(selectedRow()) && selectedColumn() == 0) { + t->selectCellAtLocation(1, numberOfRows()-1); + } +} + +StackViewController * FunctionListController::stackController() const{ + return (StackViewController *)(parentResponder()->parentResponder()->parentResponder()); +} + +void FunctionListController::configureFunction(Shared::Function * function) { + StackViewController * stack = stackController(); + parameterController()->setFunction(function); + stack->push(parameterController()); +} + +TabViewController * FunctionListController::tabController() const{ + return (TabViewController *)(parentResponder()->parentResponder()->parentResponder()->parentResponder()); +} + +View * FunctionListController::loadView() { + loadAddModelCell(); + m_emptyCell = new EvenOddCell(); + SelectableTableView * selectableTableView = new SelectableTableView(this, this, this, this); + selectableTableView->setMargins(0); + selectableTableView->setVerticalCellOverlap(0); + selectableTableView->setShowsIndicators(false); + return selectableTableView; +} + +void FunctionListController::unloadView(View * view) { + unloadAddModelCell(); + delete m_emptyCell; + m_emptyCell = nullptr; + delete view; +} + +} diff --git a/apps/shared/list_controller.h b/apps/shared/function_list_controller.h similarity index 62% rename from apps/shared/list_controller.h rename to apps/shared/function_list_controller.h index 4a0824f48..52b768d3f 100644 --- a/apps/shared/list_controller.h +++ b/apps/shared/function_list_controller.h @@ -1,59 +1,64 @@ -#ifndef SHARED_LIST_CONTROLLER_H -#define SHARED_LIST_CONTROLLER_H +#ifndef SHARED_FUNCTION_LIST_CONTROLLER_H +#define SHARED_FUNCTION_LIST_CONTROLLER_H #include #include "function_store.h" +#include "function_app.h" #include "list_parameter_controller.h" -#include "new_function_cell.h" +#include "expression_model_list_controller.h" #include "../i18n.h" namespace Shared { -class ListController : public DynamicViewController, public ButtonRowDelegate, public TableViewDataSource, public SelectableTableViewDataSource, public SelectableTableViewDelegate { +class FunctionListController : public ExpressionModelListController, public ButtonRowDelegate, public TableViewDataSource, public SelectableTableViewDelegate { public: - ListController(Responder * parentResponder, FunctionStore * functionStore, ButtonRowController * header, ButtonRowController * footer, I18n::Message text); + FunctionListController(Responder * parentResponder, FunctionStore * functionStore, ButtonRowController * header, ButtonRowController * footer, I18n::Message text); + + /* TableViewDataSource */ + int numberOfRows() override { + return numberOfExpressionRows(); + } int numberOfColumns() override; + KDCoordinate rowHeight(int j) override { + return expressionRowHeight(j); + } KDCoordinate columnWidth(int i) override; KDCoordinate cumulatedWidthFromIndex(int i) override; - KDCoordinate cumulatedHeightFromIndex(int j) override; int indexFromCumulatedWidth(KDCoordinate offsetX) override; - int indexFromCumulatedHeight(KDCoordinate offsetY) override; int typeAtLocation(int i, int j) override; HighlightCell * reusableCell(int index, int type) override; int reusableCellCount(int type) override; void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; + /* ButtonRowDelegate */ int numberOfButtons(ButtonRowController::Position position) const override; Button * buttonAtIndex(int index, ButtonRowController::Position position) const override; + /* Responder */ void didBecomeFirstResponder() override; bool handleEvent(Ion::Events::Event event) override; void didEnterResponderChain(Responder * previousFirstResponder) override; void willExitResponderChain(Responder * nextFirstResponder) override; + /* SelectableTableViewDelegate*/ void tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) override; protected: StackViewController * stackController() const; void configureFunction(Function * function); - virtual void reinitExpression(Function * function); - SelectableTableView * selectableTableView(); View * loadView() override; void unloadView(View * view) override; FunctionStore * m_functionStore; private: static constexpr KDCoordinate k_functionNameWidth = 65; TabViewController * tabController() const; - virtual int functionIndexForRow(int j); - virtual const char * textForRow(int j); - virtual void addEmptyFunction(); - virtual bool removeFunctionRow(Function * function) = 0; - virtual void editExpression(Function * function, Ion::Events::Event event) = 0; + ExpressionModelStore * modelStore() override { return m_functionStore; } + InputViewController * inputController() override { + FunctionApp * myApp = static_cast(app()); + return myApp->inputViewController(); + } virtual ListParameterController * parameterController() = 0; virtual int maxNumberOfRows() = 0; virtual HighlightCell * titleCells(int index) = 0; virtual HighlightCell * expressionCells(int index) = 0; virtual void willDisplayTitleCellAtIndex(HighlightCell * cell, int j) = 0; - virtual void willDisplayExpressionCellAtIndex(HighlightCell * cell, int j) = 0; EvenOddCell * m_emptyCell; - I18n::Message m_addNewMessage; - NewFunctionCell * m_addNewFunction; Button m_plotButton; Button m_valuesButton; }; diff --git a/apps/shared/function_store.cpp b/apps/shared/function_store.cpp index 0f7894076..d3cc63cbf 100644 --- a/apps/shared/function_store.cpp +++ b/apps/shared/function_store.cpp @@ -4,15 +4,15 @@ namespace Shared { FunctionStore::FunctionStore() : - m_numberOfFunctions(0) + ExpressionModelStore() { } Function * FunctionStore::activeFunctionAtIndex(int i) { - assert(i>=0 && i=0 && iisActive() && function->isDefined()) { if (i == index) { return function; @@ -24,29 +24,10 @@ Function * FunctionStore::activeFunctionAtIndex(int i) { return nullptr; } -Function * FunctionStore::definedFunctionAtIndex(int i) { - assert(i>=0 && iisDefined()) { - if (i == index) { - return functionAtIndex(k); - } - index++; - } - } - assert(false); - return nullptr; -} - -int FunctionStore::numberOfFunctions() { - return m_numberOfFunctions; -} - int FunctionStore::numberOfActiveFunctions() { int result = 0; - for (int i = 0; i < m_numberOfFunctions; i++) { - Function * function = functionAtIndex(i); + for (int i = 0; i < m_numberOfModels; i++) { + Function * function = modelAtIndex(i); if (function->isDefined() && function->isActive()) { result++; } @@ -54,20 +35,24 @@ int FunctionStore::numberOfActiveFunctions() { return result; } -int FunctionStore::numberOfDefinedFunctions() { - int result = 0; - for (int i = 0; i < m_numberOfFunctions; i++) { - if (functionAtIndex(i)->isDefined()) { - result++; +template +T FunctionStore::firstAvailableAttribute(T attributes[], AttributeGetter attribute) { + for (int k = 0; k < maxNumberOfModels(); k++) { + int j = 0; + while (j < m_numberOfModels) { + if (attribute(modelAtIndex(j)) == attributes[k]) { + break; + } + j++; + } + if (j == m_numberOfModels) { + return attributes[k]; } } - return result; -} - -void FunctionStore::tidy() { - for (int i = 0; i < m_numberOfFunctions; i++) { - functionAtIndex(i)->tidy(); - } + return attributes[0]; } } + +template char const* const Shared::FunctionStore::firstAvailableAttribute(char const* const*, char const* const (*)(Shared::Function*)); +template KDColor const Shared::FunctionStore::firstAvailableAttribute(KDColor const*, KDColor const (*)(Shared::Function*)); diff --git a/apps/shared/function_store.h b/apps/shared/function_store.h index 93d1c2397..1637804a5 100644 --- a/apps/shared/function_store.h +++ b/apps/shared/function_store.h @@ -2,6 +2,7 @@ #define SHARED_FUNCTION_STORE_H #include "function.h" +#include "expression_model_store.h" #include namespace Shared { @@ -9,26 +10,21 @@ namespace Shared { /* FunctionStore is a dumb class. * Its only job is to store functions and to give them a color. */ -class FunctionStore { +class FunctionStore : public ExpressionModelStore { public: FunctionStore(); virtual uint32_t storeChecksum() = 0; - virtual Function * functionAtIndex(int i) = 0; + virtual Function * modelAtIndex(int i) override = 0; virtual Function * activeFunctionAtIndex(int i); - virtual Function * definedFunctionAtIndex(int i); - virtual Function * addEmptyFunction() = 0; - virtual void removeFunction(Function * f) = 0; - virtual void removeAll() = 0; - int numberOfFunctions(); - // Functions can be undefined when they have a color and a name but no content - int numberOfDefinedFunctions(); + virtual Function * definedFunctionAtIndex(int i) { return static_cast(definedModelAtIndex(i)); } // An active function must be defined to be counted int numberOfActiveFunctions(); - virtual int maxNumberOfFunctions() = 0; virtual char symbol() const = 0; - void tidy(); protected: - int m_numberOfFunctions; + static const char * const name(Shared::Function * f) { return f->name(); } + static KDColor const color(Shared::Function * f) { return f->color(); } + template using AttributeGetter = T (*)(Function * f); + template T firstAvailableAttribute(T attributes[], AttributeGetter attribute); private: virtual const char * firstAvailableName() = 0; virtual const KDColor firstAvailableColor() = 0; diff --git a/apps/shared/function_title_cell.cpp b/apps/shared/function_title_cell.cpp index 495d8008c..76f57e7ca 100644 --- a/apps/shared/function_title_cell.cpp +++ b/apps/shared/function_title_cell.cpp @@ -17,12 +17,14 @@ void FunctionTitleCell::setColor(KDColor color) { void FunctionTitleCell::drawRect(KDContext * ctx, KDRect rect) const { if (m_orientation == Orientation::VerticalIndicator){ ctx->fillRect(KDRect(0, 0, k_colorIndicatorThickness, bounds().height()), m_functionColor); + // Color the vertical separator + ctx->fillRect(KDRect(bounds().width()-k_separatorThickness, 0, k_separatorThickness, bounds().height()), Palette::GreyBright); + KDColor separatorColor = m_even ? Palette::WallScreen : KDColorWhite; + // Color the horizontal separator + ctx->fillRect(KDRect(k_colorIndicatorThickness, bounds().height()-k_separatorThickness, bounds().width()-k_colorIndicatorThickness-k_separatorThickness, k_separatorThickness), separatorColor); } else { ctx->fillRect(KDRect(0, 0, bounds().width(), k_colorIndicatorThickness), m_functionColor); } - KDColor separatorColor = m_even ? Palette::WallScreen : KDColorWhite; - // Color the horizontal separator - ctx->fillRect(KDRect(k_colorIndicatorThickness, bounds().height()-k_separatorThickness, bounds().width()-k_colorIndicatorThickness, k_separatorThickness), separatorColor); } } diff --git a/apps/shared/function_title_cell.h b/apps/shared/function_title_cell.h index eba48301e..427926758 100644 --- a/apps/shared/function_title_cell.h +++ b/apps/shared/function_title_cell.h @@ -1,5 +1,5 @@ -#ifndef SEQUENCE_FUNCTION_TITLE_CELL_H -#define SEQUENCE_FUNCTION_TITLE_CELL_H +#ifndef SHARED_FUNCTION_TITLE_CELL_H +#define SHARED_FUNCTION_TITLE_CELL_H #include diff --git a/apps/shared/list_controller.cpp b/apps/shared/list_controller.cpp deleted file mode 100644 index 7bed4ad5e..000000000 --- a/apps/shared/list_controller.cpp +++ /dev/null @@ -1,313 +0,0 @@ -#include "list_controller.h" -#include - -namespace Shared { - -ListController::ListController(Responder * parentResponder, FunctionStore * functionStore, ButtonRowController * header, ButtonRowController * footer, I18n::Message text) : - DynamicViewController(parentResponder), - ButtonRowDelegate(header, footer), - m_functionStore(functionStore), - m_emptyCell(nullptr), - m_addNewMessage(text), - m_addNewFunction(nullptr), - m_plotButton(this, I18n::Message::Plot, Invocation([](void * context, void * sender) { - ListController * list = (ListController *)context; - TabViewController * tabController = list->tabController(); - tabController->setActiveTab(1); - }, this), KDText::FontSize::Small, Palette::PurpleBright), - m_valuesButton(this, I18n::Message::DisplayValues, Invocation([](void * context, void * sender) { - ListController * list = (ListController *)context; - TabViewController * tabController = list->tabController(); - tabController->setActiveTab(2); - }, this), KDText::FontSize::Small, Palette::PurpleBright) -{ -} - -int ListController::numberOfColumns() { - return 2; -}; - -KDCoordinate ListController::columnWidth(int i) { - switch (i) { - case 0: - return k_functionNameWidth; - case 1: - return selectableTableView()->bounds().width()-k_functionNameWidth; - default: - assert(false); - return 0; - } -} - -KDCoordinate ListController::cumulatedWidthFromIndex(int i) { - switch (i) { - case 0: - return 0; - case 1: - return k_functionNameWidth; - case 2: - return selectableTableView()->bounds().width(); - default: - assert(false); - return 0; - } -} - -KDCoordinate ListController::cumulatedHeightFromIndex(int j) { - int result = 0; - for (int k = 0; k < j; k++) { - result += rowHeight(k); - } - return result; -} - -int ListController::indexFromCumulatedWidth(KDCoordinate offsetX) { - if (offsetX <= k_functionNameWidth) { - return 0; - } else { - if (offsetX <= selectableTableView()->bounds().width()) - return 1; - else { - return 2; - } - } -} - -int ListController::indexFromCumulatedHeight(KDCoordinate offsetY) { - int result = 0; - int j = 0; - while (result < offsetY && j < numberOfRows()) { - result += rowHeight(j++); - } - return (result < offsetY || offsetY == 0) ? j : j - 1; -} - -int ListController::typeAtLocation(int i, int j) { - if (m_functionStore->numberOfFunctions() < m_functionStore->maxNumberOfFunctions() - && j == numberOfRows() - 1) { - return i + 2; - } - return i; -} - -HighlightCell * ListController::reusableCell(int index, int type) { - assert(index >= 0); - assert(index < maxNumberOfRows()); - switch (type) { - case 0: - return titleCells(index); - case 1: - return expressionCells(index); - case 2: - return m_emptyCell; - case 3: - return m_addNewFunction; - default: - assert(false); - return nullptr; - } -} - -int ListController::reusableCellCount(int type) { - if (type > 1) { - return 1; - } - return maxNumberOfRows(); -} - -void ListController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { - if (j < numberOfRows() - 1 || m_functionStore->numberOfFunctions() == m_functionStore->maxNumberOfFunctions()) { - if (i == 0) { - willDisplayTitleCellAtIndex(cell, j); - } else { - willDisplayExpressionCellAtIndex(cell, j); - } - } - EvenOddCell * myCell = (EvenOddCell *)cell; - myCell->setEven(j%2 == 0); - myCell->setHighlighted(i == selectedColumn() && j == selectedRow()); - myCell->reloadCell(); -} - -int ListController::numberOfButtons(ButtonRowController::Position position) const { - if (position == ButtonRowController::Position::Bottom) { - return 2; - } - return 0; -} - -Button * ListController::buttonAtIndex(int index, ButtonRowController::Position position) const { - if (position == ButtonRowController::Position::Top) { - return nullptr; - } - const Button * buttons[2] = {&m_plotButton, &m_valuesButton}; - return (Button *)buttons[index]; -} - -void ListController::didBecomeFirstResponder() { - if (selectedRow() == -1) { - selectCellAtLocation(1, 0); - } else { - selectCellAtLocation(selectedColumn(), selectedRow()); - } - if (selectedRow() >= numberOfRows()) { - selectCellAtLocation(selectedColumn(), numberOfRows()-1); - } - footer()->setSelectedButton(-1); - app()->setFirstResponder(selectableTableView()); -} - -bool ListController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::Up) { - if (selectedRow() == -1) { - footer()->setSelectedButton(-1); - selectableTableView()->selectCellAtLocation(1, numberOfRows()-1); - app()->setFirstResponder(selectableTableView()); - return true; - } - selectableTableView()->deselectTable(); - assert(selectedRow() == -1); - app()->setFirstResponder(tabController()); - return true; - } - if (event == Ion::Events::Down) { - if (selectedRow() == -1) { - return false; - } - selectableTableView()->deselectTable(); - footer()->setSelectedButton(0); - return true; - } - if (event == Ion::Events::OK || event == Ion::Events::EXE) { - switch (selectedColumn()) { - case 0: - { - if (m_functionStore->numberOfFunctions() < m_functionStore->maxNumberOfFunctions() && - selectedRow() == numberOfRows() - 1) { - return true; - } - configureFunction(m_functionStore->functionAtIndex(functionIndexForRow(selectedRow()))); - return true; - } - case 1: - { - if (m_functionStore->numberOfFunctions() < m_functionStore->maxNumberOfFunctions() && - selectedRow() == numberOfRows() - 1) { - addEmptyFunction(); - return true; - } - Shared::Function * function = m_functionStore->functionAtIndex(functionIndexForRow(selectedRow())); - editExpression(function, event); - return true; - } - default: - { - return false; - } - } - } - if (event == Ion::Events::Backspace && selectedRow() >= 0 && - (selectedRow() < numberOfRows() - 1 || m_functionStore->numberOfFunctions() == m_functionStore->maxNumberOfFunctions())) { - Shared::Function * function = m_functionStore->functionAtIndex(functionIndexForRow(selectedRow())); - if (selectedColumn() == 1 && !function->isEmpty()) { - reinitExpression(function); - } else { - if (removeFunctionRow(function)) { - selectCellAtLocation(selectedColumn(), selectedRow()); - if (selectedRow() >= numberOfRows()) { - selectCellAtLocation(selectedColumn(), numberOfRows()-1); - } - selectableTableView()->reloadData(); - } - } - return true; - } - if ((event.hasText() || event == Ion::Events::XNT || event == Ion::Events::Paste || event == Ion::Events::Toolbox || event == Ion::Events::Var) - && selectedColumn() == 1 - && (selectedRow() != numberOfRows() - 1 - || m_functionStore->numberOfFunctions() == m_functionStore->maxNumberOfFunctions())) { - Shared::Function * function = m_functionStore->functionAtIndex(functionIndexForRow(selectedRow())); - editExpression(function, event); - return true; - } - if (event == Ion::Events::Copy && selectedColumn() == 1 && - (selectedRow() < numberOfRows() - 1 || m_functionStore->numberOfFunctions() == m_functionStore->maxNumberOfFunctions())) { - Clipboard::sharedClipboard()->store(textForRow(selectedRow())); - return true; - } - return false; -} - -void ListController::didEnterResponderChain(Responder * previousFirstResponder) { - selectableTableView()->reloadData(); -} - -void ListController::willExitResponderChain(Responder * nextFirstResponder) { - if (nextFirstResponder == tabController()) { - selectableTableView()->deselectTable(); - footer()->setSelectedButton(-1); - } -} - -void ListController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { - if (m_functionStore->numberOfFunctions() < m_functionStore->maxNumberOfFunctions() && selectedRow() == numberOfRows() - 1 && selectedColumn() == 0) { - t->selectCellAtLocation(1, numberOfRows()-1); - } -} - -StackViewController * ListController::stackController() const{ - return (StackViewController *)(parentResponder()->parentResponder()->parentResponder()); -} - -void ListController::configureFunction(Shared::Function * function) { - StackViewController * stack = stackController(); - parameterController()->setFunction(function); - stack->push(parameterController()); -} - -void ListController::reinitExpression(Function * function) { - function->setContent(""); - selectableTableView()->reloadData(); -} - -SelectableTableView * ListController::selectableTableView() { - return (SelectableTableView *)view(); -} - -TabViewController * ListController::tabController() const{ - return (TabViewController *)(parentResponder()->parentResponder()->parentResponder()->parentResponder()); -} - -int ListController::functionIndexForRow(int j) { - return j; -} - -const char * ListController::textForRow(int j) { - Shared::Function * function = m_functionStore->functionAtIndex(functionIndexForRow(j)); - return function->text(); -} - -void ListController::addEmptyFunction() { - m_functionStore->addEmptyFunction(); - selectableTableView()->reloadData(); -} - -View * ListController::loadView() { - m_emptyCell = new EvenOddCell(); - m_addNewFunction = new NewFunctionCell(m_addNewMessage); - SelectableTableView * selectableTableView = new SelectableTableView(this, this, this, this); - selectableTableView->setMargins(0); - selectableTableView->setVerticalCellOverlap(0); - selectableTableView->setShowsIndicators(false); - return selectableTableView; -} - -void ListController::unloadView(View * view) { - delete m_emptyCell; - m_emptyCell = nullptr; - delete m_addNewFunction; - m_addNewFunction = nullptr; - delete view; -} - -} diff --git a/apps/shared/list_parameter_controller.cpp b/apps/shared/list_parameter_controller.cpp index 2ca48879a..1c6ce6b36 100644 --- a/apps/shared/list_parameter_controller.cpp +++ b/apps/shared/list_parameter_controller.cpp @@ -99,13 +99,13 @@ bool ListParameterController::handleEnterOnRow(int rowIndex) { case 1: #endif { - if (m_functionStore->numberOfFunctions() > 1) { - m_functionStore->removeFunction(m_function); + if (m_functionStore->numberOfModels() > 1) { + m_functionStore->removeModel(m_function); StackViewController * stack = (StackViewController *)(parentResponder()); stack->pop(); return true; } else { - if (m_functionStore->numberOfDefinedFunctions() == 1) { + if (m_functionStore->numberOfDefinedModels() == 1) { Function * f = m_functionStore->definedFunctionAtIndex(0); f->setContent(""); StackViewController * stack = (StackViewController *)(parentResponder()); diff --git a/apps/shared/new_function_cell.cpp b/apps/shared/new_function_cell.cpp deleted file mode 100644 index 18387df61..000000000 --- a/apps/shared/new_function_cell.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "new_function_cell.h" -#include "assert.h" - -namespace Shared { - -NewFunctionCell::NewFunctionCell(I18n::Message text) : - EvenOddCell(), - m_messageTextView(KDText::FontSize::Large, text, 0.5f, 0.5f) -{ -} - -void NewFunctionCell::setEven(bool even) { - EvenOddCell::setEven(even); - m_messageTextView.setBackgroundColor(backgroundColor()); -} - -void NewFunctionCell::setHighlighted(bool highlight) { - EvenOddCell::setHighlighted(highlight); - m_messageTextView.setBackgroundColor(backgroundColor()); -} - -int NewFunctionCell::numberOfSubviews() const { - return 1; -} - -View * NewFunctionCell::subviewAtIndex(int index) { - assert(index == 0); - return &m_messageTextView; -} - -void NewFunctionCell::layoutSubviews() { - m_messageTextView.setFrame(bounds()); -} - -} diff --git a/apps/shared/new_function_cell.h b/apps/shared/new_function_cell.h deleted file mode 100644 index 7a8418b6c..000000000 --- a/apps/shared/new_function_cell.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef SHARED_NEW_FUNCTION_CELL_H -#define SHARED_NEW_FUNCTION_CELL_H - -#include - -namespace Shared { - -class NewFunctionCell : public EvenOddCell { -public: - NewFunctionCell(I18n::Message text); - void setEven(bool even) override; - void setHighlighted(bool highlight) override; - int numberOfSubviews() const override; - View * subviewAtIndex(int index) override; - void layoutSubviews() override; -private: - MessageTextView m_messageTextView; -}; - -} - -#endif diff --git a/apps/shared/scrollable_exact_approximate_expressions_cell.cpp b/apps/shared/scrollable_exact_approximate_expressions_cell.cpp new file mode 100644 index 000000000..b590b3381 --- /dev/null +++ b/apps/shared/scrollable_exact_approximate_expressions_cell.cpp @@ -0,0 +1,47 @@ +#include "scrollable_exact_approximate_expressions_cell.h" +#include +using namespace Poincare; + +namespace Shared { + +ScrollableExactApproximateExpressionsCell::ScrollableExactApproximateExpressionsCell(Responder * parentResponder) : + Responder(parentResponder), + m_view(this) +{ +} + +void ScrollableExactApproximateExpressionsCell::setHighlighted(bool highlight) { + m_view.evenOddCell()->setHighlighted(highlight); + reloadScroll(); +} + +void ScrollableExactApproximateExpressionsCell::setEven(bool even) { + EvenOddCell::setEven(even); + m_view.evenOddCell()->setEven(even); +} + +void ScrollableExactApproximateExpressionsCell::reloadCell() { + m_view.evenOddCell()->reloadCell(); +} + +void ScrollableExactApproximateExpressionsCell::reloadScroll() { + m_view.reloadScroll(); +} + +void ScrollableExactApproximateExpressionsCell::didBecomeFirstResponder() { + app()->setFirstResponder(&m_view); +} + +int ScrollableExactApproximateExpressionsCell::numberOfSubviews() const { + return 1; +} + +View * ScrollableExactApproximateExpressionsCell::subviewAtIndex(int index) { + return &m_view; +} + +void ScrollableExactApproximateExpressionsCell::layoutSubviews() { + m_view.setFrame(KDRect(k_margin,k_margin, bounds().width()-2*k_margin, bounds().height()-2*k_margin)); +} + +} diff --git a/apps/shared/scrollable_exact_approximate_expressions_cell.h b/apps/shared/scrollable_exact_approximate_expressions_cell.h new file mode 100644 index 000000000..0c49a48a7 --- /dev/null +++ b/apps/shared/scrollable_exact_approximate_expressions_cell.h @@ -0,0 +1,37 @@ +#ifndef SHARED_SCROLLABLE_EXACT_APPROXIMATE_EXPRESSIONS_CELL_H +#define SHARED_SCROLLABLE_EXACT_APPROXIMATE_EXPRESSIONS_CELL_H + +#include +#include "scrollable_exact_approximate_expressions_view.h" + +namespace Shared { + +class ScrollableExactApproximateExpressionsCell : public ::EvenOddCell, public Responder { +public: + ScrollableExactApproximateExpressionsCell(Responder * parentResponder = nullptr); + void setExpressions(Poincare::ExpressionLayout ** expressionsLayout) { + return m_view.setExpressions(expressionsLayout); + } + void setEqualMessage(I18n::Message equalSignMessage) { + return m_view.setEqualMessage(equalSignMessage); + } + void setHighlighted(bool highlight) override; + void setEven(bool even) override; + void reloadCell() override; + void reloadScroll(); + Responder * responder() override { + return this; + } + Poincare::ExpressionLayout * expressionLayout() const override { return m_view.expressionLayout(); } + void didBecomeFirstResponder() override; + constexpr static KDCoordinate k_margin = 5; +private: + int numberOfSubviews() const override; + View * subviewAtIndex(int index) override; + void layoutSubviews() override; + ScrollableExactApproximateExpressionsView m_view; +}; + +} + +#endif diff --git a/apps/calculation/output_expressions_view.cpp b/apps/shared/scrollable_exact_approximate_expressions_view.cpp similarity index 55% rename from apps/calculation/output_expressions_view.cpp rename to apps/shared/scrollable_exact_approximate_expressions_view.cpp index beefae084..cb352ae6b 100644 --- a/apps/calculation/output_expressions_view.cpp +++ b/apps/shared/scrollable_exact_approximate_expressions_view.cpp @@ -1,31 +1,24 @@ -#include "output_expressions_view.h" -#include "scrollable_output_expressions_view.h" +#include "scrollable_exact_approximate_expressions_view.h" #include "../i18n.h" #include using namespace Poincare; -namespace Calculation { +namespace Shared { -OutputExpressionsView::OutputExpressionsView(Responder * parentResponder) : - Responder(parentResponder), +ScrollableExactApproximateExpressionsView::ContentCell::ContentCell() : m_approximateExpressionView(), m_approximateSign(KDText::FontSize::Large, I18n::Message::AlmostEqual, 0.5f, 0.5f, Palette::GreyVeryDark), m_exactExpressionView(), - m_selectedSubviewType(OutputExpressionsView::SubviewType::ExactOutput) + m_selectedSubviewType((SubviewType)0) { } -void OutputExpressionsView::setExpressions(ExpressionLayout ** expressionsLayout) { - m_approximateExpressionView.setExpressionLayout(expressionsLayout[0]); - m_exactExpressionView.setExpressionLayout(expressionsLayout[1]); - layoutSubviews(); +KDColor ScrollableExactApproximateExpressionsView::ContentCell::backgroundColor() const { + KDColor background = m_even ? KDColorWhite : Palette::WallScreen; + return background; } -void OutputExpressionsView::setEqualMessage(I18n::Message equalSignMessage) { - m_approximateSign.setMessage(equalSignMessage); -} - -void OutputExpressionsView::setHighlighted(bool highlight) { +void ScrollableExactApproximateExpressionsView::ContentCell::setHighlighted(bool highlight) { // Do not call HighlightCell::setHighlighted to avoid marking all cell as dirty m_highlighted = highlight; m_exactExpressionView.setBackgroundColor(backgroundColor()); @@ -39,12 +32,7 @@ void OutputExpressionsView::setHighlighted(bool highlight) { } } -KDColor OutputExpressionsView::backgroundColor() const { - KDColor background = m_even ? Palette::WallScreen : KDColorWhite; - return background; -} - -void OutputExpressionsView::reloadCell() { +void ScrollableExactApproximateExpressionsView::ContentCell::reloadCell() { setHighlighted(isHighlighted()); m_approximateSign.setBackgroundColor(backgroundColor()); if (numberOfSubviews() == 1) { @@ -55,7 +43,7 @@ void OutputExpressionsView::reloadCell() { layoutSubviews(); } -KDSize OutputExpressionsView::minimalSizeForOptimalDisplay() const { +KDSize ScrollableExactApproximateExpressionsView::ContentCell::minimalSizeForOptimalDisplay() const { KDSize approximateExpressionSize = m_approximateExpressionView.minimalSizeForOptimalDisplay(); if (numberOfSubviews() == 1) { return approximateExpressionSize; @@ -68,54 +56,32 @@ KDSize OutputExpressionsView::minimalSizeForOptimalDisplay() const { return KDSize(exactExpressionSize.width()+approximateSignSize.width()+approximateExpressionSize.width()+2*k_digitHorizontalMargin, height); } -void OutputExpressionsView::didBecomeFirstResponder() { - if (numberOfSubviews() == 1) { - setSelectedSubviewType(SubviewType::ApproximativeOutput); - } else { - setSelectedSubviewType(SubviewType::ExactOutput); - } -} - -bool OutputExpressionsView::handleEvent(Ion::Events::Event event) { - if (numberOfSubviews() == 1) { - return false; - } - ScrollableOutputExpressionsView * scrollResponder = static_cast(parentResponder()); - KDCoordinate offset = scrollResponder->manualScrollingOffset().x(); - bool rightExpressionIsVisible = minimalSizeForOptimalDisplay().width() - m_approximateExpressionView.minimalSizeForOptimalDisplay().width() - offset < scrollResponder->bounds().width() -; - bool leftExpressionIsVisible = m_exactExpressionView.minimalSizeForOptimalDisplay().width() - offset < scrollResponder->bounds().width(); - if ((event == Ion::Events::Right && m_selectedSubviewType == SubviewType::ExactOutput && rightExpressionIsVisible) || - (event == Ion::Events::Left && m_selectedSubviewType == SubviewType::ApproximativeOutput && leftExpressionIsVisible)) { - SubviewType otherSubviewType = m_selectedSubviewType == SubviewType::ExactOutput ? SubviewType::ApproximativeOutput : SubviewType::ExactOutput; - setSelectedSubviewType(otherSubviewType); - return true; - } - return false; -} - -OutputExpressionsView::SubviewType OutputExpressionsView::selectedSubviewType() { - return m_selectedSubviewType; -} - -void OutputExpressionsView::setSelectedSubviewType(OutputExpressionsView::SubviewType subviewType) { +void ScrollableExactApproximateExpressionsView::ContentCell::setSelectedSubviewType(ScrollableExactApproximateExpressionsView::SubviewType subviewType) { m_selectedSubviewType = subviewType; setHighlighted(isHighlighted()); } -int OutputExpressionsView::numberOfSubviews() const { +Poincare::ExpressionLayout * ScrollableExactApproximateExpressionsView::ContentCell::expressionLayout() const { + if (m_selectedSubviewType == SubviewType::ExactOutput) { + return m_exactExpressionView.expressionLayout(); + } else { + return m_approximateExpressionView.expressionLayout(); + } +} + +int ScrollableExactApproximateExpressionsView::ContentCell::numberOfSubviews() const { if (m_exactExpressionView.expressionLayout() != nullptr) { return 3; } return 1; } -View * OutputExpressionsView::subviewAtIndex(int index) { +View * ScrollableExactApproximateExpressionsView::ContentCell::subviewAtIndex(int index) { View * views[3] = {&m_approximateExpressionView, &m_approximateSign, &m_exactExpressionView}; return views[index]; } -void OutputExpressionsView::layoutSubviews() { +void ScrollableExactApproximateExpressionsView::ContentCell::layoutSubviews() { KDCoordinate height = bounds().height(); KDSize approximateExpressionSize = m_approximateExpressionView.minimalSizeForOptimalDisplay(); if (numberOfSubviews() == 1) { @@ -132,4 +98,47 @@ void OutputExpressionsView::layoutSubviews() { m_approximateSign.setFrame(KDRect(k_digitHorizontalMargin+exactExpressionSize.width(), baseline-approximateSignSize.height()/2, approximateSignSize)); } +ScrollableExactApproximateExpressionsView::ScrollableExactApproximateExpressionsView(Responder * parentResponder) : + ScrollableView(parentResponder, &m_contentCell, this), + m_contentCell() +{ +} +void ScrollableExactApproximateExpressionsView::setExpressions(ExpressionLayout ** expressionsLayout) { + m_contentCell.approximateExpressionView()->setExpressionLayout(expressionsLayout[0]); + m_contentCell.exactExpressionView()->setExpressionLayout(expressionsLayout[1]); + m_contentCell.layoutSubviews(); +} + +void ScrollableExactApproximateExpressionsView::setEqualMessage(I18n::Message equalSignMessage) { + m_contentCell.approximateSign()->setMessage(equalSignMessage); +} + +void ScrollableExactApproximateExpressionsView::didBecomeFirstResponder() { + if (m_contentCell.exactExpressionView()->expressionLayout() == nullptr) { + setSelectedSubviewType(SubviewType::ApproximativeOutput); + } else { + setSelectedSubviewType(SubviewType::ExactOutput); + } +} + +bool ScrollableExactApproximateExpressionsView::handleEvent(Ion::Events::Event event) { + if (m_contentCell.exactExpressionView()->expressionLayout() == nullptr) { + return ScrollableView::handleEvent(event); + } + bool rightExpressionIsVisible = minimalSizeForOptimalDisplay().width() - m_contentCell.approximateExpressionView()->minimalSizeForOptimalDisplay().width() - m_manualScrollingOffset.x() < bounds().width() +; + bool leftExpressionIsVisible = m_contentCell.exactExpressionView()->minimalSizeForOptimalDisplay().width() - m_manualScrollingOffset.x() > 0; + if ((event == Ion::Events::Right && selectedSubviewType() == SubviewType::ExactOutput && rightExpressionIsVisible) || + (event == Ion::Events::Left && selectedSubviewType() == SubviewType::ApproximativeOutput && leftExpressionIsVisible)) { + SubviewType otherSubviewType = selectedSubviewType() == SubviewType::ExactOutput ? SubviewType::ApproximativeOutput : SubviewType::ExactOutput; + setSelectedSubviewType(otherSubviewType); + return true; + } + return ScrollableView::handleEvent(event); +} + +KDSize ScrollableExactApproximateExpressionsView::minimalSizeForOptimalDisplay() const { + return m_contentCell.minimalSizeForOptimalDisplay(); +} + } diff --git a/apps/shared/scrollable_exact_approximate_expressions_view.h b/apps/shared/scrollable_exact_approximate_expressions_view.h new file mode 100644 index 000000000..5a7081187 --- /dev/null +++ b/apps/shared/scrollable_exact_approximate_expressions_view.h @@ -0,0 +1,69 @@ +#ifndef SHARED_SCROLLABLE_EXACT_APPROXIMATE_EXPRESSIONS_VIEW_H +#define SHARED_SCROLLABLE_EXACT_APPROXIMATE_EXPRESSIONS_VIEW_H + +#include + +namespace Shared { + +class ScrollableExactApproximateExpressionsView : public ScrollableView, public ScrollViewDataSource { +public: + enum class SubviewType { + ExactOutput, + ApproximativeOutput + }; + ScrollableExactApproximateExpressionsView(Responder * parentResponder); + ::EvenOddCell * evenOddCell() { + return &m_contentCell; + } + void setExpressions(Poincare::ExpressionLayout ** expressionsLayout); + void setEqualMessage(I18n::Message equalSignMessage); + SubviewType selectedSubviewType() { + return m_contentCell.selectedSubviewType(); + } + void setSelectedSubviewType(SubviewType subviewType) { + m_contentCell.setSelectedSubviewType(subviewType); + } + void didBecomeFirstResponder() override; + bool handleEvent(Ion::Events::Event event) override; + KDSize minimalSizeForOptimalDisplay() const override; + Poincare::ExpressionLayout * expressionLayout() const { + return m_contentCell.expressionLayout(); + } +private: + class ContentCell : public ::EvenOddCell { + public: + ContentCell(); + KDColor backgroundColor() const override; + void setHighlighted(bool highlight) override; + void reloadCell() override; + KDSize minimalSizeForOptimalDisplay() const override; + ExpressionView * approximateExpressionView() { + return &m_approximateExpressionView; + } + ExpressionView * exactExpressionView() { + return &m_exactExpressionView; + } + MessageTextView * approximateSign() { + return &m_approximateSign; + } + SubviewType selectedSubviewType() { + return m_selectedSubviewType; + } + void setSelectedSubviewType(SubviewType subviewType); + void layoutSubviews() override; + int numberOfSubviews() const override; + Poincare::ExpressionLayout * expressionLayout() const override; + private: + View * subviewAtIndex(int index) override; + constexpr static KDCoordinate k_digitHorizontalMargin = 10; + ExpressionView m_approximateExpressionView; + MessageTextView m_approximateSign; + ExpressionView m_exactExpressionView; + SubviewType m_selectedSubviewType; +}; + ContentCell m_contentCell; +}; + +} + +#endif diff --git a/apps/shared/values_controller.cpp b/apps/shared/values_controller.cpp index 4e29b6b51..71ddd028f 100644 --- a/apps/shared/values_controller.cpp +++ b/apps/shared/values_controller.cpp @@ -85,11 +85,6 @@ bool ValuesController::handleEvent(Ion::Events::Event event) { if (selectedRow() == -1) { return header()->handleEvent(event); } - if (event == Ion::Events::Copy && selectedRow() > 0 && selectedColumn() > 0) { - EvenOddBufferTextCell * cell = (EvenOddBufferTextCell *)selectableTableView()->selectedCell(); - Clipboard::sharedClipboard()->store(cell->text()); - return true; - } return false; } diff --git a/apps/solver/Makefile b/apps/solver/Makefile new file mode 100644 index 000000000..7f637d438 --- /dev/null +++ b/apps/solver/Makefile @@ -0,0 +1,29 @@ +snapshots += Solver::App::Snapshot +snapshot_headers += apps/solver/app.h + +app_objs += $(addprefix apps/solver/,\ + app.o\ + equation_models_parameter_controller.o\ + equation.o\ + equation_list_view.o\ + equation_store.o\ + interval_controller.o\ + list_controller.o\ + solutions_controller.o\ +) + +i18n_files += $(addprefix apps/solver/,\ + base.de.i18n\ + base.en.i18n\ + base.es.i18n\ + base.fr.i18n\ + base.pt.i18n\ +) + +tests += $(addprefix apps/solver/test/,\ + equation_store.cpp\ +) +test_objs += $(addprefix apps/solver/, equation.o equation_store.o) +test_objs += $(addprefix apps/shared/, expression_model.o expression_model_store.o) + +app_images += apps/solver/solver_icon.png diff --git a/apps/solver/app.cpp b/apps/solver/app.cpp new file mode 100644 index 000000000..0bba04e14 --- /dev/null +++ b/apps/solver/app.cpp @@ -0,0 +1,71 @@ +#include "app.h" +#include "../i18n.h" +#include "solver_icon.h" + +using namespace Shared; + +namespace Solver { + +I18n::Message App::Descriptor::name() { + return I18n::Message::SolverApp; +} + +I18n::Message App::Descriptor::upperName() { + return I18n::Message::SolverAppCapital; +} + +const Image * App::Descriptor::icon() { + return ImageStore::SolverIcon; +} + +App::Snapshot::Snapshot() : + m_equationStore() +{ +} + +App * App::Snapshot::unpack(Container * container) { + return new App(container, this); +} + +App::Descriptor * App::Snapshot::descriptor() { + static Descriptor descriptor; + return &descriptor; +} + +void App::Snapshot::reset() { + // Delete all equations + m_equationStore.removeAll(); +} + +void App::Snapshot::tidy() { + // Delete all expressions of equations + m_equationStore.tidy(); +} + +App::App(Container * container, Snapshot * snapshot) : + ExpressionFieldDelegateApp(container, snapshot, &m_inputViewController), + m_solutionsController(&m_alternateEmptyViewController, snapshot->equationStore()), + m_intervalController(nullptr, snapshot->equationStore()), + m_alternateEmptyViewController(nullptr, &m_solutionsController, &m_solutionsController), + m_listController(&m_listFooter, snapshot->equationStore(), &m_listFooter), + m_listFooter(&m_stackViewController, &m_listController, &m_listController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGrey, ButtonRowController::Size::Large), + m_stackViewController(&m_inputViewController, &m_listFooter), + m_inputViewController(&m_modalViewController, &m_stackViewController, &m_listController, &m_listController) +{ +} + +void App::willBecomeInactive() { + if (m_modalViewController.isDisplayingModal()) { + m_modalViewController.dismissModalViewController(); + } + if (inputViewController()->isDisplayingModal()) { + inputViewController()->abortEditionAndDismiss(); + } + ::App::willBecomeInactive(); +} + +const char * App::XNT() { + return "x"; +} + +} diff --git a/apps/solver/app.h b/apps/solver/app.h new file mode 100644 index 000000000..a1cfc313d --- /dev/null +++ b/apps/solver/app.h @@ -0,0 +1,50 @@ +#ifndef SOLVER_SOLVER_APP_H +#define SOLVER_SOLVER_APP_H + +#include +#include "../shared/expression_field_delegate_app.h" +#include "list_controller.h" +#include "equation_store.h" +#include "interval_controller.h" +#include "solutions_controller.h" + +namespace Solver { + +class App : public Shared::ExpressionFieldDelegateApp { +public: + class Descriptor : public ::App::Descriptor { + public: + I18n::Message name() override; + I18n::Message upperName() override; + const Image * icon() override; + }; + class Snapshot : public ::App::Snapshot { + public: + Snapshot(); + App * unpack(Container * container) override; + Descriptor * descriptor() override; + void reset() override; + EquationStore * equationStore() { return &m_equationStore; } + private: + void tidy() override; + EquationStore m_equationStore; + }; + InputViewController * inputViewController() { return &m_inputViewController; } + ViewController * solutionsControllerStack() { return &m_alternateEmptyViewController; } + ViewController * intervalController() { return &m_intervalController; } + void willBecomeInactive() override; + const char * XNT() override; +private: + App(Container * container, Snapshot * snapshot); + SolutionsController m_solutionsController; + IntervalController m_intervalController; + AlternateEmptyViewController m_alternateEmptyViewController; + ListController m_listController; + ButtonRowController m_listFooter; + StackViewController m_stackViewController; + InputViewController m_inputViewController; +}; + +} + +#endif diff --git a/apps/solver/base.de.i18n b/apps/solver/base.de.i18n new file mode 100644 index 000000000..fff3b1db9 --- /dev/null +++ b/apps/solver/base.de.i18n @@ -0,0 +1,21 @@ +SolverApp = "Gleichungen" +SolverAppCapital = "GLEICHUNGEN" +AddEquation = "Gleichung hinzuzufügen" +ResolveEquation = "Lösen der Gleichung" +ResolveSystem = "Lösen des Gleichungssystems" +UseEquationModel = "Verwenden Sie einen Gleichungsmodell" +UndefinedEquation = "Undefinierte Gleichung" +TooManyVariables = "Es gibt zu viele Unbekannte" +NonLinearSystem = "Das System ist nicht linear" +Solution = "Lösung" +ApproximateSolution = "Ungefähre Lösung" +SearchInverval = "Lösungssuche Intervall" +NoSolutionSystem = "Das System hat keine Lösung" +NoSolutionEquation = "Die Gleichung hat keine Lösung" +NoSolutionInterval = "Keine Lösung im Intervall gefunden" +EnterEquation = "Geben Sie eine Gleichung ein" +InfiniteNumberOfSolutions = "Es gibt unendlich viele Lösungen" +ApproximateSolutionIntervalInstruction0= "Geben Sie das Intervall für die Suche" +ApproximateSolutionIntervalInstruction1= "nach einer ungefähren Lösung ein" +OnlyFirstSolutionsDisplayed0 = "Es werden nur die ersten" +OnlyFirstSolutionsDisplayed1 = "10 Lösungen angezeigt." diff --git a/apps/solver/base.en.i18n b/apps/solver/base.en.i18n new file mode 100644 index 000000000..5a22550f4 --- /dev/null +++ b/apps/solver/base.en.i18n @@ -0,0 +1,21 @@ +SolverApp = "Equations" +SolverAppCapital = "EQUATIONS" +AddEquation = "Add equation" +ResolveEquation = "Solve the equation" +ResolveSystem = "Solve the system" +UseEquationModel = "Use an equation template" +UndefinedEquation = "Undefined equation" +TooManyVariables = "There are too many unknowns" +NonLinearSystem = "The system is not linear" +Solution = "Solution" +ApproximateSolution = "Approximate solution" +SearchInverval = "Search interval" +NoSolutionSystem = "The system has no solution" +NoSolutionEquation = "The equation has no solution" +NoSolutionInterval = "No solution found in the interval" +EnterEquation = "Enter an equation" +InfiniteNumberOfSolutions = "There are an infinite number of solutions" +ApproximateSolutionIntervalInstruction0= "Enter the interval to search" +ApproximateSolutionIntervalInstruction1= "for an approximate solution" +OnlyFirstSolutionsDisplayed0 = "Only the first 10 solutions" +OnlyFirstSolutionsDisplayed1 = "are displayed" diff --git a/apps/solver/base.es.i18n b/apps/solver/base.es.i18n new file mode 100644 index 000000000..ec2c46ae7 --- /dev/null +++ b/apps/solver/base.es.i18n @@ -0,0 +1,21 @@ +SolverApp = "Ecuaciones" +SolverAppCapital = "ECUACIONES" +AddEquation = "Agregar una ecuación" +ResolveEquation = "Resolver la ecuación" +ResolveSystem = "Resolver el sistema" +UseEquationModel = "Usar un modelo de ecuación" +UndefinedEquation = "Ecuación indefinida" +TooManyVariables = "Hay demasiadas incógnitas" +NonLinearSystem = "El sistema no es lineal" +Solution = "Solucion" +ApproximateSolution = "Solución aproximada" +SearchInverval = "Intervalo de búsqueda" +NoSolutionSystem = "El sistema no tiene solución" +NoSolutionEquation = "La ecuación no tiene solución" +NoSolutionInterval = "Ninguna solución encontrada en el intervalo" +EnterEquation = "Escribe una ecuación" +InfiniteNumberOfSolutions = "Hay un número infinito de soluciones" +ApproximateSolutionIntervalInstruction0= "Introduzca el intervalo para" +ApproximateSolutionIntervalInstruction1= "buscar una solución aproximada" +OnlyFirstSolutionsDisplayed0 = "Sólo se muestran las" +OnlyFirstSolutionsDisplayed1 = "10 primeras soluciones" diff --git a/apps/solver/base.fr.i18n b/apps/solver/base.fr.i18n new file mode 100644 index 000000000..27b8c67c5 --- /dev/null +++ b/apps/solver/base.fr.i18n @@ -0,0 +1,21 @@ +SolverApp = "Equations" +SolverAppCapital = "EQUATIONS" +AddEquation = "Ajouter une équation" +ResolveEquation = "Résoudre l'équation" +ResolveSystem = "Résoudre le système" +UseEquationModel = "Utiliser un modèle d'équation" +UndefinedEquation = "Une equation est indéfinie" +TooManyVariables = "Le nombre d'inconnues est trop grand" +NonLinearSystem = "Le système n'est pas linéaire" +Solution = "Solution" +ApproximateSolution = "Solution approchée" +SearchInverval = "Intervalle de recherche" +NoSolutionSystem = "Le systeme n'admet aucune solution" +NoSolutionEquation = "L'équation n'admet aucune solution" +NoSolutionInterval = "Aucune solution trouvée dans cet intervalle" +EnterEquation = "Entrez une équation" +InfiniteNumberOfSolutions = "Le systeme admet une infinite de solutions" +ApproximateSolutionIntervalInstruction0= "Entrez l'intervalle dans lequel" +ApproximateSolutionIntervalInstruction1= "rechercher une solution approchée" +OnlyFirstSolutionsDisplayed0 = "Seulement les 10 premières" +OnlyFirstSolutionsDisplayed1 = "solutions sont affichées" diff --git a/apps/solver/base.pt.i18n b/apps/solver/base.pt.i18n new file mode 100644 index 000000000..159a152e9 --- /dev/null +++ b/apps/solver/base.pt.i18n @@ -0,0 +1,21 @@ +SolverApp = "Equações" +SolverAppCapital = "EQUACOES" +AddEquation = "Adicione uma equação" +ResolveEquation = "Resolver a equação" +ResolveSystem = "Resolver o sistema" +UseEquationModel = "Usar um modelo de equação" +UndefinedEquation = "Equação indefinida" +TooManyVariables = "Existem muitas incógnitas" +NonLinearSystem = "O sistema não é linear" +Solution = "Solução" +ApproximateSolution = "Solução aproximada" +SearchInverval = "Intervalo de busca" +NoSolutionSystem = "O sistema não tem solução" +NoSolutionEquation = "A equação não tem solução" +NoSolutionInterval = "Nenhuma solução encontrada em el intervalo" +EnterEquation = "Digite uma equação" +InfiniteNumberOfSolutions = "Existe uma infinidade de soluções" +ApproximateSolutionIntervalInstruction0= "Digite o intervalo para procurar" +ApproximateSolutionIntervalInstruction1= "uma solução aproximada" +OnlyFirstSolutionsDisplayed0 = "Somente as 10 primeiras" +OnlyFirstSolutionsDisplayed1 = "soluções são exibidas" diff --git a/apps/solver/equation.cpp b/apps/solver/equation.cpp new file mode 100644 index 000000000..9f00539ac --- /dev/null +++ b/apps/solver/equation.cpp @@ -0,0 +1,54 @@ +#include "equation.h" + +using namespace Poincare; + +namespace Solver { + +Equation::Equation() : + Shared::ExpressionModel(), + m_standardForm(nullptr) +{ +} + +Equation& Equation::operator=(const Equation& other) { + Shared::ExpressionModel::operator=(other); + return *this; +} + +Equation::~Equation() { + if (m_standardForm) { + delete m_standardForm; + m_standardForm = nullptr; + } +} + +void Equation::setContent(const char * c) { + tidy(); + ExpressionModel::setContent(c); +} + +void Equation::tidy() { + ExpressionModel::tidy(); + if (m_standardForm) { + delete m_standardForm; + m_standardForm = nullptr; + } +} + +Expression * Equation::standardForm(Context * context) const { + if (m_standardForm == nullptr) { + Expression * e = expression(context); + if (e->type() == Expression::Type::Equal) { + m_standardForm = static_cast(e)->standardEquation(*context); + } else if (e->type() == Expression::Type::Rational && static_cast(e)->isOne()) { + // The equality was reduced which means the equality was always true. + m_standardForm = new Rational(0); + } else { + // The equality has an undefined operand + assert(e->type() == Expression::Type::Undefined); + } + } + return m_standardForm; +} + +} diff --git a/apps/solver/equation.h b/apps/solver/equation.h new file mode 100644 index 000000000..3fa925cb0 --- /dev/null +++ b/apps/solver/equation.h @@ -0,0 +1,28 @@ +#ifndef SOLVER_EQUATION_h +#define SOLVER_EQUATION_h + +#include "../shared/expression_model.h" + +namespace Solver { + +class Equation : public Shared::ExpressionModel { +public: + Equation(); + ~Equation(); + Equation& operator=(const Equation& other); + Equation& operator=(Equation&& other) = delete; + Equation(const Equation& other) = delete; + Equation(Equation&& other) = delete; + void setContent(const char * c) override; + void tidy() override; + bool shouldBeClearedBeforeRemove() override { + return false; + } + Poincare::Expression * standardForm(Poincare::Context * context) const; +private: + mutable Poincare::Expression * m_standardForm; +}; + +} + +#endif diff --git a/apps/solver/equation_list_view.cpp b/apps/solver/equation_list_view.cpp new file mode 100644 index 000000000..ae19c0dde --- /dev/null +++ b/apps/solver/equation_list_view.cpp @@ -0,0 +1,125 @@ +#include "equation_list_view.h" + +namespace Solver { + +/* EquationListView */ + +EquationListView::EquationListView(Responder * parentResponder, TableViewDataSource * dataSource, SelectableTableViewDataSource * selectionDataSource) : + Responder(parentResponder), + View(), + m_braceStyle(BraceStyle::None), + m_listView(this, dataSource, selectionDataSource), + m_braceView(), + m_scrollBraceView(&m_braceView, this) +{ + m_listView.setMargins(0); + m_listView.setVerticalCellOverlap(0); + m_listView.setShowsIndicators(false); + selectionDataSource->setScrollViewDelegate(this); + m_scrollBraceView.setMargins(k_margin, k_margin, k_margin, k_margin); + m_scrollBraceView.setShowsIndicators(false); + m_scrollBraceView.setBackgroundColor(KDColorWhite); +} + +void EquationListView::setBraceStyle(BraceStyle style) { + if (m_braceStyle != style) { + m_braceStyle = style; + layoutSubviews(); + } +} + +void EquationListView::scrollViewDidChangeOffset(ScrollViewDataSource * scrollViewDataSource) { + m_scrollBraceView.setContentOffset(KDPoint(0, scrollViewDataSource->offset().y())); + layoutSubviews(); +} + +int EquationListView::numberOfSubviews() const { + return 2; +} + +View * EquationListView::subviewAtIndex(int index) { + View * subviews[] = {&m_listView, &m_scrollBraceView}; + return subviews[index]; +} + +void EquationListView::didBecomeFirstResponder() { + app()->setFirstResponder(&m_listView); +} + +void EquationListView::layoutSubviews() { + m_listView.setFrame(KDRect(0, 0, bounds().width(), bounds().height())); + if (m_braceStyle != BraceStyle::None) { + KDCoordinate braceWidth = m_braceView.minimalSizeForOptimalDisplay().width(); + KDCoordinate braceHeight = m_listView.minimalSizeForOptimalDisplay().height()-2*k_margin; + braceHeight = m_braceStyle == BraceStyle::OneRowShort ? braceHeight - Metric::StoreRowHeight : braceHeight; + m_braceView.setSize(KDSize(braceWidth, braceHeight)); + KDCoordinate scrollBraceHeight = m_listView.minimalSizeForOptimalDisplay().height()-offset().y(); + scrollBraceHeight = m_braceStyle == BraceStyle::OneRowShort ? scrollBraceHeight - Metric::StoreRowHeight : scrollBraceHeight; + m_scrollBraceView.setFrame(KDRect(0, 0, k_braceTotalWidth, scrollBraceHeight)); + } else { + m_scrollBraceView.setFrame(KDRectZero); + } +} + +/* EquationListView::BraceWidth */ + +constexpr KDCoordinate braceExtremumHeight = 6; +constexpr KDCoordinate braceExtremumWidth = 10; +constexpr KDCoordinate braceCenterHeight = 15; +constexpr KDCoordinate braceCenterWidth = 4; + +const uint8_t topBrace[braceExtremumHeight][braceExtremumWidth] = { + {0xFF, 0xFF, 0xF7, 0x25, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xFF, 0x7B, 0x02, 0x90, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0x92, 0x18, 0xD7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0x3D, 0xAC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, +}; + +const uint8_t middleBrace[braceCenterHeight][braceCenterWidth] = { + {0xFF, 0xFF, 0xFF, 0x00}, + {0xFF, 0xFF, 0xD7, 0x29}, + {0xFF, 0xFF, 0xB9, 0x33}, + {0xFF, 0xFF, 0x7B, 0x76}, + {0xFF, 0xFF, 0x2C, 0xC3}, + {0xFF, 0xAA, 0x44, 0xFF}, + {0xFF, 0x38, 0xAB, 0xFF}, + {0x00, 0x00, 0xFF, 0xFF}, + {0xFF, 0x38, 0xAB, 0xFF}, + {0xFF, 0xAA, 0x44, 0xFF}, + {0xFF, 0xFF, 0x2C, 0xC3}, + {0xFF, 0xFF, 0x7B, 0x76}, + {0xFF, 0xFF, 0xB9, 0x33}, + {0xFF, 0xFF, 0xD7, 0x29}, + {0xFF, 0xFF, 0xFF, 0x00}, +}; + +const uint8_t bottomBrace[braceExtremumHeight][braceExtremumWidth] = { + {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0x3D, 0xAC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0x92, 0x18, 0xD7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0x7B, 0x02, 0x90, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, + {0xFF, 0xFF, 0xF7, 0x25, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, +}; + +KDColor s_braceWorkingBuffer[60]; + +void EquationListView::BraceView::drawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(bounds(), KDColorWhite); + KDCoordinate height = bounds().height(); + KDCoordinate margin = 3; + ctx->blendRectWithMask(KDRect(margin, 0, braceExtremumWidth, braceExtremumHeight), KDColorBlack, (const uint8_t *)topBrace, (KDColor *)(s_braceWorkingBuffer)); + ctx->blendRectWithMask(KDRect(0, height/2-braceCenterHeight/2, braceCenterWidth, braceCenterHeight), KDColorBlack, (const uint8_t *)middleBrace, (KDColor *)(s_braceWorkingBuffer)); + ctx->blendRectWithMask(KDRect(margin, height-braceExtremumHeight, braceExtremumWidth, braceExtremumHeight), KDColorBlack, (const uint8_t *)bottomBrace, (KDColor *)(s_braceWorkingBuffer)); + ctx->fillRect(KDRect(margin, braceExtremumHeight, 1, height/2-braceCenterHeight/2-braceExtremumHeight), KDColorBlack); + ctx->fillRect(KDRect(margin, height/2+braceCenterHeight/2, 1, height/2-braceExtremumHeight/2-braceExtremumHeight), KDColorBlack); +} + +KDSize EquationListView::BraceView::minimalSizeForOptimalDisplay() const { + return KDSize(k_braceWidth, bounds().height()); +} + +} + diff --git a/apps/solver/equation_list_view.h b/apps/solver/equation_list_view.h new file mode 100644 index 000000000..59bbaed98 --- /dev/null +++ b/apps/solver/equation_list_view.h @@ -0,0 +1,44 @@ +#ifndef SOLVER_EQUATION_LIST_VIEW_H +#define SOLVER_EQUATION_LIST_VIEW_H + +#include + +namespace Solver { + +class EquationListView : public Responder, public View, public ScrollViewDelegate, public ScrollViewDataSource { +public: + enum class BraceStyle { + None, + OneRowShort, + Full + }; + EquationListView(Responder * parentResponder, TableViewDataSource * dataSource, SelectableTableViewDataSource * selectionDataSource); + void setBraceStyle(BraceStyle style); + void scrollViewDidChangeOffset(ScrollViewDataSource * scrollViewDataSource) override; + void didBecomeFirstResponder() override; + SelectableTableView * selectableTableView() { + return &m_listView; + } + constexpr static KDCoordinate k_margin = 10; + constexpr static KDCoordinate k_braceTotalWidth = 30;//2*k_margin+BraceView::k_braceWidth; + void layoutSubviews() override; +private: + int numberOfSubviews() const override; + View * subviewAtIndex(int index) override; + + class BraceView : public View { + public: + void drawRect(KDContext * ctx, KDRect rect) const override; + KDSize minimalSizeForOptimalDisplay() const override; + constexpr static KDCoordinate k_braceWidth = 10; + }; + BraceStyle m_braceStyle; + SelectableTableView m_listView; + BraceView m_braceView; + ScrollView m_scrollBraceView; +}; + +} + +#endif + diff --git a/apps/solver/equation_models_parameter_controller.cpp b/apps/solver/equation_models_parameter_controller.cpp new file mode 100644 index 000000000..943937583 --- /dev/null +++ b/apps/solver/equation_models_parameter_controller.cpp @@ -0,0 +1,109 @@ +#include "equation_models_parameter_controller.h" +#include "list_controller.h" +#include +#include +#include "../i18n.h" + +using namespace Poincare; + +namespace Solver { + +constexpr const char * EquationModelsParameterController::k_models[k_numberOfModels]; + +EquationModelsParameterController::EquationModelsParameterController(Responder * parentResponder, EquationStore * equationStore, ListController * listController) : + ViewController(parentResponder), + m_emptyModelCell(I18n::Message::Empty, KDText::FontSize::Large), + m_expressionLayouts{}, + m_selectableTableView(this), + m_equationStore(equationStore), + m_listController(listController) +{ + m_selectableTableView.setMargins(0); + m_selectableTableView.setShowsIndicators(false); + for (int i = 0; i < k_numberOfExpressionCells; i++) { + Poincare::Expression * e = Expression::parse(k_models[i+1]); + m_expressionLayouts[i] = e->createLayout(Poincare::PrintFloat::Mode::Decimal, Poincare::Expression::ComplexFormat::Cartesian); + delete e; + m_modelCells[i].setExpressionLayout(m_expressionLayouts[i]); + } +} + +EquationModelsParameterController::~EquationModelsParameterController() { + for (int i = 0; i < k_numberOfExpressionCells; i++) { + if (m_expressionLayouts[i]) { + delete m_expressionLayouts[i]; + m_expressionLayouts[i] = nullptr; + } + } +} + +const char * EquationModelsParameterController::title() { + return I18n::translate(I18n::Message::UseEquationModel); +} + +View * EquationModelsParameterController::view() { + return &m_selectableTableView; +} + +void EquationModelsParameterController::viewWillAppear() { + ViewController::viewWillAppear(); + selectCellAtLocation(0, 0); +} + +void EquationModelsParameterController::didBecomeFirstResponder() { + app()->setFirstResponder(&m_selectableTableView); +} + +bool EquationModelsParameterController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::OK || event == Ion::Events::EXE) { + Equation * newEquation = static_cast(m_equationStore->addEmptyModel()); + newEquation->setContent(k_models[selectedRow()]); + app()->dismissModalViewController(); + m_listController->editExpression(newEquation, Ion::Events::OK); + return true; + } + return false; +} + +int EquationModelsParameterController::numberOfRows() { + return k_numberOfExpressionCells+1; +}; + +KDCoordinate EquationModelsParameterController::rowHeight(int j) { + return Metric::ToolboxRowHeight; +} + +KDCoordinate EquationModelsParameterController::cumulatedHeightFromIndex(int j) { + return rowHeight(0) * j; +} + +int EquationModelsParameterController::indexFromCumulatedHeight(KDCoordinate offsetY) { + KDCoordinate height = rowHeight(0); + if (height == 0) { + return 0; + } + return (offsetY - 1) / height; +} + +HighlightCell * EquationModelsParameterController::reusableCell(int index, int type) { + if (type == 0) { + return &m_emptyModelCell; + } + return &m_modelCells[index]; +} + +int EquationModelsParameterController::reusableCellCount(int type) { + if (type == 0) { + return 1; + } + return k_numberOfExpressionCells; +} + +int EquationModelsParameterController::typeAtLocation(int i, int j) { + if (i == 0 && j == 0) { + return 0; + } + return 1; +} + +} diff --git a/apps/solver/equation_models_parameter_controller.h b/apps/solver/equation_models_parameter_controller.h new file mode 100644 index 000000000..ba8c1ed0c --- /dev/null +++ b/apps/solver/equation_models_parameter_controller.h @@ -0,0 +1,48 @@ +#ifndef SOLVER_EQUATION_MODELS_PARAMETER_CONTROLLER_H +#define SOLVER_EQUATION_MODELS_PARAMETER_CONTROLLER_H + +#include +#include "equation_store.h" + +namespace Solver { + +class ListController; + +class EquationModelsParameterController : public ViewController, public ListViewDataSource, public SelectableTableViewDataSource { +public: + EquationModelsParameterController(Responder * parentResponder, EquationStore * equationStore, ListController * listController); + ~EquationModelsParameterController(); + EquationModelsParameterController(const EquationModelsParameterController& other) = delete; + EquationModelsParameterController(EquationModelsParameterController&& other) = delete; + EquationModelsParameterController& operator=(const EquationModelsParameterController& other) = delete; + EquationModelsParameterController& operator=(EquationModelsParameterController&& other) = delete; + const char * title() override; + View * view() override; + void viewWillAppear() override; + void didBecomeFirstResponder() override; + bool handleEvent(Ion::Events::Event event) override; + int numberOfRows() override; + KDCoordinate rowHeight(int j) override; + KDCoordinate cumulatedHeightFromIndex(int j) override; + int indexFromCumulatedHeight(KDCoordinate offsetY) override; + HighlightCell * reusableCell(int index, int type) override; + int reusableCellCount(int type) override; + int typeAtLocation(int i, int j) override; +private: + constexpr static int k_numberOfModels = 6; + static constexpr const char * k_models[k_numberOfModels] = { + "", "x+y=0", "x^2+x+1=0", "x+y+z=0", "x^3+x^2+x+1=0", "x+y+z+t=0" + }; + StackViewController * stackController() const; + constexpr static int k_numberOfExpressionCells = k_numberOfModels-1; + MessageTableCell m_emptyModelCell; + ExpressionTableCell m_modelCells[k_numberOfExpressionCells]; + Poincare::ExpressionLayout * m_expressionLayouts[k_numberOfExpressionCells]; + SelectableTableView m_selectableTableView; + EquationStore * m_equationStore; + ListController * m_listController; +}; + +} + +#endif diff --git a/apps/solver/equation_store.cpp b/apps/solver/equation_store.cpp new file mode 100644 index 000000000..5b54d68c6 --- /dev/null +++ b/apps/solver/equation_store.cpp @@ -0,0 +1,334 @@ +#include "equation_store.h" +#include + +using namespace Poincare; + +namespace Solver { + +EquationStore::EquationStore() : + m_type(Type::LinearSystem), + m_numberOfSolutions(0), + m_exactSolutionExactLayouts{}, + m_exactSolutionApproximateLayouts{} +{ +} + +EquationStore::~EquationStore() { + tidySolution(); +} + +Equation * EquationStore::emptyModel() { + static Equation e; + return &e; +} + +void EquationStore::setModelAtIndex(Shared::ExpressionModel * e, int i) { + m_equations[i] = *(static_cast(e));; +} + +void EquationStore::tidy() { + ExpressionModelStore::tidy(); + tidySolution(); +} + +Poincare::ExpressionLayout * EquationStore::exactSolutionLayoutAtIndex(int i, bool exactLayout) { + assert(m_type != Type::Monovariable && i >= 0 && (i < m_numberOfSolutions || (i == m_numberOfSolutions && m_type == Type::PolynomialMonovariable))); + if (exactLayout) { + return m_exactSolutionExactLayouts[i]; + } else { + return m_exactSolutionApproximateLayouts[i]; + } +} + +double EquationStore::intervalBound(int index) const { + assert(m_type == Type::Monovariable && index >= 0 && index < 2); + return m_intervalApproximateSolutions[index]; +} + +void EquationStore::setIntervalBound(int index, double value) { + assert(m_type == Type::Monovariable && index >= 0 && index < 2); + m_intervalApproximateSolutions[index] = value; + if (m_intervalApproximateSolutions[0] > m_intervalApproximateSolutions[1]) { + if (index == 0) { + m_intervalApproximateSolutions[1] = m_intervalApproximateSolutions[0]+1; + } else { + m_intervalApproximateSolutions[0] = m_intervalApproximateSolutions[1]-1; + } + } +} + +double EquationStore::approximateSolutionAtIndex(int i) { + assert(m_type == Type::Monovariable && i >= 0 && i < m_numberOfSolutions); + return m_approximateSolutions[i]; +} + +bool EquationStore::haveMoreApproximationSolutions(Context * context) { + if (m_numberOfSolutions < k_maxNumberOfEquations) { + return false; + } + double step = (m_intervalApproximateSolutions[1]-m_intervalApproximateSolutions[0])*k_precision; + return !std::isnan(definedModelAtIndex(0)->standardForm(context)->nextRoot(m_variables[0], m_approximateSolutions[m_numberOfSolutions-1], step, m_intervalApproximateSolutions[1], *context)); +} + +void EquationStore::approximateSolve(Poincare::Context * context) { + assert(m_variables[0] != 0 && m_variables[1] == 0); + assert(m_type == Type::Monovariable); + m_numberOfSolutions = 0; + double start = m_intervalApproximateSolutions[0]; + double step = (m_intervalApproximateSolutions[1]-m_intervalApproximateSolutions[0])*k_precision; + for (int i = 0; i < k_maxNumberOfApproximateSolutions; i++) { + m_approximateSolutions[i] = definedModelAtIndex(0)->standardForm(context)->nextRoot(m_variables[0], start, step, m_intervalApproximateSolutions[1], *context); + if (std::isnan(m_approximateSolutions[i])) { + break; + } else { + start = m_approximateSolutions[i]; + m_numberOfSolutions++; + } + } +} + +EquationStore::Error EquationStore::exactSolve(Poincare::Context * context) { + tidySolution(); + + /* 0- Get unknown variables */ + m_variables[0] = 0; + int numberOfVariables = 0; + for (int i = 0; i < numberOfDefinedModels(); i++) { + if (definedModelAtIndex(i)->standardForm(context) == nullptr) { + return Error::EquationUndefined; + } + numberOfVariables = definedModelAtIndex(i)->standardForm(context)->getVariables(m_variables); + if (numberOfVariables < 0) { + return Error::TooManyVariables; + } + } + + /* 1- Linear System? */ + /* Create matrix coefficients and vector constants as: + * coefficients*(x y z ...) = constants */ + Expression * coefficients[k_maxNumberOfEquations][Expression::k_maxNumberOfVariables]; + Expression * constants[k_maxNumberOfEquations]; + bool isLinear = true; // Invalid the linear system if one equation is non-linear + for (int i = 0; i < numberOfDefinedModels(); i++) { + isLinear = isLinear && definedModelAtIndex(i)->standardForm(context)->getLinearCoefficients(m_variables, coefficients[i], &constants[i], *context); + // Clean allocated memory if the system is not linear + if (!isLinear) { + for (int j = 0; j < i; j++) { + for (int k = 0; k < numberOfVariables; k++) { + delete coefficients[j][k]; + } + delete constants[j]; + } + if (numberOfDefinedModels() > 1 || numberOfVariables > 1) { + return Error::NonLinearSystem; + } else { + break; + } + } + } + + /* Initialize result */ + Expression * exactSolutions[k_maxNumberOfExactSolutions]; + for (int i = 0; i < k_maxNumberOfExactSolutions; i++) { + exactSolutions[i] = nullptr; + } + EquationStore::Error error; + + if (isLinear) { + m_type = Type::LinearSystem; + error = resolveLinearSystem(exactSolutions, coefficients, constants, context); + } else { + /* 2- Polynomial & Monovariable? */ + assert(numberOfVariables == 1 && numberOfDefinedModels() == 1); + char x = m_variables[0]; + Expression * polynomialCoefficients[Expression::k_maxNumberOfPolynomialCoefficients]; + int degree = definedModelAtIndex(0)->standardForm(context)->getPolynomialCoefficients(x, polynomialCoefficients, *context); + if (degree < 0) { + /* 3- Monovariable non-polynomial */ + m_type = Type::Monovariable; + m_intervalApproximateSolutions[0] = -10; + m_intervalApproximateSolutions[1] = 10; + return Error::RequireApproximateSolution; + } else { + m_type = Type::PolynomialMonovariable; + error = oneDimensialPolynomialSolve(exactSolutions, polynomialCoefficients, degree, context); + } + } + /* Turn the results in layouts */ + for (int i = 0; i < k_maxNumberOfExactSolutions; i++) { + if (exactSolutions[i]) { + m_exactSolutionExactLayouts[i] = exactSolutions[i]->createLayout(); + Expression * approximate = exactSolutions[i]->approximate(*context); + m_exactSolutionApproximateLayouts[i] = approximate->createLayout(); + /* Check for identity between exact and approximate layouts */ + char exactBuffer[Shared::ExpressionModel::k_expressionBufferSize]; + char approximateBuffer[Shared::ExpressionModel::k_expressionBufferSize]; + m_exactSolutionExactLayouts[i]->writeTextInBuffer(exactBuffer, Shared::ExpressionModel::k_expressionBufferSize, Preferences::sharedPreferences()->numberOfSignificantDigits()); + m_exactSolutionApproximateLayouts[i]->writeTextInBuffer(approximateBuffer, Shared::ExpressionModel::k_expressionBufferSize, Preferences::sharedPreferences()->numberOfSignificantDigits()); + m_exactSolutionIdentity[i] = strcmp(exactBuffer, approximateBuffer) == 0; + /* Check for equality between exact and approximate layouts */ + if (!m_exactSolutionIdentity[i]) { + m_exactSolutionEquality[i] = exactSolutions[i]->isEqualToItsApproximationLayout(approximate, Shared::ExpressionModel::k_expressionBufferSize, Preferences::sharedPreferences()->numberOfSignificantDigits(), *context); + } + delete approximate; + delete exactSolutions[i]; + } + } + return error; +} + +EquationStore::Error EquationStore::resolveLinearSystem(Expression * exactSolutions[k_maxNumberOfExactSolutions], Expression * coefficients[k_maxNumberOfEquations][Expression::k_maxNumberOfVariables], Expression * constants[k_maxNumberOfEquations], Context * context) { + Expression::AngleUnit angleUnit = Preferences::sharedPreferences()->angleUnit(); + int n = strlen(m_variables); // n unknown variables + int m = numberOfDefinedModels(); // m equations + /* Create the matrix (A | b) for the equation Ax=b */ + const Expression ** operandsAb = new const Expression * [(n+1)*m]; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + operandsAb[i*(n+1)+j] = coefficients[i][j]; + } + operandsAb[i*(n+1)+n] = constants[i]; + } + Matrix * Ab = new Matrix(operandsAb, m, n+1, false); + delete [] operandsAb; + // Compute the rank of (A | b) + int rankAb = Ab->rank(*context, angleUnit, true); + + // Initialize the number of solutions + m_numberOfSolutions = INT_MAX; + /* If the matrix has one null row except the last column, the system is + * inconsistent (equivalent to 0 = x with x non-null */ + for (int j = m-1; j >= 0; j--) { + bool rowWithNullCoefficients = true; + for (int i = 0; i < n; i++) { + if (!Ab->matrixOperand(j, i)->isRationalZero()) { + rowWithNullCoefficients = false; + break; + } + } + if (rowWithNullCoefficients && !Ab->matrixOperand(j, n)->isRationalZero()) { + m_numberOfSolutions = 0; + } + } + if (m_numberOfSolutions > 0) { + // if rank(A | b) < n, the system has an infinite number of solutions + if (rankAb == n && n > 0) { + // Otherwise, the system has n solutions which correspond to the last column + m_numberOfSolutions = n; + for (int i = 0; i < m_numberOfSolutions; i++) { + Expression * sol = Ab->matrixOperand(i,n); + exactSolutions[i] = sol; + Ab->detachOperand(sol); + Expression::Simplify(&exactSolutions[i], *context); + } + } + } + delete Ab; + return Error::NoError; +} + +EquationStore::Error EquationStore::oneDimensialPolynomialSolve(Expression * exactSolutions[k_maxNumberOfExactSolutions], Expression * coefficients[Expression::k_maxNumberOfPolynomialCoefficients], int degree, Context * context) { + /* Equation ax^2+bx+c = 0 */ + assert(degree == 2); + // Compute 4ac + Expression * deltaSubOperand[3] = {new Rational(4), coefficients[0]->clone(), coefficients[2]->clone()}; + // Compute delta = b*b-4ac + Expression * delta = new Subtraction(new Power(coefficients[1]->clone(), new Rational(2), false), new Multiplication(deltaSubOperand, 3, false), false); + Expression::Simplify(&delta, *context); + if (delta->isRationalZero()) { + // if delta = 0, x0=x1= -b/(2a) + exactSolutions[0] = new Division(new Opposite(coefficients[1], false), new Multiplication(new Rational(2), coefficients[2], false), false); + m_numberOfSolutions = 1; + } else { + // x0 = (-b-sqrt(delta))/(2a) + exactSolutions[0] = new Division(new Subtraction(new Opposite(coefficients[1]->clone(), false), new SquareRoot(delta->clone(), false), false), new Multiplication(new Rational(2), coefficients[2]->clone(), false), false); + // x1 = (-b+sqrt(delta))/(2a) + exactSolutions[1] = new Division(new Addition(new Opposite(coefficients[1], false), new SquareRoot(delta->clone(), false), false), new Multiplication(new Rational(2), coefficients[2], false), false); + m_numberOfSolutions = 2; + } + exactSolutions[m_numberOfSolutions] = delta; + delete coefficients[0]; + for (int i = 0; i < m_numberOfSolutions; i++) { + Expression::Simplify(&exactSolutions[i], *context); + } + return Error::NoError; +#if 0 + if (degree == 3) { + Expression * a = coefficients[3]; + Expression * b = coefficients[2]; + Expression * c = coefficients[1]; + Expression * d = coefficients[0]; + // Delta = b^2*c^2+18abcd-27a^2*d^2-4ac^3-4db^3 + Expression * mult0Operands[2] = {new Power(b->clone(), new Rational(2), false), new Power(c->clone(), new Rational(2), false)}; + Expression * mult1Operands[5] = {new Rational(18), a->clone(), b->clone(), c->clone(), d->clone()}; + Expression * mult2Operands[3] = {new Rational(-27), new Power(a->clone(), new Rational(2), false), new Power(d->clone(), new Rational(2), false)}; + Expression * mult3Operands[3] = {new Rational(-4), a->clone(), new Power(c->clone(), new Rational(3), false)}; + Expression * mult4Operands[3] = {new Rational(-4), d->clone(), new Power(b->clone(), new Rational(3), false)}; + Expression * add0Operands[5] = {new Multiplication(mult0Operands, 2, false), new Multiplication(mult1Operands, 5, false), new Multiplication(mult2Operands, 3, false), new Multiplication(mult3Operands, 3, false), new Multiplication(mult4Operands, 3, false)}; + Expression * delta = new Addition(add0Operands, 5, false); + Simplify(&delta, *context); + // Delta0 = b^2-3ac + Expression * mult5Operands[3] = {new Rational(3), a->clone(), c->clone()}; + Expression * delta0 = new Subtraction(new Power(b->clone(), new Rational(2), false), new Multiplication(mult5Operands, 3, false), false); + Reduce(&delta0, *context); + if (delta->isRationalZero()) { + if (delta0->isRationalZero()) { + // delta0 = 0 && delta = 0 --> x0 = -b/(3a) + delete delta0; + m_exactSolutions[0] = new Opposite(new Division(b, new Multiplication(new Rational(3), a, false), false), false); + m_numberOfSolutions = 1; + delete c; + delete d; + } else { + // delta = 0 --> x0 = (9ad-bc)/(2delta0) + // --> x1 = (4abc-9a^2d-b^3)/(a*delta0) + Expression * mult6Operands[3] = {new Rational(9), a, d}; + m_exactSolutions[0] = new Division(new Subtraction(new Multiplication(mult6Operands, 3, false), new Multiplication(b, c, false), false), new Multiplication(new Rational(2), delta0, false), false); + Expression * mult7Operands[4] = {new Rational(4), a->clone(), b->clone(), c->clone()}; + Expression * mult8Operands[3] = {new Rational(-9), new Power(a->clone(), new Rational(2), false), d->clone()}; + Expression * add1Operands[3] = {new Multiplication(mult7Operands, 4, false), new Multiplication(mult8Operands,3, false), new Opposite(new Power(b->clone(), new Rational(3), false), false)}; + m_exactSolutions[1] = new Division(new Addition(add1Operands, 3, false), new Multiplication(a->clone(), delta0, false), false); + m_numberOfSolutions = 2; + } + } else { + // delta1 = 2b^3-9abc+27a^2*d + Expression * mult9Operands[4] = {new Rational(-9), a, b, c}; + Expression * mult10Operands[3] = {new Rational(27), new Power(a->clone(), new Rational(2), false), d}; + Expression * add2Operands[3] = {new Multiplication(new Rational(2), new Power(b->clone(), new Rational(3), false), false), new Multiplication(mult9Operands, 4, false), new Multiplication(mult10Operands, 3, false)}; + Expression * delta1 = new Addition(add2Operands, 3, false); + // C = Root((delta1+sqrt(-27a^2*delta))/2, 3) + Expression * mult11Operands[3] = {new Rational(-27), new Power(a->clone(), new Rational(2), false), (*delta)->clone()}; + Expression * c = new Power(new Division(new Addition(delta1, new SquareRoot(new Multiplication(mult11Operands, 3, false), false), false), new Rational(2), false), new Rational(1,3), false); + Expression * unary3roots[2] = {new Addition(new Rational(-1,2), new Division(new Multiplication(new SquareRoot(new Rational(3), false), new Symbol(Ion::Charset::IComplex), false), new Rational(2), false), false), new Subtraction(new Rational(-1,2), new Division(new Multiplication(new SquareRoot(new Rational(3), false), new Symbol(Ion::Charset::IComplex), false), new Rational(2), false), false)}; + // x_k = -1/(3a)*(b+C*z+delta0/(zC)) with z = unary cube root + for (int k = 0; k < 3; k++) { + Expression * ccopy = c; + Expression * delta0copy = delta0; + if (k < 2) { + ccopy = new Multiplication(c->clone(), unary3roots[k], false); + delta0copy = delta0->clone(); + } + Expression * add3Operands[3] = {b->clone(), ccopy, new Division(delta0copy, ccopy->clone(), false)}; + m_exactSolutions[k] = new Multiplication(new Division(new Rational(-1), new Multiplication(new Rational(3), a->clone(), false), false), new Addition(add3Operands, 3, false), false); + } + m_numberOfSolutions = 3; + } + m_exactSolutions[m_numberOfSolutions] = delta; + } +#endif +} + +void EquationStore::tidySolution() { + for (int i = 0; i < k_maxNumberOfExactSolutions; i++) { + if (m_exactSolutionExactLayouts[i]) { + delete m_exactSolutionExactLayouts[i]; + m_exactSolutionExactLayouts[i] = nullptr; + } + if (m_exactSolutionApproximateLayouts[i]) { + delete m_exactSolutionApproximateLayouts[i]; + m_exactSolutionApproximateLayouts[i] = nullptr; + } + } +} + +} diff --git a/apps/solver/equation_store.h b/apps/solver/equation_store.h new file mode 100644 index 000000000..f761c72ab --- /dev/null +++ b/apps/solver/equation_store.h @@ -0,0 +1,87 @@ +#ifndef SOLVER_EQUATION_STORE_H +#define SOLVER_EQUATION_STORE_H + +#include "equation.h" +#include "../shared/expression_model_store.h" +#include + +namespace Solver { + +class EquationStore : public Shared::ExpressionModelStore { +public: + enum class Type { + LinearSystem, + PolynomialMonovariable, + Monovariable, + }; + enum class Error : int16_t { + NoError = 0, + EquationUndefined = -1, + TooManyVariables = -2, + NonLinearSystem = -3, + RequireApproximateSolution = -4 + }; + EquationStore(); + ~EquationStore(); + Equation * modelAtIndex(int i) override { + assert(i>=0 && i(Shared::ExpressionModelStore::definedModelAtIndex(i)); } + int maxNumberOfModels() const override { return k_maxNumberOfEquations; } + Type type() const { + return m_type; + } + char variableAtIndex(size_t i) { + assert(i < strlen(m_variables)); + return m_variables[i]; + } + int numberOfSolutions() const { + return m_numberOfSolutions; + } + Poincare::ExpressionLayout * exactSolutionLayoutAtIndex(int i, bool exactLayout); + bool exactSolutionLayoutsAtIndexAreIdentical(int i) { + assert(m_type != Type::Monovariable && i >= 0 && (i < m_numberOfSolutions || (i == m_numberOfSolutions && m_type == Type::PolynomialMonovariable))); + return m_exactSolutionIdentity[i]; + } + bool exactSolutionLayoutsAtIndexAreEqual(int i) { + assert(m_type != Type::Monovariable && i >= 0 && (i < m_numberOfSolutions || (i == m_numberOfSolutions && m_type == Type::PolynomialMonovariable))); + return m_exactSolutionEquality[i]; + } + double intervalBound(int index) const; + void setIntervalBound(int index, double value); + double approximateSolutionAtIndex(int i); + void tidy() override; + void approximateSolve(Poincare::Context * context); + bool haveMoreApproximationSolutions(Poincare::Context * context); + Error exactSolve(Poincare::Context * context); + static constexpr int k_maxNumberOfExactSolutions = Poincare::Expression::k_maxNumberOfVariables > Poincare::Expression::k_maxPolynomialDegree + 1? Poincare::Expression::k_maxNumberOfVariables : Poincare::Expression::k_maxPolynomialDegree + 1; + static constexpr int k_maxNumberOfApproximateSolutions = 10; + static constexpr int k_maxNumberOfSolutions = k_maxNumberOfExactSolutions > k_maxNumberOfApproximateSolutions ? k_maxNumberOfExactSolutions : k_maxNumberOfApproximateSolutions; +private: + static constexpr double k_precision = 0.01; + static constexpr int k_maxNumberOfEquations = Poincare::Expression::k_maxNumberOfVariables; // Enable the same number of equations as the number of unknown variables + Equation * emptyModel() override; + Equation * nullModel() override { + return emptyModel(); + } + void setModelAtIndex(Shared::ExpressionModel * f, int i) override; + Error resolveLinearSystem(Poincare::Expression * solutions[k_maxNumberOfExactSolutions], Poincare::Expression * coefficients[k_maxNumberOfEquations][Poincare::Expression::k_maxNumberOfVariables], Poincare::Expression * constants[k_maxNumberOfEquations], Poincare::Context * context); + Error oneDimensialPolynomialSolve(Poincare::Expression * solutions[k_maxNumberOfExactSolutions], Poincare::Expression * polynomialCoefficients[Poincare::Expression::k_maxNumberOfPolynomialCoefficients], int degree, Poincare::Context * context); + void tidySolution(); + + Equation m_equations[k_maxNumberOfEquations]; + Type m_type; + char m_variables[Poincare::Expression::k_maxNumberOfVariables+1]; + int m_numberOfSolutions; + Poincare::ExpressionLayout * m_exactSolutionExactLayouts[k_maxNumberOfApproximateSolutions]; + Poincare::ExpressionLayout * m_exactSolutionApproximateLayouts[k_maxNumberOfExactSolutions]; + bool m_exactSolutionIdentity[k_maxNumberOfExactSolutions]; + bool m_exactSolutionEquality[k_maxNumberOfExactSolutions]; + double m_intervalApproximateSolutions[2]; + double m_approximateSolutions[k_maxNumberOfApproximateSolutions]; +}; + +} + +#endif diff --git a/apps/solver/interval_controller.cpp b/apps/solver/interval_controller.cpp new file mode 100644 index 000000000..2e0c4a80f --- /dev/null +++ b/apps/solver/interval_controller.cpp @@ -0,0 +1,134 @@ +#include "interval_controller.h" +#include "app.h" +#include "../i18n.h" +#include +#include + +namespace Solver { + +IntervalController::ContentView::ContentView(SelectableTableView * selectableTableView) : + m_instructions0(KDText::FontSize::Small, I18n::Message::ApproximateSolutionIntervalInstruction0, 0.5f, 0.5f, KDColorBlack, Palette::WallScreen), + m_instructions1(KDText::FontSize::Small, I18n::Message::ApproximateSolutionIntervalInstruction1, 0.5f, 0.5f, KDColorBlack, Palette::WallScreen), + m_selectableTableView(selectableTableView) +{ +} + +void IntervalController::ContentView::drawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(KDRect(0, 0, bounds().width(), k_topMargin), Palette::WallScreen); +} + +int IntervalController::ContentView::numberOfSubviews() const { + return 3; +} + +View * IntervalController::ContentView::subviewAtIndex(int index) { + assert(index >= 0 && index < 5); + if (index == 0) { + return &m_instructions0; + } + if (index == 1) { + return &m_instructions1; + } + return m_selectableTableView; +} + +void IntervalController::ContentView::layoutSubviews() { + KDCoordinate textHeight = KDText::charSize(KDText::FontSize::Small).height(); + m_instructions0.setFrame(KDRect(0, k_topMargin/2-textHeight, bounds().width(), textHeight)); + m_instructions1.setFrame(KDRect(0, k_topMargin/2, bounds().width(), textHeight)); + m_selectableTableView->setFrame(KDRect(0, k_topMargin, bounds().width(), bounds().height()-k_topMargin)); +} + +/* IntervalController Controller */ + +IntervalController::IntervalController(Responder * parentResponder, EquationStore * equationStore) : + FloatParameterController(parentResponder), + m_selectableTableView(nullptr), + m_intervalCell{}, + m_equationStore(equationStore) +{ +} + +const char * IntervalController::title() { + return I18n::translate(I18n::Message::SearchInverval); +} + +int IntervalController::numberOfRows() { + return k_maxNumberOfCells+1; +} + +void IntervalController::willDisplayCellForIndex(HighlightCell * cell, int index) { + if (index == numberOfRows()-1) { + return; + } + I18n::Message labels[k_maxNumberOfCells] = {I18n::Message::XMin, I18n::Message::XMax}; + MessageTableCellWithEditableText * myCell = (MessageTableCellWithEditableText *) cell; + myCell->setMessage(labels[index]); + FloatParameterController::willDisplayCellForIndex(cell, index); +} + +HighlightCell * IntervalController::reusableParameterCell(int index, int type) { + assert(index >= 0); + assert(index < 2); + return m_intervalCell[index]; +} + +int IntervalController::reusableParameterCellCount(int type) { + return k_maxNumberOfCells; +} + +double IntervalController::parameterAtIndex(int index) { + return m_equationStore->intervalBound(index); +} + +bool IntervalController::setParameterAtIndex(int parameterIndex, double f) { + m_equationStore->setIntervalBound(parameterIndex, f); + return true; +} + +bool IntervalController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { + if (FloatParameterController::textFieldDidFinishEditing(textField, text, event)) { + m_selectableTableView->reloadData(); + return true; + } + return false; +} + +void IntervalController::buttonAction() { + StackViewController * stack = stackController(); + App * solverApp = static_cast(app()); + m_equationStore->approximateSolve(solverApp->localContext()); + stack->push(solverApp->solutionsControllerStack(), KDColorWhite, Palette::SubTab, Palette::SubTab); +} + +I18n::Message IntervalController::okButtonText() { + return I18n::Message::ResolveEquation; +} + +View * IntervalController::loadView() { + m_selectableTableView = (SelectableTableView *)FloatParameterController::loadView(); + m_selectableTableView->setTopMargin(0); + for (int i = 0; i < k_maxNumberOfCells; i++) { + m_intervalCell[i] = new MessageTableCellWithEditableText(m_selectableTableView, this, m_draftTextBuffer); + } + ContentView * contentView = (ContentView *)new ContentView(m_selectableTableView); + return contentView; +} + +void IntervalController::unloadView(View * view) { + delete m_selectableTableView; + m_selectableTableView = nullptr; + for (int i = 0; i < k_maxNumberOfCells; i++) { + delete m_intervalCell[i]; + m_intervalCell[i] = nullptr; + } + FloatParameterController::unloadView(view); +} + +SelectableTableView * IntervalController::selectableTableView() { + return m_selectableTableView; +} + +} + + diff --git a/apps/solver/interval_controller.h b/apps/solver/interval_controller.h new file mode 100644 index 000000000..7a608cc3a --- /dev/null +++ b/apps/solver/interval_controller.h @@ -0,0 +1,49 @@ +#ifndef SOLVER_INTERVAL_CONTROLLER_H +#define SOLVER_INTERVAL_CONTROLLER_H + +#include +#include "equation_store.h" +#include "../shared/float_parameter_controller.h" + +namespace Solver { + +class IntervalController : public Shared::FloatParameterController { +public: + IntervalController(Responder * parentResponder, EquationStore * equationStore); + const char * title() override; + int numberOfRows() override; + void willDisplayCellForIndex(HighlightCell * cell, int index) override; +private: + HighlightCell * reusableParameterCell(int index, int type) override; + int reusableParameterCellCount(int type) override; + void buttonAction() override; + double parameterAtIndex(int index) override; + bool setParameterAtIndex(int parameterIndex, double f) override; + bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; + I18n::Message okButtonText() override; + View * loadView() override; + void unloadView(View * view) override; + class ContentView : public View { + public: + ContentView(SelectableTableView * selectableTableView); + void drawRect(KDContext * ctx, KDRect rect) const override; + private: + constexpr static KDCoordinate k_topMargin = 50; + int numberOfSubviews() const override; + View * subviewAtIndex(int index) override; + void layoutSubviews() override; + MessageTextView m_instructions0; + MessageTextView m_instructions1; + SelectableTableView * m_selectableTableView; + }; + SelectableTableView * selectableTableView() override; + SelectableTableView * m_selectableTableView; + constexpr static int k_maxNumberOfCells = 2; + char m_draftTextBuffer[MessageTableCellWithEditableText::k_bufferLength]; + MessageTableCellWithEditableText * m_intervalCell[k_maxNumberOfCells]; + EquationStore * m_equationStore; +}; + +} + +#endif diff --git a/apps/solver/list_controller.cpp b/apps/solver/list_controller.cpp new file mode 100644 index 000000000..d17bd18bd --- /dev/null +++ b/apps/solver/list_controller.cpp @@ -0,0 +1,249 @@ +#include "list_controller.h" +#include "app.h" +#include + +using namespace Shared; + +namespace Solver { + +ListController::ListController(Responder * parentResponder, EquationStore * equationStore, ButtonRowController * footer) : + ExpressionModelListController(parentResponder, I18n::Message::AddEquation), + ButtonRowDelegate(nullptr, footer), + m_equationStore(equationStore), + m_resolveButton(this, equationStore->numberOfDefinedModels() > 1 ? I18n::Message::ResolveSystem : I18n::Message::ResolveEquation, Invocation([](void * context, void * sender) { + ListController * list = (ListController *)context; + list->resolveEquations(); + }, this), KDText::FontSize::Large, Palette::PurpleBright), + m_modelsParameterController(this, equationStore, this), + m_modelsStackController(nullptr, &m_modelsParameterController, KDColorWhite, Palette::PurpleDark, Palette::PurpleDark) +{ +} + +int ListController::numberOfButtons(ButtonRowController::Position position) const { + if (position == ButtonRowController::Position::Bottom) { + return 1; + } + return 0; +} + +Button * ListController::buttonAtIndex(int index, ButtonRowController::Position position) const { + if (position == ButtonRowController::Position::Top) { + return nullptr; + } + return const_cast