Merge branch 'version-11' into f7

This commit is contained in:
Émilie Feral
2019-05-03 17:25:53 +02:00
120 changed files with 910 additions and 890 deletions

View File

@@ -172,11 +172,15 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) {
}
if (shouldOnlyDisplayExactOutput()) {
m_displayOutput = DisplayOutput::ExactOnly;
} else if (exactOutput().recursivelyMatches([](const Expression e, Context & c, bool replaceSymbols) {
/* If the exact result contains one of the following types, do not
* display it. */
ExpressionNode::Type t = e.type();
return (t == ExpressionNode::Type::Random) || (t == ExpressionNode::Type::Round);},
} else if (input().recursivelyMatches(
[](const Expression e, Context & c) {
/* If the input contains:
* - Random
* - Round
* or involves a Matrix, we only display the approximate output. */
ExpressionNode::Type t = e.type();
return (t == ExpressionNode::Type::Random) || (t == ExpressionNode::Type::Round) || Expression::IsMatrix(e, c);
},
*context, true))
{
m_displayOutput = DisplayOutput::ApproximateOnly;
@@ -188,7 +192,7 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) {
} else if (strcmp(m_exactOutputText, Undefined::Name()) == 0 || strcmp(m_approximateOutputText, Unreal::Name()) == 0) {
// If the approximate result is 'unreal' or the exact result is 'undef'
m_displayOutput = DisplayOutput::ApproximateOnly;
} else if (input().isApproximate(*context) || exactOutput().isApproximate(*context)) {
} else if (input().recursivelyMatches(Expression::IsApproximate, *context) || exactOutput().recursivelyMatches(Expression::IsApproximate, *context)) {
m_displayOutput = DisplayOutput::ExactAndApproximateToggle;
} else {
m_displayOutput = DisplayOutput::ExactAndApproximate;

View File

@@ -96,10 +96,10 @@ Expression CalculationStore::ansExpression(Context * context) {
* To avoid turning 'ans->A' in '2->A->A' or '2=A->A' (which cannot be
* parsed), ans is replaced by the approximation output when any Store or
* Equal expression appears. */
bool exactOuptutInvolvesStoreEqual = lastCalculation->exactOutput().recursivelyMatches([](const Expression e, Context & context, bool replaceSymbols) {
bool exactOuptutInvolvesStoreEqual = lastCalculation->exactOutput().recursivelyMatches([](const Expression e, Context & context) {
return e.type() == ExpressionNode::Type::Store || e.type() == ExpressionNode::Type::Equal;
}, *context, false);
if (lastCalculation->input().isApproximate(*context) || exactOuptutInvolvesStoreEqual) {
if (lastCalculation->input().recursivelyMatches(Expression::IsApproximate, *context) || exactOuptutInvolvesStoreEqual) {
return lastCalculation->approximateOutput(context);
}
return lastCalculation->exactOutput();

View File

@@ -129,7 +129,7 @@ void HistoryViewCell::layoutSubviews() {
maxCoordinate(0, maxFrameWidth - outputSize.width()),
inputSize.height(),
minCoordinate(maxFrameWidth, outputSize.width()),
bounds().height() - inputSize.height()
outputSize.height()
));
}
@@ -180,9 +180,9 @@ bool HistoryViewCell::handleEvent(Ion::Events::Event event) {
if ((event == Ion::Events::Down && m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Input) ||
(event == Ion::Events::Up && m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Output)) {
HistoryViewCellDataSource::SubviewType otherSubviewType = m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Input ? HistoryViewCellDataSource::SubviewType::Output : HistoryViewCellDataSource::SubviewType::Input;
m_dataSource->setSelectedSubviewType(otherSubviewType, this);
CalculationSelectableTableView * tableView = (CalculationSelectableTableView *)parentResponder();
tableView->scrollToSubviewOfTypeOfCellAtLocation(otherSubviewType, tableView->selectedColumn(), tableView->selectedRow());
m_dataSource->setSelectedSubviewType(otherSubviewType, this);
app()->setFirstResponder(this);
return true;
}

View File

@@ -63,7 +63,7 @@ QUIZ_CASE(calculation_ans) {
store.push("ans+0.22", &globalContext);
lastCalculation = store.calculationAtIndex(2);
quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ApproximateOnly);
quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximateToggle);
quiz_assert(strcmp(lastCalculation->approximateOutputText(),"2.6366666666667") == 0);
store.deleteAll();

View File

@@ -36,6 +36,7 @@ PythonEval = "Returns the evaluated expression"
PythonExp = "Exponential function"
PythonExpm1 = "Compute exp(x)-1"
PythonFabs = "Absolute value"
PythonFillRect = "Fill a rectangle at pixel (x,y)"
PythonFloat = "Convert x to a float"
PythonFloor = "Floor"
PythonFmod = "a modulo b"

View File

@@ -36,6 +36,7 @@ PythonEval = "Returns the evaluated expression"
PythonExp = "Exponential function"
PythonExpm1 = "Compute exp(x)-1"
PythonFabs = "Absolute value"
PythonFillRect = "Fill a rectangle at pixel (x,y)"
PythonFloat = "Convert x to a float"
PythonFloor = "Floor"
PythonFmod = "a modulo b"

View File

@@ -36,6 +36,7 @@ PythonEval = "Returns the evaluated expression"
PythonExp = "Exponential function"
PythonExpm1 = "Compute exp(x)-1"
PythonFabs = "Absolute value"
PythonFillRect = "Fill a rectangle at pixel (x,y)"
PythonFloat = "Convert x to a float"
PythonFloor = "Floor"
PythonFmod = "a modulo b"

View File

@@ -36,6 +36,7 @@ PythonEval = "Evalue l'expression en argument "
PythonExp = "Fonction exponentielle"
PythonExpm1 = "Calcul de exp(x)-1"
PythonFabs = "Valeur absolue"
PythonFillRect = "Remplit un rectangle"
PythonFloat = "Conversion en flottant"
PythonFloor = "Partie entière"
PythonFmod = "a modulo b"

View File

@@ -36,6 +36,7 @@ PythonEval = "Returns the evaluated expression"
PythonExp = "Exponential function"
PythonExpm1 = "Compute exp(x)-1"
PythonFabs = "Absolute value"
PythonFillRect = "Fill a rectangle at pixel (x,y)"
PythonFloat = "Convert x to a float"
PythonFloor = "Floor"
PythonFmod = "a modulo b"

View File

@@ -38,6 +38,7 @@ PythonCommandExp = "exp(x)"
PythonCommandExpComplex = "exp(z)"
PythonCommandExpm1 = "expm1(x)"
PythonCommandFabs = "fabs(x)"
PythonCommandFillRect = "fill_rect(x,y,width,height,color)"
PythonCommandFloat = "float(x)"
PythonCommandFloor = "floor(x)"
PythonCommandFmod = "fmod(a,b)"

View File

@@ -424,11 +424,24 @@ void ConsoleController::appendTextToOutputAccumulationBuffer(const char * text,
memcpy(&m_outputAccumulationBuffer[endOfAccumulatedText], text, length);
return;
}
memcpy(&m_outputAccumulationBuffer[endOfAccumulatedText], text, spaceLeft-1);
/* The text to append is too long for the buffer. We need to split it in
* chunks. We take special care not to break in the middle of code points! */
int maxAppendedTextLength = spaceLeft-1; // we keep the last char to null-terminate the buffer
int appendedTextLength = 0;
UTF8Decoder decoder(text);
while (decoder.stringPosition() - text <= maxAppendedTextLength) {
appendedTextLength = decoder.stringPosition() - text;
decoder.nextCodePoint();
}
memcpy(&m_outputAccumulationBuffer[endOfAccumulatedText], text, appendedTextLength);
// The last char of m_outputAccumulationBuffer is kept to 0 to ensure a null-terminated text.
assert(endOfAccumulatedText+appendedTextLength < k_outputAccumulationBufferSize);
m_outputAccumulationBuffer[endOfAccumulatedText+appendedTextLength] = 0;
flushOutputAccumulationBufferToStore();
appendTextToOutputAccumulationBuffer(&text[spaceLeft-1], length - (spaceLeft - 1));
appendTextToOutputAccumulationBuffer(&text[appendedTextLength], length - appendedTextLength);
}
// TODO: is it really needed? Maybe discard to optimize?
void ConsoleController::emptyOutputAccumulationBuffer() {
for (int i = 0; i < k_outputAccumulationBufferSize; i++) {
m_outputAccumulationBuffer[i] = 0;

View File

@@ -273,7 +273,7 @@ void MenuController::willDisplayScriptTitleCellForIndex(HighlightCell * cell, in
}
void MenuController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY, bool withinTemporarySelection) {
if (!withinTemporarySelection && selectedRow() == numberOfRows() - 1 && selectedColumn() == 1 && m_shouldDisplayAddScriptRow) {
if (selectedRow() == numberOfRows() - 1 && selectedColumn() == 1 && m_shouldDisplayAddScriptRow) {
t->selectCellAtLocation(0, numberOfRows()-1);
}
}

View File

@@ -99,7 +99,8 @@ const ToolboxMessageTree KandinskyModuleChildren[] = {
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandGetPixel, I18n::Message::PythonGetPixel),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandSetPixel, I18n::Message::PythonSetPixel),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandColor, I18n::Message::PythonColor),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawString, I18n::Message::PythonDrawString)
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandDrawString, I18n::Message::PythonDrawString),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillRect, I18n::Message::PythonFillRect)
};
const ToolboxMessageTree RandomModuleChildren[] = {
@@ -216,6 +217,7 @@ const ToolboxMessageTree catalogChildren[] = {
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandExp, I18n::Message::PythonExp),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandExpm1, I18n::Message::PythonExpm1),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFabs, I18n::Message::PythonFabs),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFillRect, I18n::Message::PythonFillRect),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFloat, I18n::Message::PythonFloat),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFloor, I18n::Message::PythonFloor),
ToolboxMessageTree::Leaf(I18n::Message::PythonTurtleCommandForward, I18n::Message::PythonTurtleForward),

View File

@@ -23,7 +23,7 @@ const Image * App::Descriptor::icon() {
App::Snapshot::Snapshot() :
Shared::FunctionApp::Snapshot::Snapshot(),
m_functionStore(),
m_graphRange(&m_cursor)
m_graphRange()
{
}

View File

@@ -1,37 +1,30 @@
#include "banner_view.h"
#include <assert.h>
#include <apps/i18n.h>
namespace Graph {
BannerView::BannerView() :
m_abscissaView(KDFont::SmallFont, 0.5f, 0.5f, KDColorBlack, Palette::GreyMiddle),
m_functionView(KDFont::SmallFont, 0.5f, 0.5f, KDColorBlack, Palette::GreyMiddle),
m_derivativeView(KDFont::SmallFont, 0.5f, 0.5f, KDColorBlack, Palette::GreyMiddle),
m_tangentEquationView(KDFont::SmallFont, I18n::Message::LinearRegressionFormula, 0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle),
m_aView(KDFont::SmallFont, 0.5f, 0.5f, KDColorBlack, Palette::GreyMiddle),
m_bView(KDFont::SmallFont, 0.5f, 0.5f, KDColorBlack, Palette::GreyMiddle),
m_numberOfSubviews(2)
BannerView::BannerView(
Responder * parentResponder,
InputEventHandlerDelegate * inputEventHandlerDelegate,
TextFieldDelegate * textFieldDelegate
) :
Shared::XYBannerView(parentResponder, inputEventHandlerDelegate, textFieldDelegate),
m_derivativeView(Font(), 0.5f, 0.5f, TextColor(), BackgroundColor()),
m_tangentEquationView(Font(), I18n::Message::LinearRegressionFormula, 0.0f, 0.5f, TextColor(), BackgroundColor()),
m_aView(Font(), 0.5f, 0.5f, TextColor(), BackgroundColor()),
m_bView(Font(), 0.5f, 0.5f, TextColor(), BackgroundColor()),
m_numberOfSubviews(Shared::XYBannerView::k_numberOfSubviews)
{
}
void BannerView::setNumberOfSubviews(int numberOfSubviews) {
m_numberOfSubviews = numberOfSubviews;
}
int BannerView::numberOfSubviews() const {
return m_numberOfSubviews;
}
TextView * BannerView::textViewAtIndex(int i) const {
const TextView * textViews[6] = {&m_abscissaView, &m_functionView, &m_derivativeView, &m_tangentEquationView, &m_aView, &m_bView};
return (TextView *)textViews[i];
}
MessageTextView * BannerView::messageTextViewAtIndex(int i) const {
if (i == 3) {
return (MessageTextView *)&m_tangentEquationView;
View * BannerView::subviewAtIndex(int index) {
assert(0 <= index && index < numberOfSubviews());
if (index < Shared::XYBannerView::k_numberOfSubviews) {
return Shared::XYBannerView::subviewAtIndex(index);
}
return nullptr;
View * subviews[] = {&m_derivativeView, &m_tangentEquationView, &m_aView, &m_bView};
return subviews[index - Shared::XYBannerView::k_numberOfSubviews];
}
}

View File

@@ -1,20 +1,25 @@
#ifndef GRAPH_BANNER_VIEW_H
#define GRAPH_BANNER_VIEW_H
#include "../../shared/banner_view.h"
#include "../../shared/xy_banner_view.h"
namespace Graph {
class BannerView : public Shared::BannerView {
class BannerView : public Shared::XYBannerView {
public:
BannerView();
void setNumberOfSubviews(int numberOfSubviews);
BannerView(
Responder * parentResponder,
InputEventHandlerDelegate * inputEventHandlerDelegate,
TextFieldDelegate * textFieldDelegate
);
BufferTextView * derivativeView() { return &m_derivativeView; }
BufferTextView * aView() { return &m_aView; }
BufferTextView * bView() { return &m_bView; }
void setNumberOfSubviews(int numberOfSubviews) { m_numberOfSubviews = numberOfSubviews; }
static constexpr int k_numberOfSubviews = Shared::XYBannerView::k_numberOfSubviews + 4;
private:
int numberOfSubviews() const override;
TextView * textViewAtIndex(int i) const override;
MessageTextView * messageTextViewAtIndex(int i) const override;
BufferTextView m_abscissaView;
BufferTextView m_functionView;
int numberOfSubviews() const override { return m_numberOfSubviews; }
View * subviewAtIndex(int index) override;
BufferTextView m_derivativeView;
MessageTextView m_tangentEquationView;
BufferTextView m_aView;

View File

@@ -7,21 +7,16 @@ using namespace Poincare;
namespace Graph {
CalculationGraphController::CalculationGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, I18n::Message defaultMessage) :
ViewController(parentResponder),
SimpleInteractiveCurveViewController(parentResponder, cursor),
m_graphView(graphView),
m_bannerView(bannerView),
m_graphRange(curveViewRange),
m_cursor(cursor),
m_record(),
m_defaultBannerView(KDFont::SmallFont, defaultMessage, 0.5f, 0.5f, KDColorBlack, Palette::GreyMiddle),
m_defaultBannerView(BannerView::Font(), defaultMessage, 0.5f, 0.5f, BannerView::TextColor(), BannerView::BackgroundColor()),
m_isActive(false)
{
}
View * CalculationGraphController::view() {
return m_graphView;
}
void CalculationGraphController::viewWillAppear() {
assert(!m_record.isNull());
Expression::Coordinate2D pointOfInterest = computeNewPointOfInteresetFromAbscissa(m_graphRange->xMin(), 1);
@@ -32,50 +27,23 @@ void CalculationGraphController::viewWillAppear() {
} else {
m_isActive = true;
m_cursor->moveTo(pointOfInterest.abscissa, pointOfInterest.value);
m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), k_cursorTopMarginRatio, SimpleInteractiveCurveViewController::k_cursorRightMarginRatio, k_cursorBottomMarginRatio, SimpleInteractiveCurveViewController::k_cursorLeftMarginRatio);
m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
m_bannerView->setNumberOfSubviews(Shared::XYBannerView::k_numberOfSubviews);
reloadBannerView();
}
m_graphView->setOkView(nullptr);
m_graphView->reload();
}
bool CalculationGraphController::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::EXE || event == Ion::Events::OK) {
StackViewController * stack = static_cast<StackViewController *>(parentResponder());
stack->pop();
return true;
}
if (m_isActive && (event == Ion::Events::Right || event == Ion::Events::Left)) {
int direction = event == Ion::Events::Right ? 1 : -1;
if (moveCursor(direction)) {
reloadBannerView();
m_graphView->reload();
return true;
}
}
return false;
}
void CalculationGraphController::setRecord(Ion::Storage::Record record) {
m_graphView->selectRecord(record);
m_record = record;
}
void CalculationGraphController::reloadBannerView() {
m_bannerView->setNumberOfSubviews(2);
reloadBannerViewForCursorOnFunction(m_cursor, m_record, functionStore(), CartesianFunction::Symbol());
}
bool CalculationGraphController::moveCursor(int direction) {
Expression::Coordinate2D newPointOfInterest = computeNewPointOfInteresetFromAbscissa(m_cursor->x(), direction);
if (std::isnan(newPointOfInterest.abscissa)) {
return false;
}
m_cursor->moveTo(newPointOfInterest.abscissa, newPointOfInterest.value);
m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), k_cursorTopMarginRatio, SimpleInteractiveCurveViewController::k_cursorRightMarginRatio, k_cursorBottomMarginRatio, SimpleInteractiveCurveViewController::k_cursorLeftMarginRatio);
return true;
}
Expression::Coordinate2D CalculationGraphController::computeNewPointOfInteresetFromAbscissa(double start, int direction) {
App * myApp = static_cast<App *>(app());
double step = m_graphRange->xGridUnit()/10.0;
@@ -89,4 +57,26 @@ CartesianFunctionStore * CalculationGraphController::functionStore() const {
return a->functionStore();
}
bool CalculationGraphController::handleLeftRightEvent(Ion::Events::Event event) {
if (!m_isActive) {
return false;
}
return SimpleInteractiveCurveViewController::handleLeftRightEvent(event);
}
bool CalculationGraphController::handleEnter() {
StackViewController * stack = static_cast<StackViewController *>(parentResponder());
stack->pop();
return true;
}
bool CalculationGraphController::moveCursorHorizontally(int direction) {
Expression::Coordinate2D newPointOfInterest = computeNewPointOfInteresetFromAbscissa(m_cursor->x(), direction);
if (std::isnan(newPointOfInterest.abscissa)) {
return false;
}
m_cursor->moveTo(newPointOfInterest.abscissa, newPointOfInterest.value);
return true;
}
}

View File

@@ -3,8 +3,7 @@
#include "graph_view.h"
#include "banner_view.h"
#include "../../shared/curve_view_cursor.h"
#include "../../shared/interactive_curve_view_range.h"
#include "../../shared/simple_interactive_curve_view_controller.h"
#include "../../shared/function_banner_delegate.h"
#include "../cartesian_function_store.h"
@@ -12,29 +11,31 @@ namespace Graph {
class App;
class CalculationGraphController : public ViewController, public Shared::FunctionBannerDelegate {
class CalculationGraphController : public Shared::SimpleInteractiveCurveViewController, public Shared::FunctionBannerDelegate {
public:
CalculationGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor, I18n::Message defaultMessage);
View * view() override;
void viewWillAppear() override;
bool handleEvent(Ion::Events::Event event) override;
void setRecord(Ion::Storage::Record record);
protected:
constexpr static float k_cursorTopMarginRatio = 0.07f; // (cursorHeight/2)/graphViewHeight
constexpr static float k_cursorBottomMarginRatio = 0.15f; // (cursorHeight/2+bannerHeigh)/graphViewHeight
float cursorBottomMarginRatio() override { return 0.15f; }
BannerView * bannerView() override { return m_bannerView; }
virtual void reloadBannerView();
bool moveCursor(int direction);
void reloadBannerView() override;
Poincare::Expression::Coordinate2D computeNewPointOfInteresetFromAbscissa(double start, int direction);
CartesianFunctionStore * functionStore() const;
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;
Shared::CurveViewCursor * m_cursor;
Ion::Storage::Record m_record;
MessageTextView m_defaultBannerView;
bool m_isActive;
private:
bool handleZoom(Ion::Events::Event event) override { return false; }
bool handleLeftRightEvent(Ion::Events::Event event) override;
bool handleEnter() override;
bool moveCursorHorizontally(int direction) override;
Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; }
Shared::CurveView * curveView() override { return m_graphView; }
};
}

View File

@@ -8,7 +8,7 @@ using namespace Shared;
namespace Graph {
CurveParameterController::CurveParameterController(InputEventHandlerDelegate * inputEventHandlerDelegate, InteractiveCurveViewRange * graphRange, BannerView * bannerView, CurveViewCursor * cursor, GraphView * graphView, GraphController * graphController) :
FunctionCurveParameterController(graphRange, cursor),
FunctionCurveParameterController(),
m_goToParameterController(this, inputEventHandlerDelegate, graphRange, cursor, I18n::Message::X),
m_graphController(graphController),
m_calculationCell(I18n::Message::Compute),

View File

@@ -9,7 +9,7 @@ static inline float maxFloat(float x, float y) { return x > y ? x : y; }
GraphController::GraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, CartesianFunctionStore * functionStore, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header) :
FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, curveViewRange, &m_view, cursor, indexFunctionSelectedByCursor, modelVersion, rangeVersion, angleUnitVersion),
m_bannerView(),
m_bannerView(this, inputEventHandlerDelegate, this),
m_view(functionStore, curveViewRange, m_cursor, &m_bannerView, &m_cursorView),
m_graphRange(curveViewRange),
m_curveParameterController(inputEventHandlerDelegate, curveViewRange, &m_bannerView, m_cursor, &m_view, this),
@@ -27,6 +27,8 @@ I18n::Message GraphController::emptyMessage() {
void GraphController::viewWillAppear() {
m_view.drawTangent(false);
m_view.setCursorView(&m_cursorView);
m_bannerView.setNumberOfSubviews(Shared::XYBannerView::k_numberOfSubviews + m_displayDerivativeInBanner);
FunctionGraphController::viewWillAppear();
selectFunctionWithCursor(indexFunctionSelectedByCursor()); // update the color of the cursor
}
@@ -58,7 +60,7 @@ int GraphController::estimatedBannerNumberOfLines() const {
void GraphController::selectFunctionWithCursor(int functionIndex) {
FunctionGraphController::selectFunctionWithCursor(functionIndex);
ExpiringPointer<CartesianFunction> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor()));
ExpiringPointer<CartesianFunction> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(functionIndex));
m_cursorView.setColor(f->color());
}
@@ -68,7 +70,6 @@ BannerView * GraphController::bannerView() {
void GraphController::reloadBannerView() {
FunctionGraphController::reloadBannerView();
m_bannerView.setNumberOfSubviews(2+m_displayDerivativeInBanner);
if (functionStore()->numberOfActiveFunctions() == 0 || !m_displayDerivativeInBanner) {
return;
}
@@ -80,7 +81,7 @@ void GraphController::reloadBannerView() {
bool GraphController::moveCursorHorizontally(int direction) {
Ion::Storage::Record record = functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor());
App * myApp = static_cast<App *>(app());
return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, record, myApp, cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, record, myApp);
}
InteractiveCurveViewRange * GraphController::interactiveCurveViewRange() {

View File

@@ -29,9 +29,6 @@ private:
bool moveCursorHorizontally(int direction) override;
Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override;
GraphView * functionGraphView() override;
View * cursorView() override {
return &m_cursorView;
}
CurveParameterController * curveParameterController() override;
CartesianFunctionStore * functionStore() const override { return static_cast<CartesianFunctionStore *>(Shared::FunctionGraphController::functionStore()); }
Shared::RoundCursorView m_cursorView;

View File

@@ -9,13 +9,12 @@ using namespace Poincare;
namespace Graph {
bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, App * app, float cursorTopMarginRatio, float cursorRightMarginRatio, float cursorBottomMarginRatio, float cursorLeftMarginRatio) {
bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, App * app) {
ExpiringPointer<CartesianFunction> function = app->functionStore()->modelForRecord(record);
double xCursorPosition = cursor->x();
double x = direction > 0 ? xCursorPosition + range->xGridUnit()/numberOfStepsInGradUnit : xCursorPosition - range->xGridUnit()/numberOfStepsInGradUnit;
double y = function->evaluateAtAbscissa(x, app->localContext());
cursor->moveTo(x, y);
range->panToMakePointVisible(x, y, cursorTopMarginRatio, cursorRightMarginRatio, cursorBottomMarginRatio, cursorLeftMarginRatio);
return true;
}
@@ -30,7 +29,8 @@ void GraphControllerHelper::reloadDerivativeInBannerViewForCursorOnFunction(Shar
double y = function->approximateDerivative(cursor->x(), app->localContext());
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(y, buffer + numberOfChar, bufferSize-numberOfChar, Constant::ShortNumberOfSignificantDigits);
strlcpy(buffer+numberOfChar, space, bufferSize-numberOfChar);
bannerView()->setLegendAtIndex(buffer, 2);
bannerView()->derivativeView()->setText(buffer);
bannerView()->reload();
}
}

View File

@@ -1,10 +1,9 @@
#ifndef GRAPH_GRAPH_CONTROLLER_HELPER_H
#define GRAPH_GRAPH_CONTROLLER_HELPER_H
#include "../../shared/banner_view.h"
#include "../../shared/text_field_delegate_app.h"
#include "banner_view.h"
#include "../../shared/curve_view_cursor.h"
#include "../../shared/interactive_curve_view_range.h"
#include "../cartesian_function_store.h"
namespace Graph {
@@ -12,9 +11,9 @@ class App;
class GraphControllerHelper {
protected:
bool privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, App * app, float cursorTopMarginRatio, float cursorRightMarginRatio, float cursorBottomMarginRatio, float cursorLeftMarginRatio);
bool privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Ion::Storage::Record record, App * app);
void reloadDerivativeInBannerViewForCursorOnFunction(Shared::CurveViewCursor * cursor, Ion::Storage::Record record, App * app);
virtual Shared::BannerView * bannerView() = 0;
virtual BannerView * bannerView() = 0;
};
}

View File

@@ -17,8 +17,7 @@ const char * IntersectionGraphController::title() {
}
void IntersectionGraphController::reloadBannerView() {
m_bannerView->setNumberOfSubviews(2);
reloadBannerViewForCursorOnFunction(m_cursor, m_record, functionStore(), CartesianFunction::Symbol());
CalculationGraphController::reloadBannerView();
constexpr size_t bufferSize = FunctionBannerDelegate::k_maxNumberOfCharacters+Poincare::PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits);
char buffer[bufferSize];
const char * space = " ";
@@ -33,7 +32,8 @@ void IntersectionGraphController::reloadBannerView() {
numberOfChar += strlcpy(buffer+numberOfChar, legend, bufferSize-numberOfChar);
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(m_cursor->y(), buffer+numberOfChar, bufferSize-numberOfChar, Constant::MediumNumberOfSignificantDigits);
strlcpy(buffer+numberOfChar, space, bufferSize-numberOfChar);
bannerView()->setLegendAtIndex(buffer, 1);
bannerView()->ordinateView()->setText(buffer);
bannerView()->reload();
}
Poincare::Expression::Coordinate2D IntersectionGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) {

View File

@@ -8,7 +8,7 @@ using namespace Poincare;
namespace Graph {
TangentGraphController::TangentGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor) :
SimpleInteractiveCurveViewController(parentResponder, curveViewRange, graphView, cursor),
SimpleInteractiveCurveViewController(parentResponder, cursor),
m_graphView(graphView),
m_bannerView(bannerView),
m_graphRange(curveViewRange),
@@ -21,21 +21,44 @@ const char * TangentGraphController::title() {
}
void TangentGraphController::viewWillAppear() {
m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio);
m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
m_graphView->drawTangent(true);
m_graphView->setOkView(nullptr);
m_graphView->selectMainView(true);
m_bannerView->setNumberOfSubviews(BannerView::k_numberOfSubviews);
reloadBannerView();
m_graphView->reload();
}
void TangentGraphController::didBecomeFirstResponder() {
if (curveView()->isMainViewSelected()) {
m_bannerView->abscissaValue()->setParentResponder(this);
m_bannerView->abscissaValue()->setDelegates(textFieldDelegateApp(), this);
app()->setFirstResponder(m_bannerView->abscissaValue());
}
}
bool TangentGraphController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) {
double floatBody;
if (textFieldDelegateApp()->hasUndefinedValue(text, floatBody)) {
return false;
}
App * myApp = static_cast<App *>(app());
ExpiringPointer<CartesianFunction> function = myApp->functionStore()->modelForRecord(m_record);
double y = function->evaluateAtAbscissa(floatBody, textFieldDelegateApp()->localContext());
m_cursor->moveTo(floatBody, y);
interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
reloadBannerView();
curveView()->reload();
return true;
}
void TangentGraphController::setRecord(Ion::Storage::Record record) {
m_graphView->selectRecord(record);
m_record = record;
}
void TangentGraphController::reloadBannerView() {
m_bannerView->setNumberOfSubviews(6);
if (m_record.isNull()) {
return;
}
@@ -49,18 +72,19 @@ void TangentGraphController::reloadBannerView() {
ExpiringPointer<CartesianFunction> function = myApp->functionStore()->modelForRecord(m_record);
double y = function->approximateDerivative(m_cursor->x(), myApp->localContext());
PoincareHelpers::ConvertFloatToText<double>(y, buffer + legendLength, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits);
m_bannerView->setLegendAtIndex(buffer, 4);
m_bannerView->aView()->setText(buffer);
legend = "b=";
legendLength = strlcpy(buffer, legend, bufferSize);
y = -y*m_cursor->x()+function->evaluateAtAbscissa(m_cursor->x(), myApp->localContext());
PoincareHelpers::ConvertFloatToText<double>(y, buffer + legendLength, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits);
m_bannerView->setLegendAtIndex(buffer, 5);
m_bannerView->bView()->setText(buffer);
m_bannerView->reload();
}
bool TangentGraphController::moveCursorHorizontally(int direction) {
App * myApp = static_cast<App *>(app());
return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, m_record, myApp, k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio);
return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, m_record, myApp);
}
bool TangentGraphController::handleEnter() {

View File

@@ -15,10 +15,11 @@ public:
TangentGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor);
const char * title() override;
void viewWillAppear() override;
void didBecomeFirstResponder() override;
bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override;
void setRecord(Ion::Storage::Record record);
private:
constexpr static float k_cursorTopMarginRatio = 0.07f; // (cursorHeight/2)/graphViewHeight
constexpr static float k_cursorBottomMarginRatio = 0.22f; // (cursorHeight/2+bannerHeigh)/graphViewHeight
float cursorBottomMarginRatio() override { return 0.22f; }
Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; }
Shared::CurveView * curveView() override { return m_graphView; }
BannerView * bannerView() override { return m_bannerView; };

View File

@@ -212,11 +212,8 @@ bool CalculationController::textFieldShouldFinishEditing(TextField * textField,
}
bool CalculationController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) {
App * probaApp = (App *)app();
Context * globalContext = probaApp->container()->globalContext();
double floatBody = PoincareHelpers::ApproximateToScalar<double>(text, *globalContext);
if (std::isnan(floatBody) || std::isinf(floatBody)) {
app()->displayWarning(I18n::Message::UndefinedValue);
double floatBody;
if (textFieldDelegateApp()->hasUndefinedValue(text, floatBody)) {
return false;
}
if (m_calculation->type() != Calculation::Type::FiniteIntegral && selectedColumn() == 2) {

View File

@@ -1,37 +1,44 @@
#include "banner_view.h"
#include <assert.h>
namespace Regression {
constexpr KDColor BannerView::k_textColor;
constexpr KDColor BannerView::k_backgroundColor;
BannerView::BannerView() :
m_dotNameView(k_font, 0.0f, 0.5f, k_textColor, k_backgroundColor),
m_xView(k_font, 0.5f, 0.5f, k_textColor, k_backgroundColor),
m_yView(k_font, 0.5f, 0.5f, k_textColor, k_backgroundColor),
m_regressionTypeView(k_font, (I18n::Message)0, 0.0f, 0.5f, k_textColor,k_backgroundColor),
m_subText1(k_font, 0.5f, 0.5f, k_textColor, k_backgroundColor),
m_subText2(k_font, 0.5f, 0.5f, k_textColor, k_backgroundColor),
m_subText3(k_font, 0.5f, 0.5f, k_textColor, k_backgroundColor),
m_subText4(k_font, 0.5f, 0.5f, k_textColor, k_backgroundColor),
m_subText5(k_font, 0.5f, 0.5f, k_textColor, k_backgroundColor)
BannerView::BannerView(
Responder * parentResponder,
InputEventHandlerDelegate * inputEventHandlerDelegate,
TextFieldDelegate * textFieldDelegate
) :
Shared::XYBannerView(parentResponder, inputEventHandlerDelegate, textFieldDelegate),
m_dotNameView(Font(), 0.0f, 0.5f, TextColor(), BackgroundColor()),
m_regressionTypeView(Font(), (I18n::Message)0, 0.0f, 0.5f, TextColor(), BackgroundColor()),
m_subText0(Font(), 0.5f, 0.5f, TextColor(), BackgroundColor()),
m_subText1(Font(), 0.5f, 0.5f, TextColor(), BackgroundColor()),
m_subText2(Font(), 0.5f, 0.5f, TextColor(), BackgroundColor()),
m_subText3(Font(), 0.5f, 0.5f, TextColor(), BackgroundColor()),
m_subText4(Font(), 0.5f, 0.5f, TextColor(), BackgroundColor())
{
}
int BannerView::numberOfSubviews() const {
return k_numberOfTextViews;
BufferTextView * BannerView::subTextAtIndex(int index) {
assert(0 <= index && index < numberOfsubTexts());
BufferTextView * subTexts[numberOfsubTexts()] = {&m_subText0, &m_subText1, &m_subText2, &m_subText3, &m_subText4};
return subTexts[index];
}
TextView * BannerView::textViewAtIndex(int i) const {
const TextView * textViews[k_numberOfTextViews] = {&m_dotNameView, &m_xView, &m_yView, &m_regressionTypeView, &m_subText1, &m_subText2, &m_subText3, &m_subText4, &m_subText5};
return (TextView *)textViews[i];
}
MessageTextView * BannerView::messageTextViewAtIndex(int i) const {
if (i == 3) {
return (MessageTextView *)&m_regressionTypeView;
View * BannerView::subviewAtIndex(int index) {
assert(0 <= index && index < numberOfSubviews());
if (index == 0) {
return &m_dotNameView;
}
return nullptr;
index--;
if (index < Shared::XYBannerView::k_numberOfSubviews) {
return Shared::XYBannerView::subviewAtIndex(index);
}
index -= Shared::XYBannerView::k_numberOfSubviews;
if (index == 0) {
return &m_regressionTypeView;
}
return subTextAtIndex(index - 1);
}
}

View File

@@ -1,33 +1,32 @@
#ifndef REGRESSION_BANNER_VIEW_H
#define REGRESSION_BANNER_VIEW_H
#include <escher.h>
#include "../shared/banner_view.h"
#include "../shared/xy_banner_view.h"
namespace Regression {
class BannerView : public Shared::BannerView {
class BannerView : public Shared::XYBannerView {
public:
BannerView();
int numberOfTextviews() const { return k_numberOfTextViews; }
const KDFont * font() const { return k_font; }
BannerView(
Responder * parentResponder,
InputEventHandlerDelegate * inputEventHandlerDelegate,
TextFieldDelegate * textFieldDelegate
);
BufferTextView * dotNameView() { return &m_dotNameView; }
MessageTextView * regressionTypeView() { return &m_regressionTypeView; }
BufferTextView * subTextAtIndex(int index);
static constexpr int numberOfsubTexts() { return 5; }
private:
static constexpr const KDFont * k_font = KDFont::SmallFont;
static constexpr KDColor k_textColor = KDColorBlack;
static constexpr KDColor k_backgroundColor = Palette::GreyMiddle;
static constexpr int k_numberOfTextViews = 9;
int numberOfSubviews() const override;
TextView * textViewAtIndex(int i) const override;
MessageTextView * messageTextViewAtIndex(int i) const override;
static constexpr int k_numberOfSubviews = Shared::XYBannerView::k_numberOfSubviews + 7;
int numberOfSubviews() const override { return k_numberOfSubviews; }
View * subviewAtIndex(int index) override;
BufferTextView m_dotNameView;
BufferTextView m_xView;
BufferTextView m_yView;
MessageTextView m_regressionTypeView;
BufferTextView m_subText0;
BufferTextView m_subText1;
BufferTextView m_subText2;
BufferTextView m_subText3;
BufferTextView m_subText4;
BufferTextView m_subText5;
};
}

View File

@@ -40,22 +40,12 @@ double GoToParameterController::parameterAtIndex(int index) {
bool GoToParameterController::setParameterAtIndex(int parameterIndex, double f) {
assert(parameterIndex == 0);
int series = m_graphController->selectedSeriesIndex();
if (std::fabs(f) > k_maxDisplayableFloat) {
app()->displayWarning(I18n::Message::ForbiddenValue);
return false;
}
Poincare::Context * globContext = const_cast<AppsContainer *>(static_cast<const AppsContainer *>(app()->container()))->globalContext();
double unknown = m_xPrediction ?
m_store->yValueForXValue(series, f, globContext) :
m_store->xValueForYValue(series, f, globContext);
// Forbidden value
if (std::fabs(unknown) > k_maxDisplayableFloat) {
app()->displayWarning(I18n::Message::ForbiddenValue);
return false;
}
if (std::isnan(unknown)) {
if (std::isnan(unknown) || std::isinf(unknown)) {
if (!m_xPrediction) {
double x = m_cursor->x();
unknown = m_store->modelForSeries(series)->evaluate(m_store->coefficientsForSeries(series, globContext), x);
@@ -77,7 +67,7 @@ bool GoToParameterController::setParameterAtIndex(int parameterIndex, double f)
double yFromX = m_store->modelForSeries(series)->evaluate(m_store->coefficientsForSeries(series, globContext), unknown);
/* We here compute y2 = a*((y1-b)/a)+b, which does not always give y1,
* because of computation precision. y2 migth thus be invalid. */
if (std::fabs(yFromX) > k_maxDisplayableFloat || std::isnan(yFromX)) {
if (std::isnan(yFromX) || std::isinf(yFromX)) {
app()->displayWarning(I18n::Message::ForbiddenValue);
return false;
}

View File

@@ -1,7 +1,6 @@
#include "graph_controller.h"
#include "../shared/poincare_helpers.h"
#include "../apps_container.h"
#include <kandinsky/font.h>
#include <cmath>
using namespace Poincare;
@@ -17,7 +16,7 @@ GraphController::GraphController(Responder * parentResponder, InputEventHandlerD
InteractiveCurveViewController(parentResponder, inputEventHandlerDelegate, header, store, &m_view, cursor, modelVersion, rangeVersion),
m_crossCursorView(),
m_roundCursorView(),
m_bannerView(),
m_bannerView(this, inputEventHandlerDelegate, this),
m_view(store, m_cursor, &m_bannerView, &m_crossCursorView, this),
m_store(store),
m_initialisationParameterController(this, m_store),
@@ -28,7 +27,6 @@ GraphController::GraphController(Responder * parentResponder, InputEventHandlerD
for (int i = 0; i < Store::k_numberOfSeries; i++) {
m_modelType[i] = (Model::Type) -1;
}
m_store->setCursor(m_cursor);
m_store->setDelegate(this);
}
@@ -45,16 +43,43 @@ I18n::Message GraphController::emptyMessage() {
}
void GraphController::viewWillAppear() {
InteractiveCurveViewController::viewWillAppear();
if (m_modelType[*m_selectedSeriesIndex] != m_store->seriesRegressionType(*m_selectedSeriesIndex)) {
initCursorParameters();
}
for (int i = 0; i < Store::k_numberOfSeries; i++) {
m_modelType[i] = m_store->seriesRegressionType(*m_selectedSeriesIndex);
}
/* At this point, there is at least one non-empty series.
* However, before the graph view appears for the very first time,
* *m_selectedSeriesIndex is set to -1. Thus one needs to select a series.
* TODO Perhaps the three following lines should be moved elsewhere. */
if (*m_selectedSeriesIndex < 0) {
*m_selectedSeriesIndex = m_store->indexOfKthNonEmptySeries(0);
}
/* Both the GraphController and the Store hold the Model::Type of each
* series. The values differ in two cases:
* 1) the very first time the graph view appears
* 2) when the user selects another Model::Type for a series.
* where we decide to place the cursor at a default position. */
if (m_modelType[*m_selectedSeriesIndex] != m_store->seriesRegressionType(*m_selectedSeriesIndex)) {
initCursorParameters();
}
/* Equalize the Model::Type of each series between the GraphController and
* the Store.
* TODO In passing, one may probably avoid keeping the Model::Type of each
* series in two places:
* 1) call initCursorParameters elsewhere the very first time the graph view
* appears,
* 2) take into account the Model::Type in the computation of the
* storeChecksum in order to detect any change in the series and in
* their model types. */
for (int i = 0; i < Store::k_numberOfSeries; i++) {
m_modelType[i] = m_store->seriesRegressionType(*m_selectedSeriesIndex);
}
/* The following
* - calls initCursorParameters() if necessary,
* - reloads the bannerView and the curveView. */
InteractiveCurveViewController::viewWillAppear();
/* Since *m_selectedDotIndex is altered by initCursorParameters(),
* the following must absolutely come at the end. */
if (*m_selectedDotIndex >= 0) {
m_view.setCursorView(static_cast<View *>(&m_crossCursorView));
} else {
@@ -75,26 +100,9 @@ Poincare::Context * GraphController::globalContext() {
return const_cast<AppsContainer *>(static_cast<const AppsContainer *>(app()->container()))->globalContext();
}
float GraphController::cursorBottomMarginRatio() {
float f = (m_view.cursorView()->minimalSizeForOptimalDisplay().height()/2 + 2 + estimatedBannerHeight())/k_viewHeight;
return f;
}
float GraphController::estimatedBannerHeight() const {
if (selectedSeriesIndex() < 0) {
return KDFont::SmallFont->glyphSize().height() * 3;
}
float result = KDFont::SmallFont->glyphSize().height() * m_store->modelForSeries(selectedSeriesIndex())->bannerLinesCount();
return result;
}
// SimpleInteractiveCurveViewController
void GraphController::reloadBannerView() {
if (*m_selectedSeriesIndex < 0) {
return;
}
// Set point equals: "P(...) ="
constexpr size_t bufferSize = k_maxNumberOfCharacters + PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits);
char buffer[bufferSize];
@@ -113,10 +121,9 @@ void GraphController::reloadBannerView() {
legend = ") ";
strlcpy(buffer+numberOfChar, legend, bufferSize - numberOfChar);
buffer[k_maxLegendLength] = 0;
m_bannerView.setLegendAtIndex(buffer, 0);
m_bannerView.dotNameView()->setText(buffer);
// Set "x=..." or "xmean=..."
numberOfChar = 0;
legend = "x=";
double x = m_cursor->x();
// Display a specific legend if the mean dot is selected
@@ -125,14 +132,15 @@ void GraphController::reloadBannerView() {
legend = "x\xCC\x85=";
x = m_store->meanOfColumn(*m_selectedSeriesIndex, 0);
}
numberOfChar += strlcpy(buffer, legend, bufferSize);
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(x, buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits);
m_bannerView.abscissaSymbol()->setText(legend);
numberOfChar = PoincareHelpers::ConvertFloatToText<double>(x, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits);
assert(UTF8Decoder::CharSizeOfCodePoint(' ') == 1);
for (int i = numberOfChar; i < k_maxLegendLength; i++) {
buffer[numberOfChar++] = ' ';
}
buffer[k_maxLegendLength] = 0;
m_bannerView.setLegendAtIndex(buffer, 1);
m_bannerView.abscissaValue()->setText(buffer);
// Set "y=..." or "ymean=..."
numberOfChar = 0;
@@ -149,15 +157,15 @@ void GraphController::reloadBannerView() {
buffer[numberOfChar++] = ' ';
}
buffer[k_maxLegendLength] = 0;
m_bannerView.setLegendAtIndex(buffer, 2);
m_bannerView.ordinateView()->setText(buffer);
// Set formula
Model * model = m_store->modelForSeries(selectedSeriesIndex());
Model * model = m_store->modelForSeries(*m_selectedSeriesIndex);
I18n::Message formula = model->formulaMessage();
m_bannerView.setMessageAtIndex(formula, 3);
m_bannerView.regressionTypeView()->setMessage(formula);
// Get the coefficients
double * coefficients = m_store->coefficientsForSeries(selectedSeriesIndex(), globalContext());
double * coefficients = m_store->coefficientsForSeries(*m_selectedSeriesIndex, globalContext());
bool coefficientsAreDefined = true;
for (int i = 0; i < model->numberOfCoefficients(); i++) {
if (std::isnan(coefficients[i])) {
@@ -167,19 +175,18 @@ void GraphController::reloadBannerView() {
}
if (!coefficientsAreDefined) {
// Force the "Data not suitable" message to be on the next line
int numberOfCharToCompleteLine = maxInt(Ion::Display::Width/(m_bannerView.font()->glyphSize().width())- strlen(I18n::translate(formula)), 0);
int numberOfCharToCompleteLine = maxInt(Ion::Display::Width / BannerView::Font()->glyphSize().width() - strlen(I18n::translate(formula)), 0);
numberOfChar = 0;
for (int i = 0; i < numberOfCharToCompleteLine-1; i++) {
buffer[numberOfChar++] = ' ';
}
buffer[numberOfChar] = 0;
m_bannerView.setLegendAtIndex(buffer, 4);
m_bannerView.subTextAtIndex(0)->setText(buffer);
const char * dataNotSuitableMessage = I18n::translate(I18n::Message::DataNotSuitableForRegression);
m_bannerView.setLegendAtIndex(const_cast<char *>(dataNotSuitableMessage), 5);
for (int i = 6; i < m_bannerView.numberOfTextviews(); i++) {
char empty[] = {0};
m_bannerView.setLegendAtIndex(empty, i);
m_bannerView.subTextAtIndex(1)->setText(const_cast<char *>(dataNotSuitableMessage));
for (int i = 2; i < m_bannerView.numberOfsubTexts(); i++) {
m_bannerView.subTextAtIndex(i)->setText("");
}
return;
}
@@ -191,11 +198,11 @@ void GraphController::reloadBannerView() {
numberOfChar += strlcpy(buffer, legend, bufferSize);
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(coefficients[i], buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits);
buffer[k_maxLegendLength] = 0;
m_bannerView.setLegendAtIndex(buffer, 4 + i);
m_bannerView.subTextAtIndex(i)->setText(buffer);
coefficientName++;
}
if (m_store->seriesRegressionType(selectedSeriesIndex()) == Model::Type::Linear) {
if (m_store->seriesRegressionType(*m_selectedSeriesIndex) == Model::Type::Linear) {
// Set "r=..."
numberOfChar = 0;
legend = " r=";
@@ -203,7 +210,7 @@ void GraphController::reloadBannerView() {
numberOfChar += strlcpy(buffer, legend, bufferSize);
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(r, buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits);
buffer[k_maxLegendLength+10] = 0;
m_bannerView.setLegendAtIndex(buffer, 6);
m_bannerView.subTextAtIndex(2)->setText(buffer);
// Set "r2=..."
numberOfChar = 0;
@@ -212,19 +219,20 @@ void GraphController::reloadBannerView() {
numberOfChar += strlcpy(buffer, legend, bufferSize);
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(r2, buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits);
buffer[k_maxLegendLength] = 0;
m_bannerView.setLegendAtIndex(buffer, 7);
m_bannerView.subTextAtIndex(3)->setText(buffer);
// Clean the last subview
buffer[0] = 0;
m_bannerView.setLegendAtIndex(buffer, 8);
m_bannerView.subTextAtIndex(4)->setText(buffer);
} else {
// Empty all non used subviews
for (int i = 4+model->numberOfCoefficients(); i < m_bannerView.numberOfTextviews(); i++) {
for (int i = model->numberOfCoefficients(); i < m_bannerView.numberOfsubTexts(); i++) {
buffer[0] = 0;
m_bannerView.setLegendAtIndex(buffer, i);
m_bannerView.subTextAtIndex(i)->setText(buffer);
}
}
m_bannerView.reload();
}
bool GraphController::moveCursorHorizontally(int direction) {
@@ -233,13 +241,11 @@ bool GraphController::moveCursorHorizontally(int direction) {
if (dotSelected >= 0 && dotSelected < m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) {
*m_selectedDotIndex = dotSelected;
m_cursor->moveTo(m_store->get(*m_selectedSeriesIndex, 0, *m_selectedDotIndex), m_store->get(*m_selectedSeriesIndex, 1, *m_selectedDotIndex));
m_store->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
return true;
}
if (dotSelected == m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) {
*m_selectedDotIndex = dotSelected;
m_cursor->moveTo(m_store->meanOfColumn(*m_selectedSeriesIndex, 0), m_store->meanOfColumn(*m_selectedSeriesIndex, 1));
m_store->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
return true;
}
return false;
@@ -248,7 +254,6 @@ bool GraphController::moveCursorHorizontally(int direction) {
m_cursor->x() - m_store->xGridUnit()/k_numberOfCursorStepsInGradUnit;
double y = yValue(*m_selectedSeriesIndex, x, globalContext());
m_cursor->moveTo(x, y);
m_store->panToMakePointVisible(x, y, cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
return true;
}
@@ -266,14 +271,7 @@ bool GraphController::handleEnter() {
}
// InteractiveCurveViewController
void GraphController::initRangeParameters() {
m_store->setDefault();
}
void GraphController::initCursorParameters() {
if (*m_selectedSeriesIndex < 0 || m_store->seriesIsEmpty(*m_selectedSeriesIndex)) {
*m_selectedSeriesIndex = m_store->indexOfKthNonEmptySeries(0);
}
double x = m_store->meanOfColumn(*m_selectedSeriesIndex, 0);
double y = m_store->meanOfColumn(*m_selectedSeriesIndex, 1);
m_cursor->moveTo(x, y);
@@ -333,7 +331,6 @@ bool GraphController::moveCursorVertically(int direction) {
*m_selectedSeriesIndex = closestRegressionSeries;
selectRegressionCurve();
m_cursor->moveTo(x, yValue(*m_selectedSeriesIndex, x, context));
m_store->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
return true;
}
@@ -345,11 +342,9 @@ bool GraphController::moveCursorVertically(int direction) {
if (dotSelected == m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) {
// Select the mean dot
m_cursor->moveTo(m_store->meanOfColumn(*m_selectedSeriesIndex, 0), m_store->meanOfColumn(*m_selectedSeriesIndex, 1));
m_store->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
} else {
// Select a data point dot
m_cursor->moveTo(m_store->get(*m_selectedSeriesIndex, 0, *m_selectedDotIndex), m_store->get(*m_selectedSeriesIndex, 1, *m_selectedDotIndex));
m_store->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
}
return true;
}
@@ -366,10 +361,6 @@ uint32_t GraphController::rangeVersion() {
return m_store->rangeChecksum();
}
bool GraphController::isCursorVisible() {
return interactiveCurveViewRange()->isCursorVisible(cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
}
bool GraphController::closestCurveIndexIsSuitable(int newIndex, int currentIndex) const {
return newIndex != currentIndex && !m_store->seriesIsEmpty(newIndex);
}
@@ -386,13 +377,8 @@ int GraphController::numberOfCurves() const {
return Store::k_numberOfSeries;
}
float GraphController::displayTopMarginRatio() {
return 0.12f; // cursorHeight/graphViewHeight
}
float GraphController::displayBottomMarginRatio() {
float f = (m_view.cursorView()->minimalSizeForOptimalDisplay().height() + 2 + estimatedBannerHeight())/k_viewHeight;
return f;
int GraphController::estimatedBannerNumberOfLines() const {
return (selectedSeriesIndex() < 0) ? 3 : m_store->modelForSeries(selectedSeriesIndex())->bannerLinesCount();
}
InteractiveCurveViewRangeDelegate::Range GraphController::computeYRange(InteractiveCurveViewRange * interactiveCurveViewRange) {

View File

@@ -31,12 +31,8 @@ public:
private:
constexpr static int k_maxLegendLength = 16;
constexpr static int k_maxNumberOfCharacters = 50;
constexpr static float k_viewHeight = 174.0f;
Poincare::Context * globalContext();
float cursorTopMarginRatio() { return 0.07f; } // (cursorHeight/2) / graphViewHeight
float cursorBottomMarginRatio();
float estimatedBannerHeight() const;
// SimpleInteractiveCurveViewController
void reloadBannerView() override;
@@ -45,19 +41,17 @@ private:
bool handleEnter() override;
// InteractiveCurveViewController
void initRangeParameters() override;
void initCursorParameters() override;
uint32_t modelVersion() override;
uint32_t rangeVersion() override;
bool isCursorVisible() override;
int selectedCurveIndex() const override { return *m_selectedSeriesIndex; }
bool closestCurveIndexIsSuitable(int newIndex, int currentIndex) const override;
double yValue(int curveIndex, double x, Poincare::Context * context) const override;
bool suitableYValue(double y) const override;
int numberOfCurves() const override;
int estimatedBannerNumberOfLines() const override;
// InteractiveCurveViewRangeDelegate
float displayTopMarginRatio() override;
float displayBottomMarginRatio() override;
Shared::InteractiveCurveViewRangeDelegate::Range computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) override;
Shared::CursorView m_crossCursorView;

View File

@@ -170,6 +170,7 @@ int Model::solveLinearSystem(double * solutions, double * coefficients, double *
for (int i = 0; i < n * n; i++) {
coefficientsSave[i] = coefficients[i];
}
assert(k_maxNumberOfCoefficients < Matrix::k_maxNumberOfCoefficients);
int inverseResult = Matrix::ArrayInverse(coefficients, n, n);
int numberOfMatrixModifications = 0;
while (inverseResult < 0 && numberOfMatrixModifications < k_maxMatrixInversionFixIterations) {

View File

@@ -48,7 +48,7 @@ private:
virtual double partialDerivate(double * modelCoefficients, int derivateCoefficientIndex, double x) const = 0;
// Levenberg-Marquardt
static constexpr double k_maxIterations = 100;
static constexpr double k_maxIterations = 300;
static constexpr double k_maxMatrixInversionFixIterations = 10;
static constexpr double k_initialLambda = 0.001;
static constexpr double k_lambdaFactor = 10;

View File

@@ -17,7 +17,7 @@ static_assert(Model::k_numberOfModels == 9, "Number of models changed, Regressio
static_assert(Store::k_numberOfSeries == 3, "Number of series changed, Regression::Store() needs to adapt (m_seriesChecksum)");
Store::Store() :
InteractiveCurveViewRange(nullptr),
InteractiveCurveViewRange(),
DoublePairStore(),
m_seriesChecksum{0, 0, 0},
m_angleUnit(Poincare::Preferences::AngleUnit::Degree)

View File

@@ -3,7 +3,6 @@ app_headers += apps/sequence/app.h
app_src += $(addprefix apps/sequence/,\
app.cpp \
graph/banner_view.cpp \
graph/curve_parameter_controller.cpp \
graph/curve_view_range.cpp \
graph/go_to_parameter_controller.cpp \

View File

@@ -21,7 +21,7 @@ const Image * App::Descriptor::icon() {
App::Snapshot::Snapshot() :
Shared::FunctionApp::Snapshot::Snapshot(),
m_sequenceStore(),
m_graphRange(&m_cursor)
m_graphRange()
{
}

View File

@@ -1,20 +0,0 @@
#include "banner_view.h"
namespace Sequence {
BannerView::BannerView() :
m_abscissaView(KDFont::SmallFont, 0.5f, 0.5f, KDColorBlack, Palette::GreyMiddle),
m_sequenceView(KDFont::SmallFont, 0.5f, 0.5f, KDColorBlack, Palette::GreyMiddle)
{
}
int BannerView::numberOfSubviews() const {
return 2;
}
TextView * BannerView::textViewAtIndex(int i) const {
const TextView * views[2] = {&m_abscissaView, &m_sequenceView};
return (TextView *)views[i];
}
}

View File

@@ -1,21 +0,0 @@
#ifndef SEQUENCE_BANNER_VIEW_H
#define SEQUENCE_BANNER_VIEW_H
#include <escher.h>
#include "../../shared/banner_view.h"
namespace Sequence {
class BannerView : public Shared::BannerView {
public:
BannerView();
private:
int numberOfSubviews() const override;
TextView * textViewAtIndex(int i) const override;
BufferTextView m_abscissaView;
BufferTextView m_sequenceView;
};
}
#endif

View File

@@ -7,7 +7,7 @@ using namespace Shared;
namespace Sequence {
CurveParameterController::CurveParameterController(InputEventHandlerDelegate * inputEventHandlerDelegate, GraphController * graphController, InteractiveCurveViewRange * graphRange, CurveViewCursor * cursor) :
FunctionCurveParameterController(graphRange, cursor),
FunctionCurveParameterController(),
m_goToParameterController(this, inputEventHandlerDelegate, graphRange, cursor, I18n::Message::N),
m_sumCell(I18n::Message::TermSum),
m_graphController(graphController)

View File

@@ -1,7 +1,6 @@
#ifndef SEQUENCE_CURVE_PARAMETER_CONTROLLER_H
#define SEQUENCE_CURVE_PARAMETER_CONTROLLER_H
#include <escher.h>
#include "../../shared/function_curve_parameter_controller.h"
#include "go_to_parameter_controller.h"

View File

@@ -8,8 +8,8 @@ using namespace Poincare;
namespace Sequence {
CurveViewRange::CurveViewRange(CurveViewCursor * cursor, InteractiveCurveViewRangeDelegate * delegate) :
InteractiveCurveViewRange(cursor, delegate)
CurveViewRange::CurveViewRange(InteractiveCurveViewRangeDelegate * delegate) :
InteractiveCurveViewRange(delegate)
{
m_xMin = -k_displayLeftMarginRatio * m_xMax;
}

View File

@@ -7,7 +7,7 @@ namespace Sequence {
class CurveViewRange : public Shared::InteractiveCurveViewRange {
public:
CurveViewRange(Shared::CurveViewCursor * cursor, Shared::InteractiveCurveViewRangeDelegate * delegate = nullptr);
CurveViewRange(Shared::InteractiveCurveViewRangeDelegate * delegate = nullptr);
void roundAbscissa() override;
void normalize() override;
void setTrigonometric() override;

View File

@@ -6,7 +6,7 @@ namespace Sequence {
bool GoToParameterController::setParameterAtIndex(int parameterIndex, double f) {
assert(parameterIndex == 0);
return Shared::FunctionGoToParameterController::setParameterAtIndex(parameterIndex, std::round(f));
return Shared::FunctionGoToParameterController::setParameterAtIndex(parameterIndex, std::fmax(0, std::round(f)));
}
}

View File

@@ -12,7 +12,7 @@ static inline int maxInt(int x, int y) { return (x > y ? x : y); }
GraphController::GraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, SequenceStore * sequenceStore, CurveViewRange * graphRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * rangeVersion, Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header) :
FunctionGraphController(parentResponder, inputEventHandlerDelegate, header, graphRange, &m_view, cursor, indexFunctionSelectedByCursor, modelVersion, rangeVersion, angleUnitVersion),
m_bannerView(),
m_bannerView(this, inputEventHandlerDelegate, this),
m_view(sequenceStore, graphRange, m_cursor, &m_bannerView, &m_cursorView),
m_graphRange(graphRange),
m_curveParameterController(inputEventHandlerDelegate, this, graphRange, m_cursor),
@@ -28,6 +28,11 @@ I18n::Message GraphController::emptyMessage() {
return I18n::Message::NoActivatedSequence;
}
void GraphController::viewWillAppear() {
m_view.setCursorView(&m_cursorView);
FunctionGraphController::viewWillAppear();
}
float GraphController::interestingXMin() const {
int nmin = INT_MAX;
int nbOfActiveModels = functionStore()->numberOfActiveFunctions();
@@ -54,6 +59,20 @@ float GraphController::interestingXHalfRange() const {
return nmax - nmin;
}
bool GraphController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) {
double floatBody;
if (textFieldDelegateApp()->hasUndefinedValue(text, floatBody)) {
return false;
}
floatBody = std::fmax(0, std::round(floatBody));
double y = yValue(selectedCurveIndex(), floatBody, textFieldDelegateApp()->localContext());
m_cursor->moveTo(floatBody, y);
interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
reloadBannerView();
m_view.reload();
return true;
}
bool GraphController::handleEnter() {
Ion::Storage::Record record = functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor());
m_termSumController.setRecord(record);
@@ -79,7 +98,6 @@ bool GraphController::moveCursorHorizontally(int direction) {
TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app();
double y = s->evaluateAtAbscissa(x, myApp->localContext());
m_cursor->moveTo(x, y);
m_graphRange->panToMakePointVisible(x, y, cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
return true;
}

View File

@@ -2,7 +2,7 @@
#define SEQUENCE_GRAPH_CONTROLLER_H
#include "graph_view.h"
#include "banner_view.h"
#include "../../shared/xy_banner_view.h"
#include "curve_parameter_controller.h"
#include "curve_view_range.h"
#include "term_sum_controller.h"
@@ -16,24 +16,23 @@ class GraphController final : public Shared::FunctionGraphController {
public:
GraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, SequenceStore * sequenceStore, CurveViewRange * graphRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header);
I18n::Message emptyMessage() override;
void viewWillAppear() override;
TermSumController * termSumController() { return &m_termSumController; }
// InteractiveCurveViewRangeDelegate
float interestingXMin() const override;
float interestingXHalfRange() const override;
bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override;
private:
BannerView * bannerView() override { return &m_bannerView; }
Shared::XYBannerView * bannerView() override { return &m_bannerView; }
bool handleEnter() override;
bool moveCursorHorizontally(int direction) override;
double defaultCursorAbscissa() override;
CurveViewRange * interactiveCurveViewRange() override { return m_graphRange; }
SequenceStore * functionStore() const override { return static_cast<SequenceStore *>(Shared::FunctionGraphController::functionStore()); }
GraphView * functionGraphView() override { return &m_view; }
View * cursorView() override {
return &m_cursorView;
}
CurveParameterController * curveParameterController() override { return &m_curveParameterController; }
Shared::CursorView m_cursorView;
BannerView m_bannerView;
Shared::XYBannerView m_bannerView;
GraphView m_view;
CurveViewRange * m_graphRange;
CurveParameterController m_curveParameterController;

View File

@@ -72,14 +72,11 @@ bool ListParameterController::textFieldDidFinishEditing(TextField * textField, c
static float maxFirstIndex = std::pow(10.0f, Sequence::k_initialRankNumberOfDigits) - 1.0f;
/* -1 to take into account a double recursive sequence, which has
* SecondIndex = FirstIndex + 1 */
AppsContainer * appsContainer = ((TextFieldDelegateApp *)app())->container();
Context * globalContext = appsContainer->globalContext();
float floatBody = PoincareHelpers::ApproximateToScalar<float>(text, *globalContext);
int index = std::round(floatBody);
if (std::isnan(floatBody) || std::isinf(floatBody)) {
app()->displayWarning(I18n::Message::UndefinedValue);
double floatBody;
if (textFieldDelegateApp()->hasUndefinedValue(text, floatBody)) {
return false;
}
int index = std::round(floatBody);
if (index < 0 || floatBody >= maxFirstIndex) {
app()->displayWarning(I18n::Message::ForbiddenValue);
return false;

View File

@@ -76,10 +76,9 @@ bool DisplayModeController::textFieldShouldFinishEditing(TextField * textField,
}
bool DisplayModeController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) {
Context * globalContext = textFieldDelegateApp()->localContext();
float floatBody = PoincareHelpers::ApproximateToScalar<float>(text, *globalContext);
if (std::isnan(floatBody) || std::isinf(floatBody)) {
floatBody = PrintFloat::k_numberOfPrintedSignificantDigits;
double floatBody;
if (textFieldDelegateApp()->hasUndefinedValue(text, floatBody)) {
return false;
}
if (floatBody < 1) {
floatBody = 1;

View File

@@ -70,5 +70,6 @@ app_src += $(addprefix apps/shared/,\
values_function_parameter_controller.cpp \
values_parameter_controller.cpp \
vertical_cursor_view.cpp \
xy_banner_view.cpp\
zoom_parameter_controller.cpp \
)

View File

@@ -1,103 +1,78 @@
#include "banner_view.h"
#include <string.h>
#include <assert.h>
namespace Shared {
KDCoordinate BannerView::HeightGivenNumberOfLines(int linesCount) {
return KDFont::SmallFont->glyphSize().height()*linesCount;
return LineSpacing + (Font()->glyphSize().height() + LineSpacing) * linesCount;
}
void BannerView::setLegendAtIndex(char * text, int index) {
/* The layout of the banner's subviews depends on their content.
* Indeed, we're using a "centered text" algorithm to layout the subviews.
* So changing a legend implies two things
* - First, we need to update the textView to ensure it has the new content
* - Second, we need to relayout *all* of our subviews. */
TextView * textView = textViewAtIndex(index);
textView->setText(text);
layoutSubviews();
}
void BannerView::setMessageAtIndex(I18n::Message text, int index) {
MessageTextView * textView = messageTextViewAtIndex(index);
textView->setMessage(text);
layoutSubviews();
void BannerView::drawRect(KDContext * ctx, KDRect rect) const {
const KDCoordinate frameHeight = minimalSizeForOptimalDisplay().height();
const KDCoordinate lineHeight = Font()->glyphSize().height() + LineSpacing;
const KDCoordinate lineWidth = m_frame.width();
for (KDCoordinate y = 0; y < frameHeight; y += lineHeight) {
ctx->fillRect(KDRect(0, y, lineWidth, LineSpacing), BackgroundColor());
}
}
KDSize BannerView::minimalSizeForOptimalDisplay() const {
return KDSize(0, HeightGivenNumberOfLines(numberOfLines()));
}
int BannerView::numberOfSubviews() const {
return 0;
}
void BannerView::layoutSubviews() {
if (m_frame.isEmpty()) {
/* If the frame has not been set yet, there is no point in layouting the
* subviews.
* TODO: Do not call layout Subviews if the frame has not been set? */
return;
}
/* We iterate on subviews, adding their width until we exceed the view bound.
* The last subview that exceeds the bound is recorded as the first subview of
* the next line. For the current line, we scan again the subviews and frame
* them by equally distributing the remaining width. We then jump to the next
* line and iterate the process. */
KDCoordinate totalWidth = bounds().width();
KDCoordinate lineWidth = 0;
TextView * textViewPreviousLine = textViewAtIndex(0);
const KDCoordinate lineWidth = m_frame.width();
KDCoordinate remainingWidth = lineWidth;
int indexOfFirstViewOfCurrentLine = 0;
KDCoordinate y = 0;
KDCoordinate y = LineSpacing;
/* We do a last iteration of the loop to layout the last line. */
for (int i = 0; i <= numberOfSubviews(); i++) {
TextView * textView = nullptr;
KDCoordinate currentViewWidth = totalWidth;
if (i < numberOfSubviews()) {
textView = textViewAtIndex(i);
currentViewWidth = textView->minimalSizeForOptimalDisplay().width();
}
// The subview exceed the total width
if (lineWidth + currentViewWidth > totalWidth) {
KDCoordinate subviewWidth = (i < numberOfSubviews()) ? subviewAtIndex(i)->minimalSizeForOptimalDisplay().width() : lineWidth;
if (subviewWidth > remainingWidth) {
KDCoordinate x = 0;
int nbOfTextViewInLine = i > indexOfFirstViewOfCurrentLine ? i-indexOfFirstViewOfCurrentLine : 1;
KDCoordinate roundingError = totalWidth-lineWidth-nbOfTextViewInLine*(int)((totalWidth-lineWidth)/nbOfTextViewInLine);
int nbOfSubviewsOnLine = i > indexOfFirstViewOfCurrentLine ? i-indexOfFirstViewOfCurrentLine : 1;
KDCoordinate roundingError = remainingWidth % nbOfSubviewsOnLine;
View * subviewPreviousLine = nullptr;
for (int j = indexOfFirstViewOfCurrentLine; j < i; j++) {
textViewPreviousLine = textViewAtIndex(j);
KDCoordinate textWidth = textViewPreviousLine->minimalSizeForOptimalDisplay().width() + (totalWidth - lineWidth)/nbOfTextViewInLine;
// For the last text view, avoid letting a 1-pixel-wide empty vertical due to rounding error:
textWidth = j == i-1 ? textWidth + roundingError : textWidth;
KDCoordinate textHeight = textViewPreviousLine->minimalSizeForOptimalDisplay().height();
textViewPreviousLine->setFrame(KDRect(x, y, textWidth, textHeight));
x += textWidth;
subviewPreviousLine = subviewAtIndex(j);
KDCoordinate width = subviewPreviousLine->minimalSizeForOptimalDisplay().width() + remainingWidth/nbOfSubviewsOnLine + (j == i-1) * roundingError;
KDCoordinate height = subviewPreviousLine->minimalSizeForOptimalDisplay().height();
subviewPreviousLine->setFrame(KDRect(x, y, width, height));
x += width;
}
// Next line
y += textViewPreviousLine->minimalSizeForOptimalDisplay().height();
lineWidth = 0;
y += subviewPreviousLine->minimalSizeForOptimalDisplay().height() + LineSpacing;
remainingWidth = lineWidth;
indexOfFirstViewOfCurrentLine = i;
}
lineWidth += currentViewWidth;
remainingWidth -= subviewWidth;
}
}
View * BannerView::subviewAtIndex(int index) {
assert(index >= 0 && index < numberOfSubviews());
return textViewAtIndex(index);
}
int BannerView::numberOfLines() const {
KDCoordinate width = bounds().width();
KDCoordinate usedWidth = 0;
KDCoordinate lineNumber = 0;
int lineNumber = 1;
const KDCoordinate lineWidth = m_frame.width();
KDCoordinate remainingWidth = lineWidth;
for (int i = 0; i < numberOfSubviews(); i++) {
KDCoordinate textWidth = KDFont::SmallFont->stringSize(textViewAtIndex(i)->text()).width();
if (usedWidth+textWidth > width) {
usedWidth = textWidth;
KDCoordinate subviewWidth = const_cast<Shared::BannerView *>(this)->subviewAtIndex(i)->minimalSizeForOptimalDisplay().width();
if (subviewWidth > remainingWidth) {
remainingWidth = lineWidth;
lineNumber++;
} else {
usedWidth += textWidth;
}
remainingWidth -= subviewWidth;
}
return lineNumber+1;
}
MessageTextView * BannerView::messageTextViewAtIndex(int i) const {
return nullptr;
return lineNumber;
}
}

View File

@@ -8,16 +8,18 @@ namespace Shared {
class BannerView : public View {
public:
static KDCoordinate HeightGivenNumberOfLines(int linesCount);
void setLegendAtIndex(char * text, int index);
void setMessageAtIndex(I18n::Message text, int index);
void drawRect(KDContext * ctx, KDRect rect) const override;
KDSize minimalSizeForOptimalDisplay() const override;
void reload() { layoutSubviews(); }
static constexpr const KDFont * Font() { return KDFont::SmallFont; }
static constexpr KDColor TextColor() { return KDColorBlack; }
static constexpr KDColor BackgroundColor() { return Palette::GreyMiddle; }
private:
int numberOfSubviews() const override;
View * subviewAtIndex(int index) override;
static constexpr KDCoordinate LineSpacing = 2;
int numberOfSubviews() const override = 0;
View * subviewAtIndex(int index) override = 0;
void layoutSubviews() override;
int numberOfLines() const;
virtual TextView * textViewAtIndex(int i) const = 0;
virtual MessageTextView * messageTextViewAtIndex(int i) const;
};
}

View File

@@ -74,7 +74,7 @@ int CartesianFunction::derivativeNameWithArgument(char * buffer, size_t bufferSi
if (!UTF8Helper::CodePointIs(firstParenthesis, '(')) {
return numberOfChars;
}
memmove(firstParenthesis + derivativeSize, firstParenthesis, buffer + numberOfChars - firstParenthesis);
memmove(firstParenthesis + derivativeSize, firstParenthesis, numberOfChars - (firstParenthesis - buffer) + 1);
UTF8Decoder::CodePointToChars('\'', firstParenthesis, derivativeSize);
return numberOfChars + derivativeSize;
}

View File

@@ -9,14 +9,6 @@ CurveViewCursor::CurveViewCursor() :
{
}
double CurveViewCursor::x() {
return m_x;
}
double CurveViewCursor::y() {
return m_y;
}
void CurveViewCursor::moveTo(double x, double y) {
m_x = clipped(x, false);
m_y = clipped(y, true);

View File

@@ -6,8 +6,8 @@ namespace Shared {
class CurveViewCursor {
public:
CurveViewCursor();
double x();
double y();
double x() const { return m_x; }
double y() const { return m_y; }
void moveTo(double x, double y);
private:
static double clipped(double f, bool canBeInfinite);

View File

@@ -24,25 +24,19 @@ bool EditableCellTableViewController::textFieldShouldFinishEditing(TextField * t
}
bool EditableCellTableViewController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) {
AppsContainer * appsContainer = ((TextFieldDelegateApp *)app())->container();
Context * globalContext = appsContainer->globalContext();
double floatBody = PoincareHelpers::ApproximateToScalar<double>(text, *globalContext);
if (std::isnan(floatBody) || std::isinf(floatBody)) {
app()->displayWarning(I18n::Message::UndefinedValue);
double floatBody;
if (textFieldDelegateApp()->hasUndefinedValue(text, floatBody)) {
return false;
}
int nbOfRows = numberOfRows();
int nbOfColumns = numberOfColumns();
if (!setDataAtLocation(floatBody, selectedColumn(), selectedRow())) {
app()->displayWarning(I18n::Message::ForbiddenValue);
return false;
}
for (int j = 0; j < numberOfColumns(); j++) {
selectableTableView()->reloadCellAtLocation(j, selectedRow());
}
if (nbOfRows != numberOfRows() || nbOfColumns != numberOfColumns()) {
selectableTableView()->reloadData();
}
/* At this point, a new cell is selected depending on the event, before the
* data is reloaded, which means that the right cell is selected but the data
* may be incorrect. The data is reloaded afterwards by the
* textFieldDidFinishEditing methods of the derived classes StoreController
* and ValuesController. */
if (event == Ion::Events::EXE || event == Ion::Events::OK) {
selectableTableView()->selectCellAtLocation(selectedColumn(), selectedRow()+1);
} else {

View File

@@ -122,11 +122,8 @@ bool FloatParameterController::textFieldShouldFinishEditing(TextField * textFiel
}
bool FloatParameterController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) {
AppsContainer * appsContainer = ((TextFieldDelegateApp *)app())->container();
Context * globalContext = appsContainer->globalContext();
double floatBody = PoincareHelpers::ApproximateToScalar<double>(text, *globalContext);
if (std::isnan(floatBody) || std::isinf(floatBody)) {
app()->displayWarning(I18n::Message::UndefinedValue);
double floatBody;
if (textFieldDelegateApp()->hasUndefinedValue(text, floatBody)) {
return false;
}
if (!setParameterAtIndex(selectedRow(), floatBody)) {

View File

@@ -11,23 +11,22 @@ void FunctionBannerDelegate::reloadBannerViewForCursorOnFunction(CurveViewCursor
constexpr int bufferSize = k_maxNumberOfCharacters+PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits);
char buffer[bufferSize];
const char * space = " ";
const char * legend = "0=";
int legendLength = strlen(legend);
int numberOfChar = 0;
strlcpy(buffer, legend, bufferSize);
numberOfChar += legendLength;
buffer[0] = symbol;
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(cursor->x(), buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits);
strlcpy(buffer+numberOfChar, space, bufferSize - numberOfChar);
bannerView()->setLegendAtIndex(buffer, 0);
buffer[numberOfChar++] = symbol;
strlcpy(buffer + numberOfChar, "=", bufferSize - numberOfChar);
bannerView()->abscissaSymbol()->setText(buffer);
numberOfChar = 0;
numberOfChar += function->nameWithArgument(buffer, bufferSize, symbol);
legend = "=";
numberOfChar += strlcpy(buffer+numberOfChar, legend, bufferSize-numberOfChar);
numberOfChar = PoincareHelpers::ConvertFloatToText<double>(cursor->x(), buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits);
strlcpy(buffer+numberOfChar, space, bufferSize - numberOfChar);
bannerView()->abscissaValue()->setText(buffer);
numberOfChar = function->nameWithArgument(buffer, bufferSize, symbol);
numberOfChar += strlcpy(buffer+numberOfChar, "=", bufferSize-numberOfChar);
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(cursor->y(), buffer+numberOfChar, bufferSize-numberOfChar, Constant::MediumNumberOfSignificantDigits);
strlcpy(buffer+numberOfChar, space, bufferSize-numberOfChar);
bannerView()->setLegendAtIndex(buffer, 1);
bannerView()->ordinateView()->setText(buffer);
bannerView()->reload();
}
}

View File

@@ -2,7 +2,7 @@
#define SHARED_FUNCTION_BANNER_DELEGATE_H
#include "function_store.h"
#include "banner_view.h"
#include "xy_banner_view.h"
#include "curve_view_cursor.h"
namespace Shared {
@@ -12,7 +12,7 @@ public:
constexpr static int k_maxNumberOfCharacters = 50;
protected:
void reloadBannerViewForCursorOnFunction(CurveViewCursor * cursor, Ion::Storage::Record record, FunctionStore * functionStore, char symbol);
virtual BannerView * bannerView() = 0;
virtual XYBannerView * bannerView() = 0;
};
}

View File

@@ -3,7 +3,7 @@
namespace Shared {
FunctionCurveParameterController::FunctionCurveParameterController(InteractiveCurveViewRange * graphRange, CurveViewCursor * cursor) :
FunctionCurveParameterController::FunctionCurveParameterController() :
ViewController(nullptr),
m_goToCell(I18n::Message::Goto),
m_selectableTableView(this, this, this),

View File

@@ -3,14 +3,12 @@
#include <escher.h>
#include "function_go_to_parameter_controller.h"
#include "curve_view_cursor.h"
#include "interactive_curve_view_range.h"
namespace Shared {
class FunctionCurveParameterController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource {
public:
FunctionCurveParameterController(InteractiveCurveViewRange * graphRange, CurveViewCursor * cursor);
FunctionCurveParameterController();
View * view() override;
void didBecomeFirstResponder() override;
KDCoordinate cellHeight() override;

View File

@@ -25,14 +25,6 @@ bool FunctionGoToParameterController::setParameterAtIndex(int parameterIndex, do
FunctionApp * myApp = (FunctionApp *)app();
ExpiringPointer<Function> function = myApp->functionStore()->modelForRecord(m_record);
float y = function->evaluateAtAbscissa(f, myApp->localContext());
if (std::fabs(f) > k_maxDisplayableFloat || std::fabs(y) > k_maxDisplayableFloat) {
app()->displayWarning(I18n::Message::ForbiddenValue);
return false;
}
if (std::isnan(y) || std::isinf(y)) {
app()->displayWarning(I18n::Message::ValueNotReachedByFunction);
return false;
}
m_cursor->moveTo(f, y);
m_graphRange->centerAxisAround(CurveViewRange::Axis::X, m_cursor->x());
m_graphRange->centerAxisAround(CurveViewRange::Axis::Y, m_cursor->y());

View File

@@ -27,8 +27,17 @@ ViewController * FunctionGraphController::initialisationParameterController() {
return &m_initialisationParameterController;
}
void FunctionGraphController::didBecomeFirstResponder() {
if (curveView()->isMainViewSelected()) {
bannerView()->abscissaValue()->setParentResponder(this);
bannerView()->abscissaValue()->setDelegates(textFieldDelegateApp(), this);
app()->setFirstResponder(bannerView()->abscissaValue());
} else {
InteractiveCurveViewController::didBecomeFirstResponder();
}
}
void FunctionGraphController::viewWillAppear() {
functionGraphView()->setCursorView(cursorView());
functionGraphView()->setBannerView(bannerView());
functionGraphView()->setAreaHighlight(NAN,NAN);
@@ -56,10 +65,6 @@ void FunctionGraphController::selectFunctionWithCursor(int functionIndex) {
*m_indexFunctionSelectedByCursor = functionIndex;
}
float FunctionGraphController::cursorBottomMarginRatio() {
return (cursorView()->minimalSizeForOptimalDisplay().height()/2+estimatedBannerHeight())/k_viewHeight;
}
void FunctionGraphController::reloadBannerView() {
if (functionStore()->numberOfActiveFunctions() == 0) {
return;
@@ -68,14 +73,6 @@ void FunctionGraphController::reloadBannerView() {
reloadBannerViewForCursorOnFunction(m_cursor, record, functionStore(), functionStore()->symbol());
}
float FunctionGraphController::displayBottomMarginRatio() {
return (cursorView()->minimalSizeForOptimalDisplay().height() + 2 + estimatedBannerHeight()) / k_viewHeight;
}
float FunctionGraphController::estimatedBannerHeight() const {
return BannerView::HeightGivenNumberOfLines(estimatedBannerNumberOfLines());
}
InteractiveCurveViewRangeDelegate::Range FunctionGraphController::computeYRange(InteractiveCurveViewRange * interactiveCurveViewRange) {
FunctionApp * myApp = static_cast<FunctionApp *>(app());
float min = FLT_MAX;
@@ -109,12 +106,6 @@ InteractiveCurveViewRangeDelegate::Range FunctionGraphController::computeYRange(
return range;
}
void FunctionGraphController::initRangeParameters() {
interactiveCurveViewRange()->setDefault();
initCursorParameters();
selectFunctionWithCursor(0);
}
double FunctionGraphController::defaultCursorAbscissa() {
return (interactiveCurveViewRange()->xMin()+interactiveCurveViewRange()->xMax())/2.0f;
}
@@ -137,7 +128,7 @@ void FunctionGraphController::initCursorParameters() {
functionIndex = (std::isnan(y) || std::isinf(y)) ? 0 : functionIndex - 1;
selectFunctionWithCursor(functionIndex);
if (interactiveCurveViewRange()->yAuto()) {
interactiveCurveViewRange()->panToMakePointVisible(x, y, displayTopMarginRatio(), k_cursorRightMarginRatio, displayBottomMarginRatio(), k_cursorLeftMarginRatio);
interactiveCurveViewRange()->panToMakePointVisible(x, y, cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
}
}
@@ -151,7 +142,6 @@ bool FunctionGraphController::moveCursorVertically(int direction) {
}
selectFunctionWithCursor(nextActiveFunctionIndex);
m_cursor->moveTo(m_cursor->x(), yValue(nextActiveFunctionIndex, m_cursor->x(), context));
interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
return true;
}
@@ -167,10 +157,6 @@ uint32_t FunctionGraphController::rangeVersion() {
return interactiveCurveViewRange()->rangeChecksum();
}
bool FunctionGraphController::isCursorVisible() {
return interactiveCurveViewRange()->isCursorVisible(cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
}
bool FunctionGraphController::closestCurveIndexIsSuitable(int newIndex, int currentIndex) const {
return newIndex != currentIndex;
}

View File

@@ -16,11 +16,11 @@ public:
FunctionGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion);
bool isEmpty() const override;
ViewController * initialisationParameterController() override;
void didBecomeFirstResponder() override;
void viewWillAppear() override;
protected:
float cursorTopMarginRatio() { return 0.068f; } // (cursorHeight/2)/graphViewHeight
float cursorBottomMarginRatio();
float cursorTopMarginRatio() override { return 0.068f; }
void reloadBannerView() override;
bool handleEnter() override;
int indexFunctionSelectedByCursor() const { return *m_indexFunctionSelectedByCursor; }
@@ -28,35 +28,25 @@ protected:
virtual double defaultCursorAbscissa();
virtual FunctionStore * functionStore() const;
// Closest vertical curve helper
bool closestCurveIndexIsSuitable(int newIndex, int currentIndex) const override;
int selectedCurveIndex() const override { return *m_indexFunctionSelectedByCursor; }
double yValue(int curveIndex, double x, Poincare::Context * context) const override;
int numberOfCurves() const override;
private:
constexpr static float k_viewHeight = 174.0f; // TODO Taken from Regresssion/graph_controller. Maybe we should compute and/or put in common ?
virtual FunctionGraphView * functionGraphView() = 0;
virtual View * cursorView() = 0;
virtual FunctionCurveParameterController * curveParameterController() = 0;
// InteractiveCurveViewController
/* When y auto is ticked, we use a display margin to be ensure that the user
* can move the cursor along the curve without panning the window */
float displayTopMarginRatio() override { return 0.09f; } // cursorHeight/graphViewHeight
float displayBottomMarginRatio() override;
// InteractiveCurveViewRangeDelegate
InteractiveCurveViewRangeDelegate::Range computeYRange(InteractiveCurveViewRange * interactiveCurveViewRange) override;
float estimatedBannerHeight() const;
virtual int estimatedBannerNumberOfLines() const { return 1; }
// InteractiveCurveViewController
void initRangeParameters() override;
void initCursorParameters() override;
bool moveCursorVertically(int direction) override;
CurveView * curveView() override;
uint32_t modelVersion() override;
uint32_t rangeVersion() override;
bool isCursorVisible() override;
bool closestCurveIndexIsSuitable(int newIndex, int currentIndex) const override;
double yValue(int curveIndex, double x, Poincare::Context * context) const override;
int numberOfCurves() const override;
InitialisationParameterController m_initialisationParameterController;
Poincare::Preferences::AngleUnit * m_angleUnitVersion;

View File

@@ -218,7 +218,7 @@ void FunctionListController::tableViewDidChangeSelection(SelectableTableView * t
// Update memoization of cell heights
ExpressionModelListController::tableViewDidChangeSelection(t, previousSelectedCellX, previousSelectedCellY, withinTemporarySelection);
// Do not select the cell left of the "addEmptyFunction" cell
if (!withinTemporarySelection && isAddEmptyRow(selectedRow()) && selectedColumn() == 0) {
if (isAddEmptyRow(selectedRow()) && selectedColumn() == 0) {
t->selectCellAtLocation(1, numberOfRows()-1);
}
}

View File

@@ -14,7 +14,6 @@ public:
int numberOfRows() override;
bool handleEvent(Ion::Events::Event event) override;
protected:
constexpr static double k_maxDisplayableFloat = 1E8;
CurveViewCursor * m_cursor;
InteractiveCurveViewRange * m_graphRange;
private:

View File

@@ -9,7 +9,7 @@ using namespace Poincare;
namespace Shared {
InteractiveCurveViewController::InteractiveCurveViewController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, uint32_t * modelVersion, uint32_t * rangeVersion) :
SimpleInteractiveCurveViewController(parentResponder, interactiveRange, curveView, cursor),
SimpleInteractiveCurveViewController(parentResponder, cursor),
ButtonRowDelegate(header, nullptr),
m_modelVersion(modelVersion),
m_rangeVersion(rangeVersion),
@@ -61,9 +61,11 @@ float InteractiveCurveViewController::addMargin(float x, float range, bool isMin
* topRatioBefore = topRatioAfter, we would create too small margins and the
* controller might need to pan right after a Y auto calibration. */
assert(displayBottomMarginRatio()+displayTopMarginRatio() < 1); // Assertion so that the formula is correct
float ratioDenominator = 1-displayBottomMarginRatio()-displayTopMarginRatio();
float ratio = isMin ? -displayBottomMarginRatio() : displayTopMarginRatio();
float topMarginRatio = cursorTopMarginRatio();
float bottomMarginRatio = cursorBottomMarginRatio();
assert(topMarginRatio + bottomMarginRatio < 1); // Assertion so that the formula is correct
float ratioDenominator = 1 - bottomMarginRatio - topMarginRatio;
float ratio = isMin ? -bottomMarginRatio : topMarginRatio;
ratio = ratio / ratioDenominator;
return x+ratio*range;
}
@@ -89,24 +91,25 @@ bool InteractiveCurveViewController::handleEvent(Ion::Events::Event event) {
}
return false;
}
if (SimpleInteractiveCurveViewController::handleEvent(event)) {
return true;
}
if (event == Ion::Events::Down || event == Ion::Events::Up) {
int direction = event == Ion::Events::Down ? -1 : 1;
if (moveCursorVertically(direction)) {
interactiveCurveViewRange()->panToMakePointVisible(
m_cursor->x(), m_cursor->y(),
cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio
);
reloadBannerView();
curveView()->reload();
return true;
}
if (event == Ion::Events::Down) {
return false;
if (event == Ion::Events::Up) {
curveView()->selectMainView(false);
header()->setSelectedButton(0);
return true;
}
curveView()->selectMainView(false);
header()->setSelectedButton(0);
return true;
return false;
}
return false;
return SimpleInteractiveCurveViewController::handleEvent(event);
}
void InteractiveCurveViewController::didBecomeFirstResponder() {
@@ -143,7 +146,7 @@ void InteractiveCurveViewController::viewWillAppear() {
uint32_t newModelVersion = modelVersion();
if (*m_modelVersion != newModelVersion) {
if (*m_modelVersion == 0 || numberOfCurves() == 1) {
initRangeParameters();
interactiveCurveViewRange()->setDefault();
}
*m_modelVersion = newModelVersion;
didChangeRange(interactiveCurveViewRange());
@@ -171,11 +174,6 @@ void InteractiveCurveViewController::viewDidDisappear() {
*m_rangeVersion = rangeVersion();
}
void InteractiveCurveViewController::didEnterResponderChain(Responder * previousFirstResponder) {
reloadBannerView();
curveView()->reload();
}
void InteractiveCurveViewController::willExitResponderChain(Responder * nextFirstResponder) {
if (nextFirstResponder == nullptr || nextFirstResponder == tabController()) {
curveView()->selectMainView(false);
@@ -184,6 +182,26 @@ void InteractiveCurveViewController::willExitResponderChain(Responder * nextFirs
}
}
bool InteractiveCurveViewController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) {
double floatBody;
if (textFieldDelegateApp()->hasUndefinedValue(text, floatBody)) {
return false;
}
double y = yValue(selectedCurveIndex(), floatBody, textFieldDelegateApp()->localContext());
m_cursor->moveTo(floatBody, y);
interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
reloadBannerView();
curveView()->reload();
return true;
}
bool InteractiveCurveViewController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) {
if ((event == Ion::Events::Plus || event == Ion::Events::Minus) && !textField->isEditing()) {
return handleEvent(event);
}
return SimpleInteractiveCurveViewController::textFieldDidReceiveEvent(textField, event);
}
Responder * InteractiveCurveViewController::tabController() const{
return (stackController()->parentResponder());
}
@@ -192,6 +210,17 @@ StackViewController * InteractiveCurveViewController::stackController() const{
return (StackViewController *)(parentResponder()->parentResponder()->parentResponder());
}
bool InteractiveCurveViewController::isCursorVisible() {
InteractiveCurveViewRange * range = interactiveCurveViewRange();
float xRange = range->xMax() - range->xMin();
float yRange = range->yMax() - range->yMin();
return
m_cursor->x() >= range->xMin() + k_cursorLeftMarginRatio * xRange &&
m_cursor->x() <= range->xMax() - k_cursorRightMarginRatio * xRange &&
m_cursor->y() >= range->yMin() + cursorBottomMarginRatio() * yRange &&
m_cursor->y() <= range->yMax() - cursorTopMarginRatio() * yRange;
}
int InteractiveCurveViewController::closestCurveIndexVertically(bool goingUp, int currentCurveIndex, Poincare::Context * context) const {
double x = m_cursor->x();
double y = m_cursor->y();
@@ -247,4 +276,12 @@ int InteractiveCurveViewController::closestCurveIndexVertically(bool goingUp, in
return nextCurveIndex;
}
float InteractiveCurveViewController::cursorBottomMarginRatio() {
return (curveView()->cursorView()->minimalSizeForOptimalDisplay().height()/2+estimatedBannerHeight())/k_viewHeight;
}
float InteractiveCurveViewController::estimatedBannerHeight() const {
return BannerView::HeightGivenNumberOfLines(estimatedBannerNumberOfLines());
}
}

View File

@@ -13,11 +13,6 @@ class InteractiveCurveViewController : public SimpleInteractiveCurveViewControll
public:
InteractiveCurveViewController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor, uint32_t * modelVersion, uint32_t * rangeVersion);
// InteractiveCurveViewRangeDelegate
float addMargin(float x, float range, bool isMin) override;
virtual float displayTopMarginRatio() = 0;
virtual float displayBottomMarginRatio() = 0;
const char * title() override;
bool handleEvent(Ion::Events::Event event) override;
void didBecomeFirstResponder() override;
@@ -33,27 +28,41 @@ public:
void viewWillAppear() override;
void viewDidDisappear() override;
void didEnterResponderChain(Responder * previousFirstResponder) override;
void willExitResponderChain(Responder * nextFirstResponder) override;
bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override;
bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override;
protected:
Responder * tabController() const;
virtual StackViewController * stackController() const;
virtual void initRangeParameters() = 0;
virtual void initCursorParameters() = 0;
virtual bool moveCursorVertically(int direction) = 0;
virtual uint32_t modelVersion() = 0;
virtual uint32_t rangeVersion() = 0;
virtual bool isCursorVisible() = 0;
bool isCursorVisible();
// Closest vertical curve helper
int closestCurveIndexVertically(bool goingUp, int currentSelectedCurve, Poincare::Context * context) const;
virtual bool closestCurveIndexIsSuitable(int newIndex, int currentIndex) const { assert(false); return false; }
virtual double yValue(int curveIndex, double x, Poincare::Context * context) const { assert(false); return 0; }
virtual bool closestCurveIndexIsSuitable(int newIndex, int currentIndex) const = 0;
virtual int selectedCurveIndex() const = 0;
virtual double yValue(int curveIndex, double x, Poincare::Context * context) const = 0;
virtual bool suitableYValue(double y) const { return true; }
virtual int numberOfCurves() const = 0;
// SimpleInteractiveCurveViewController
float cursorBottomMarginRatio() override;
OkView m_okView;
private:
/* The value 21 is the actual height of the ButtonRow, that is
* ButtonRowController::ContentView::k_plainStyleHeight + 1.
* That value is not public though. */
constexpr static float k_viewHeight = Ion::Display::Height - Metric::TitleBarHeight - Metric::TabHeight - 21;
float estimatedBannerHeight() const;
virtual int estimatedBannerNumberOfLines() const { return 1; }
// InteractiveCurveViewRangeDelegate
float addMargin(float x, float range, bool isMin) override;
uint32_t * m_modelVersion;
uint32_t * m_rangeVersion;
RangeParameterController m_rangeParameterController;

View File

@@ -208,12 +208,6 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to
}
}
bool InteractiveCurveViewRange::isCursorVisible(float topMarginRatio, float rightMarginRatio, float bottomMarginRation, float leftMarginRation) {
float xRange = m_xMax - m_xMin;
float yRange = m_yMax - m_yMin;
return m_cursor->x() >= m_xMin + leftMarginRation*xRange && m_cursor->x() <= m_xMax - rightMarginRatio*xRange && m_cursor->y() >= m_yMin + bottomMarginRation*yRange && m_cursor->y() <= m_yMax - topMarginRatio*yRange;
}
float InteractiveCurveViewRange::clipped(float x, bool isMax) {
float maxF = isMax ? k_upperMaxFloat : k_lowerMaxFloat;
float minF = isMax ? -k_lowerMaxFloat : -k_upperMaxFloat;

View File

@@ -3,7 +3,6 @@
#include <stdint.h>
#include "memoized_curve_view_range.h"
#include "curve_view_cursor.h"
#include "interactive_curve_view_range_delegate.h"
#include <ion/display.h>
#include <float.h>
@@ -13,15 +12,13 @@ namespace Shared {
class InteractiveCurveViewRange : public MemoizedCurveViewRange {
public:
constexpr static float k_minFloat = 1E-4f;
InteractiveCurveViewRange(CurveViewCursor * cursor, InteractiveCurveViewRangeDelegate * delegate = nullptr) :
InteractiveCurveViewRange(InteractiveCurveViewRangeDelegate * delegate = nullptr) :
MemoizedCurveViewRange(),
m_yAuto(true),
m_delegate(delegate),
m_cursor(cursor)
m_delegate(delegate)
{}
void setDelegate(InteractiveCurveViewRangeDelegate * delegate) { m_delegate = delegate; }
void setCursor(CurveViewCursor * cursor) { m_cursor = cursor; }
uint32_t rangeChecksum() override;
bool yAuto() const { return m_yAuto; }
@@ -42,7 +39,6 @@ public:
virtual void setDefault();
void centerAxisAround(Axis axis, float position);
void panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRation, float leftMarginRation);
bool isCursorVisible(float topMarginRatio, float rightMarginRatio, float bottomMarginRation, float leftMarginRation);
protected:
bool m_yAuto;
/* In normalized settings, we put each axis so that 1cm = 2 units. For now,
@@ -64,7 +60,6 @@ private:
constexpr static float k_lowerMaxFloat = 9E+7f;
constexpr static float k_maxRatioPositionRange = 1E5f;
void notifyRangeChange();
CurveViewCursor * m_cursor;
};
static_assert(InteractiveCurveViewRange::k_minFloat >= FLT_EPSILON, "InteractiveCurveViewRange's minimal float range is lower than float precision, it might draw uglily curves such as cos(x)^2+sin(x)^2");

View File

@@ -6,6 +6,9 @@
namespace Shared {
bool InteractiveCurveViewRangeDelegate::didChangeRange(InteractiveCurveViewRange * interactiveCurveViewRange) {
/* When y auto is ticked, top and bottom margins are added to ensure that
* the cursor can be move along the curve, in the current x-range,
* without panning the window. */
if (!interactiveCurveViewRange->yAuto()) {
return false;
}

View File

@@ -6,7 +6,7 @@ using namespace Poincare;
namespace Shared {
SimpleInteractiveCurveViewController::SimpleInteractiveCurveViewController(Responder * parentResponder,InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor) :
SimpleInteractiveCurveViewController::SimpleInteractiveCurveViewController(Responder * parentResponder, CurveViewCursor * cursor) :
ViewController(parentResponder),
m_cursor(cursor)
{
@@ -29,6 +29,18 @@ bool SimpleInteractiveCurveViewController::handleEvent(Ion::Events::Event event)
return false;
}
bool SimpleInteractiveCurveViewController::textFieldDidAbortEditing(TextField * textField) {
reloadBannerView();
return true;
}
bool SimpleInteractiveCurveViewController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) {
if ((event == Ion::Events::OK || event == Ion::Events::EXE) && !textField->isEditing()) {
return handleEnter();
}
return TextFieldDelegate::textFieldDidReceiveEvent(textField, event);
}
bool SimpleInteractiveCurveViewController::handleZoom(Ion::Events::Event event) {
float ratio = event == Ion::Events::Plus ? 2.0f/3.0f : 3.0f/2.0f;
interactiveCurveViewRange()->zoom(ratio, m_cursor->x(), m_cursor->y());
@@ -39,6 +51,10 @@ bool SimpleInteractiveCurveViewController::handleZoom(Ion::Events::Event event)
bool SimpleInteractiveCurveViewController::handleLeftRightEvent(Ion::Events::Event event) {
int direction = event == Ion::Events::Left ? -1 : 1;
if (moveCursorHorizontally(direction)) {
interactiveCurveViewRange()->panToMakePointVisible(
m_cursor->x(), m_cursor->y(),
cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio
);
reloadBannerView();
curveView()->reload();
return true;

View File

@@ -1,7 +1,8 @@
#ifndef SHARED_SIMPLE_INTERACTIVE_CURVE_VIEW_CONTROLLER_H
#define SHARED_SIMPLE_INTERACTIVE_CURVE_VIEW_CONTROLLER_H
#include <escher.h>
#include <escher/view_controller.h>
#include "text_field_delegate.h"
#include "interactive_curve_view_range.h"
#include "curve_view_cursor.h"
#include "curve_view.h"
@@ -11,18 +12,25 @@ namespace Shared {
/* SimpleInteractiveCurveViewController is a View controller with a cursor that
* can handles zoom in/out and left and right events. */
class SimpleInteractiveCurveViewController : public ViewController {
class SimpleInteractiveCurveViewController : public ViewController, public TextFieldDelegate {
public:
SimpleInteractiveCurveViewController(Responder * parentResponder, InteractiveCurveViewRange * interactiveRange, CurveView * curveView, CurveViewCursor * cursor);
SimpleInteractiveCurveViewController(Responder * parentResponder, CurveViewCursor * cursor);
View * view() override;
bool handleEvent(Ion::Events::Event event) override;
bool textFieldDidAbortEditing(TextField * textField) override;
bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override;
protected:
TextFieldDelegateApp * textFieldDelegateApp() override {
return static_cast<TextFieldDelegateApp *>(app());
}
constexpr static float k_cursorRightMarginRatio = 0.04f; // (cursorWidth/2)/graphViewWidth
constexpr static float k_cursorLeftMarginRatio = 0.04f; // (cursorWidth/2)/graphViewWidth
virtual float cursorTopMarginRatio() { return 0.07f; } // (cursorHeight/2)/graphViewHeight
virtual float cursorBottomMarginRatio() = 0; // (cursorHeight/2+bannerHeight)/graphViewHeight
constexpr static float k_numberOfCursorStepsInGradUnit = 5.0f;
protected:
virtual bool handleZoom(Ion::Events::Event event);
virtual bool handleLeftRightEvent(Ion::Events::Event event);
virtual void reloadBannerView() {};
virtual void reloadBannerView() = 0;
/* the result of moveCursorVertically/Horizontally means:
* false -> the cursor cannot move in this direction
* true -> the cursor moved */

View File

@@ -95,25 +95,13 @@ bool StoreController::textFieldDidFinishEditing(TextField * textField, const cha
}
return true;
}
AppsContainer * appsContainer = ((TextFieldDelegateApp *)app())->container();
Context * globalContext = appsContainer->globalContext();
double floatBody = PoincareHelpers::ApproximateToScalar<double>(text, *globalContext);
if (std::isnan(floatBody) || std::isinf(floatBody)) {
app()->displayWarning(I18n::Message::UndefinedValue);
return false;
bool didFinishEditing = EditableCellTableViewController::textFieldDidFinishEditing(textField, text, event);
if (didFinishEditing) {
// FIXME Find out if redrawing errors can be suppressed without always reloading all the data
// See Shared::ValuesController::textFieldDidFinishEditing
selectableTableView()->reloadData();
}
if (!setDataAtLocation(floatBody, selectedColumn(), selectedRow())) {
app()->displayWarning(I18n::Message::ForbiddenValue);
return false;
}
// FIXME Find out if redrawing errors can be suppressed without always reloading all the data
selectableTableView()->reloadData();
if (event == Ion::Events::EXE || event == Ion::Events::OK) {
selectableTableView()->selectCellAtLocation(selectedColumn(), selectedRow()+1);
} else {
selectableTableView()->handleEvent(event);
}
return true;
return didFinishEditing;
}
bool StoreController::textFieldDidAbortEditing(TextField * textField) {

View File

@@ -1,7 +1,8 @@
#include "sum_graph_controller.h"
#include "function_app.h"
#include "../apps_container.h"
#include <poincare_layouts.h>
#include <poincare/empty_layout.h>
#include <poincare/condensed_sum_layout.h>
#include <poincare/layout_helper.h>
#include "poincare_helpers.h"
@@ -14,7 +15,7 @@ using namespace Poincare;
namespace Shared {
SumGraphController::SumGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, FunctionGraphView * graphView, InteractiveCurveViewRange * range, CurveViewCursor * cursor, CodePoint sumSymbol) :
SimpleInteractiveCurveViewController(parentResponder, range, graphView, cursor),
SimpleInteractiveCurveViewController(parentResponder, cursor),
m_step(Step::FirstParameter),
m_startSum(NAN),
m_endSum(NAN),
@@ -27,7 +28,7 @@ SumGraphController::SumGraphController(Responder * parentResponder, InputEventHa
}
void SumGraphController::viewWillAppear() {
m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio);
m_graphRange->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
m_graphView->setBannerView(&m_legendView);
m_graphView->setCursorView(&m_cursorView);
m_graphView->setOkView(nullptr);
@@ -39,9 +40,7 @@ void SumGraphController::viewWillAppear() {
m_startSum = m_cursor->x();
m_endSum = NAN;
m_step = Step::FirstParameter;
m_legendView.setLegendMessage(legendMessageAtStep(Step::FirstParameter), Step::FirstParameter);
m_legendView.setEditableZone(m_startSum);
m_legendView.setSumSymbol(m_step);
reloadBannerView();
}
@@ -50,54 +49,21 @@ void SumGraphController::didEnterResponderChain(Responder * previousFirstRespond
}
bool SumGraphController::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::Plus || event == Ion::Events::Minus) {
return handleZoom(event);
}
if ((int)m_step > 1 && event != Ion::Events::OK && event != Ion::Events::EXE && event != Ion::Events::Back) {
return false;
}
if (event == Ion::Events::Left && !m_legendView.textField()->isEditing()) {
if ((int)m_step > 0 && m_startSum >= m_cursor->x()) {
return false;
}
if (moveCursorHorizontallyToPosition(cursorNextStep(m_cursor->x(), -1))) {
m_graphView->reload();
return true;
}
return false;
}
if (event == Ion::Events::Right && !m_legendView.textField()->isEditing()) {
if (moveCursorHorizontallyToPosition(cursorNextStep(m_cursor->x(), 1))) {
m_graphView->reload();
return true;
}
return false;
}
if (event == Ion::Events::OK || event == Ion::Events::EXE) {
return handleEnter();
}
if (event == Ion::Events::Back && (int)m_step > 0) {
if (event == Ion::Events::Back && m_step != Step::FirstParameter) {
m_step = (Step)((int)m_step-1);
m_legendView.setLegendMessage(legendMessageAtStep(m_step), m_step);
if (m_step == Step::SecondParameter) {
app()->setFirstResponder(m_legendView.textField());
m_graphView->setAreaHighlightColor(false);
m_graphView->setCursorView(&m_cursorView);
m_endSum = m_cursor->x();
m_legendView.setEditableZone(m_endSum);
m_legendView.setSumSymbol(m_step, m_startSum);
}
if (m_step == Step::FirstParameter) {
m_graphView->setAreaHighlight(NAN,NAN);
moveCursorHorizontallyToPosition(m_startSum);
m_legendView.setLegendMessage(legendMessageAtStep(m_step), m_step);
m_legendView.setEditableZone(m_startSum);
m_legendView.setSumSymbol(m_step);
m_graphView->reload();
}
reloadBannerView();
return true;
}
return false;
return SimpleInteractiveCurveViewController::handleEvent(event);
}
bool SumGraphController::moveCursorHorizontallyToPosition(double x) {
@@ -108,14 +74,14 @@ bool SumGraphController::moveCursorHorizontallyToPosition(double x) {
m_cursor->moveTo(x, y);
if (m_step == Step::FirstParameter) {
m_startSum = m_cursor->x();
m_legendView.setEditableZone(m_startSum);
}
if (m_step == Step::SecondParameter) {
m_graphView->setAreaHighlight(m_startSum, m_cursor->x());
m_endSum = m_cursor->x();
m_legendView.setEditableZone(m_endSum);
m_graphView->setAreaHighlight(m_startSum, m_endSum);
}
m_graphRange->panToMakePointVisible(x, y, k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio);
m_legendView.setEditableZone(m_cursor->x());
m_graphRange->panToMakePointVisible(x, y, cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio);
m_graphView->reload();
return true;
}
@@ -125,82 +91,67 @@ void SumGraphController::setRecord(Ion::Storage::Record record) {
}
bool SumGraphController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) {
AppsContainer * appsContainer = ((TextFieldDelegateApp *)app())->container();
Context * globalContext = appsContainer->globalContext();
double floatBody = PoincareHelpers::ApproximateToScalar<double>(text, *globalContext);
if (std::isnan(floatBody) || std::isinf(floatBody)) {
app()->displayWarning(I18n::Message::UndefinedValue);
double floatBody;
if (textFieldDelegateApp()->hasUndefinedValue(text, floatBody)) {
return false;
}
if (m_step == Step::SecondParameter && floatBody < m_startSum) {
if ((m_step == Step::SecondParameter && floatBody < m_startSum) || !moveCursorHorizontallyToPosition(floatBody)) {
app()->displayWarning(I18n::Message::ForbiddenValue);
return false;
}
if (moveCursorHorizontallyToPosition(floatBody)) {
handleEnter();
m_graphView->reload();
return true;
}
app()->displayWarning(I18n::Message::ForbiddenValue);
return false;
return handleEnter();
}
bool SumGraphController::textFieldDidAbortEditing(TextField * textField) {
char buffer[PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits)];
double parameter = NAN;
switch(m_step) {
case Step::FirstParameter:
parameter = m_startSum;
break;
case Step::SecondParameter:
parameter = m_endSum;
break;
default:
assert(false);
}
PrintFloat::convertFloatToText<double>(parameter, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits, Preferences::PrintFloatMode::Decimal);
textField->setText(buffer);
return true;
}
bool SumGraphController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) {
if ((event == Ion::Events::OK || event == Ion::Events::EXE) && !textField->isEditing()) {
return handleEnter();
}
bool SumGraphController::handleLeftRightEvent(Ion::Events::Event event) {
if (m_step == Step::Result) {
return handleEvent(event);
return false;
}
return TextFieldDelegate::textFieldDidReceiveEvent(textField, event);
const double oldPosition = m_cursor->x();
int direction = event == Ion::Events::Left ? -1 : 1;
double newPosition = cursorNextStep(oldPosition, direction);
if (m_step == Step::SecondParameter && newPosition < m_startSum) {
newPosition = m_startSum;
}
return moveCursorHorizontallyToPosition(newPosition);
}
bool SumGraphController::handleEnter() {
if (m_step == Step::Result) {
StackViewController * stack = (StackViewController *)parentResponder();
stack->pop();
return true;
} else {
if (m_step == Step::FirstParameter) {
m_graphView->setAreaHighlight(m_startSum, m_startSum);
m_endSum = m_startSum;
} else {
m_graphView->setAreaHighlightColor(true);
m_graphView->setCursorView(nullptr);
app()->setFirstResponder(this);
}
m_step = (Step)((int)m_step+1);
reloadBannerView();
}
if (m_step == Step::FirstParameter) {
m_step = Step::SecondParameter;
m_graphView->setAreaHighlight(m_startSum,m_startSum);
m_endSum = m_cursor->x();
m_legendView.setEditableZone(m_endSum);
m_legendView.setSumSymbol(m_step, m_startSum);
m_legendView.setLegendMessage(legendMessageAtStep(m_step), m_step);
return true;
}
m_step = (Step)((int)m_step+1);
FunctionApp * myApp = static_cast<FunctionApp *>(app());
assert(!m_record.isNull());
ExpiringPointer<Function> function = myApp->functionStore()->modelForRecord(m_record);
double sum = function->sumBetweenBounds(m_startSum, m_endSum, myApp->localContext());
m_legendView.setSumSymbol(m_step, m_startSum, m_endSum, sum, createFunctionLayout(function));
m_legendView.setLegendMessage(I18n::Message::Default, m_step);
m_graphView->setAreaHighlightColor(true);
m_graphView->setCursorView(nullptr);
myApp->setFirstResponder(this);
return true;
}
void SumGraphController::reloadBannerView() {
m_legendView.setLegendMessage(legendMessageAtStep(m_step), m_step);
double result;
Poincare::Layout functionLayout;
if (m_step == Step::Result) {
FunctionApp * myApp = static_cast<FunctionApp *>(app());
assert(!m_record.isNull());
ExpiringPointer<Function> function = myApp->functionStore()->modelForRecord(m_record);
result = function->sumBetweenBounds(m_startSum, m_endSum, myApp->localContext());
functionLayout = createFunctionLayout(function);
} else {
m_legendView.setEditableZone(m_cursor->x());
result = NAN;
functionLayout = Poincare::Layout();
}
m_legendView.setSumSymbol(m_step, m_startSum, m_endSum, result, functionLayout);
}
/* Legend View */
SumGraphController::LegendView::LegendView(SumGraphController * controller, InputEventHandlerDelegate * inputEventHandlerDelegate, CodePoint sumSymbol) :
@@ -248,7 +199,7 @@ void SumGraphController::LegendView::setSumSymbol(Step step, double start, doubl
} else {
char buffer[2+PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits)];
PrintFloat::convertFloatToText<double>(start, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits, Preferences::PrintFloatMode::Decimal);
Layout start = LayoutHelper::String(buffer, strlen(buffer), KDFont::SmallFont);
Layout start = LayoutHelper::String(buffer, strlen(buffer), k_font);
PrintFloat::convertFloatToText<double>(end, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits, Preferences::PrintFloatMode::Decimal);
Layout end = LayoutHelper::String(buffer, strlen(buffer), k_font);
m_sumLayout = CondensedSumLayout::Builder(
@@ -271,10 +222,6 @@ void SumGraphController::LegendView::setSumSymbol(Step step, double start, doubl
layoutSubviews(step);
}
int SumGraphController::LegendView::numberOfSubviews() const {
return 3;
}
View * SumGraphController::LegendView::subviewAtIndex(int index) {
assert(index >= 0 && index < 3);
if (index == 0) {
@@ -303,17 +250,12 @@ void SumGraphController::LegendView::layoutSubviews(Step step) {
m_legend.setFrame(KDRectZero);
}
KDSize largeCharSize = KDFont::LargeFont->glyphSize();
switch(step) {
case Step::FirstParameter:
m_editableZone.setFrame(KDRect(2*largeCharSize.width(), k_symbolHeightMargin+k_sigmaHeight/2, editableZoneWidth(), editableZoneHeight()));
return;
case Step::SecondParameter:
m_editableZone.setFrame(KDRect(2*largeCharSize.width(), k_symbolHeightMargin+k_sigmaHeight/2-editableZoneHeight(), editableZoneWidth(), editableZoneHeight()));
return;
default:
m_editableZone.setFrame(KDRectZero);
}
KDRect frame = (step == Step::Result) ? KDRectZero : KDRect(
2 * KDFont::LargeFont->glyphSize().width(),
k_symbolHeightMargin + k_sigmaHeight/2 - (step == Step::SecondParameter) * editableZoneHeight(),
editableZoneWidth(), editableZoneHeight()
);
m_editableZone.setFrame(frame);
}
}

View File

@@ -3,9 +3,7 @@
#include <escher.h>
#include "function_graph_view.h"
#include "interactive_curve_view_range.h"
#include "vertical_cursor_view.h"
#include "curve_view_cursor.h"
#include "simple_interactive_curve_view_controller.h"
#include "function.h"
#include "text_field_delegate.h"
@@ -13,7 +11,7 @@
namespace Shared {
class SumGraphController : public SimpleInteractiveCurveViewController, public TextFieldDelegate {
class SumGraphController : public SimpleInteractiveCurveViewController {
public:
SumGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, FunctionGraphView * curveView, InteractiveCurveViewRange * range, CurveViewCursor * cursor, CodePoint sumSymbol);
void viewWillAppear() override;
@@ -21,8 +19,6 @@ public:
bool handleEvent(Ion::Events::Event event) override;
void setRecord(Ion::Storage::Record record);
bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override;
bool textFieldDidAbortEditing(TextField * textField) override;
bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override;
protected:
virtual bool moveCursorHorizontallyToPosition(double position);
enum class Step {
@@ -36,17 +32,16 @@ protected:
Ion::Storage::Record m_record;
InteractiveCurveViewRange * m_graphRange;
private:
constexpr static float k_cursorTopMarginRatio = 0.06f; // (cursorHeight/2)/graphViewHeight
constexpr static float k_cursorBottomMarginRatio = 0.28f; // (cursorHeight/2+bannerHeigh)/graphViewHeight
float cursorTopMarginRatio() override { return 0.06f; }
float cursorBottomMarginRatio() override { return 0.28f; }
bool handleLeftRightEvent(Ion::Events::Event event) override;
bool handleEnter() override;
void reloadBannerView() override;
Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; }
Shared::CurveView * curveView() override { return m_graphView; }
virtual I18n::Message legendMessageAtStep(Step step) = 0;
virtual double cursorNextStep(double position, int direction) = 0;
virtual Poincare::Layout createFunctionLayout(ExpiringPointer<Function> function) = 0;
Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override { return m_graphRange; }
Shared::CurveView * curveView() override { return m_graphView; }
TextFieldDelegateApp * textFieldDelegateApp() override {
return static_cast<TextFieldDelegateApp *>(app());
}
bool handleEnter() override;
class LegendView : public View {
public:
LegendView(SumGraphController * controller, InputEventHandlerDelegate * inputEventHandlerDelegate, CodePoint sumSymbol);
@@ -59,7 +54,7 @@ private:
void drawRect(KDContext * ctx, KDRect rect) const override;
void setLegendMessage(I18n::Message message, Step step);
void setEditableZone(double d);
void setSumSymbol(Step step, double start = NAN, double end = NAN, double result = NAN, Poincare::Layout sequenceName = Poincare::Layout());
void setSumSymbol(Step step, double start, double end, double result, Poincare::Layout functionLayout);
private:
constexpr static KDCoordinate k_legendHeight = 35;
constexpr static const KDFont * k_font = KDFont::SmallFont;
@@ -67,7 +62,7 @@ private:
static KDCoordinate editableZoneHeight() { return k_font->glyphSize().height(); }
constexpr static KDCoordinate k_symbolHeightMargin = 8;
constexpr static KDCoordinate k_sigmaHeight = 18;
int numberOfSubviews() const override;
int numberOfSubviews() const override { return 3; }
View * subviewAtIndex(int index) override;
void layoutSubviews() override;
void layoutSubviews(Step step);

View File

@@ -15,7 +15,7 @@ Context * TextFieldDelegateApp::localContext() {
}
char TextFieldDelegateApp::XNT() {
return 'X';
return 'x';
}
bool TextFieldDelegateApp::textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) {
@@ -41,6 +41,15 @@ bool TextFieldDelegateApp::isAcceptableText(const char * text) {
return isAcceptableExpression(exp);
}
bool TextFieldDelegateApp::hasUndefinedValue(const char * text, double & value) {
value = PoincareHelpers::ApproximateToScalar<double>(text, *localContext());
bool isUndefined = std::isnan(value) || std::isinf(value);
if (isUndefined) {
displayWarning(I18n::Message::UndefinedValue);
}
return isUndefined;
}
/* Protected */
TextFieldDelegateApp::TextFieldDelegateApp(Container * container, Snapshot * snapshot, ViewController * rootViewController) :

View File

@@ -18,6 +18,7 @@ public:
bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override;
virtual bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override;
bool isAcceptableText(const char * text);
bool hasUndefinedValue(const char * text, double & value);
protected:
TextFieldDelegateApp(Container * container, Snapshot * snapshot, ViewController * rootViewController);
bool fieldDidReceiveEvent(EditableField * field, Responder * responder, Ion::Events::Event event);

View File

@@ -41,6 +41,24 @@ ValuesController::ValuesController(Responder * parentResponder, InputEventHandle
}
}
bool ValuesController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) {
int row = selectedRow();
int nbOfRows = numberOfRows();
bool didFinishEditing = EditableCellTableViewController::textFieldDidFinishEditing(textField, text, event);
if (didFinishEditing) {
if (nbOfRows != numberOfRows()) {
// Reload the whole table, if a value is appended.
selectableTableView()->reloadData();
} else {
// Reload the row, if an existing value is edited.
for (int i = 0; i < numberOfColumns(); i++) {
selectableTableView()->reloadCellAtLocation(i, row);
}
}
}
return didFinishEditing;
}
const char * ValuesController::title() {
return I18n::translate(I18n::Message::ValuesTab);
}

View File

@@ -16,6 +16,7 @@ namespace Shared {
class ValuesController : public EditableCellTableViewController, public ButtonRowDelegate, public AlternateEmptyViewDefaultDelegate {
public:
ValuesController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, ButtonRowController * header, I18n::Message parameterTitle, IntervalParameterController * intervalParameterController, Interval * interval);
bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override;
const char * title() override;
Interval * interval();
int numberOfColumns() override;

View File

@@ -0,0 +1,36 @@
#include "xy_banner_view.h"
#include <assert.h>
namespace Shared {
XYBannerView::XYBannerView(
Responder * parentResponder,
InputEventHandlerDelegate * inputEventHandlerDelegate,
TextFieldDelegate * textFieldDelegate
) :
m_abscissaSymbol(Font(), 1.0f, 0.5f, TextColor(), BackgroundColor()),
m_abscissaValue(
parentResponder,
m_draftTextBuffer,
m_draftTextBuffer,
TextField::maxBufferSize(),
inputEventHandlerDelegate,
textFieldDelegate,
false,
Font(),
0.0f, 0.5f,
TextColor(),
BackgroundColor()
),
m_ordinateView(Font(), 0.5f, 0.5f, TextColor(), BackgroundColor())
{
m_draftTextBuffer[0] = 0;
}
View * XYBannerView::subviewAtIndex(int index) {
assert(0 <= index && index < numberOfSubviews());
View * subviews[] = {&m_abscissaSymbol, &m_abscissaValue, &m_ordinateView};
return subviews[index];
}
}

View File

@@ -0,0 +1,31 @@
#ifndef SHARED_XY_BANNER_VIEW_H
#define SHARED_XY_BANNER_VIEW_H
#include "banner_view.h"
namespace Shared {
class XYBannerView : public BannerView {
public:
XYBannerView(
Responder * parentResponder,
InputEventHandlerDelegate * inputEventHandlerDelegate,
TextFieldDelegate * textFieldDelegate
);
BufferTextView * abscissaSymbol() { return &m_abscissaSymbol; }
TextField * abscissaValue() { return &m_abscissaValue; }
BufferTextView * ordinateView() { return &m_ordinateView; }
static constexpr int k_numberOfSubviews = 3;
protected:
View * subviewAtIndex(int index) override;
private:
int numberOfSubviews() const override { return k_numberOfSubviews; }
BufferTextView m_abscissaSymbol;
TextField m_abscissaValue;
BufferTextView m_ordinateView;
char m_draftTextBuffer[TextField::maxBufferSize()];
};
}
#endif

View File

@@ -18,7 +18,7 @@ Equation::Equation(Ion::Storage::Record record) :
}
bool Equation::containsIComplex(Context * context) const {
return expressionClone().recursivelyMatches([](const Expression e, Context & context, bool replaceSymbols) { return e.type() == ExpressionNode::Type::Constant && static_cast<const Constant &>(e).isIComplex(); }, *context, true);
return expressionClone().recursivelyMatches([](const Expression e, Context & context) { return e.type() == ExpressionNode::Type::Constant && static_cast<const Constant &>(e).isIComplex(); }, *context, true);
}
Expression Equation::Model::standardForm(const Storage::Record * record, Context * context) const {
@@ -28,7 +28,7 @@ Expression Equation::Model::standardForm(const Storage::Record * record, Context
m_standardForm = Unreal::Builder();
return m_standardForm;
}
if (e.recursivelyMatches([](const Expression e, Context & context, bool replaceSymbols) { return e.type() == ExpressionNode::Type::Undefined || e.type() == ExpressionNode::Type::Infinity || Expression::IsMatrix(e, context, replaceSymbols); }, *context, true)) {
if (e.recursivelyMatches([](const Expression e, Context & context) { return e.type() == ExpressionNode::Type::Undefined || e.type() == ExpressionNode::Type::Infinity || Expression::IsMatrix(e, context); }, *context, true)) {
m_standardForm = Undefined::Builder();
return m_standardForm;
}

View File

@@ -1,21 +1,19 @@
#include "box_banner_view.h"
#include <assert.h>
namespace Statistics {
BoxBannerView::BoxBannerView() :
m_seriesName(KDFont::SmallFont, 0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle),
m_calculationName(KDFont::SmallFont, I18n::Message::Minimum, 0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle),
m_calculationValue(KDFont::SmallFont, 1.0f, 0.5f, KDColorBlack, Palette::GreyMiddle)
m_seriesName(Font(), 0.0f, 0.5f, TextColor(), BackgroundColor()),
m_calculationName(Font(), I18n::Message::Minimum, 0.0f, 0.5f, TextColor(), BackgroundColor()),
m_calculationValue(Font(), 1.0f, 0.5f, TextColor(), BackgroundColor())
{
}
TextView * BoxBannerView::textViewAtIndex(int index) const {
const TextView * textViews[3] = {&m_seriesName, &m_calculationName, &m_calculationValue};
return (TextView *)textViews[index];
}
MessageTextView * BoxBannerView::messageTextViewAtIndex(int index) const {
return index == 1 ? (MessageTextView *)&m_calculationName : nullptr;
View * BoxBannerView::subviewAtIndex(int index) {
assert(0 <= index && index < numberOfSubviews());
View * subviews[] = {&m_seriesName, &m_calculationName, &m_calculationValue};
return subviews[index];
}
}

View File

@@ -10,10 +10,13 @@ namespace Statistics {
class BoxBannerView : public Shared::BannerView {
public:
BoxBannerView();
BufferTextView * seriesName() { return &m_seriesName; }
MessageTextView * calculationName() { return &m_calculationName; }
BufferTextView * calculationValue() { return &m_calculationValue; }
private:
int numberOfSubviews() const override { return 3; }
TextView * textViewAtIndex(int i) const override;
MessageTextView * messageTextViewAtIndex(int i) const override;
static constexpr int k_numberOfSubviews = 3;
int numberOfSubviews() const override { return k_numberOfSubviews; }
View * subviewAtIndex(int index) override;
BufferTextView m_seriesName;
MessageTextView m_calculationName;
BufferTextView m_calculationValue;

View File

@@ -43,12 +43,11 @@ void BoxController::reloadBannerView() {
// Set series name
char seriesChar = '0' + selectedSeriesIndex() + 1;
char bufferName[] = {' ', 'V', seriesChar, '/', 'N', seriesChar, 0};
m_view.editableBannerView()->setLegendAtIndex(bufferName, 0);
m_view.bannerView()->seriesName()->setText(bufferName);
// Set calculation name
I18n::Message calculationName[5] = {I18n::Message::Minimum, I18n::Message::FirstQuartile, I18n::Message::Median, I18n::Message::ThirdQuartile, I18n::Message::Maximum};
m_view.editableBannerView()->setMessageAtIndex(calculationName[selectedQuantile], 1);
m_view.bannerView()->calculationName()->setMessage(calculationName[selectedQuantile]);
// Set calculation result
assert(UTF8Decoder::CharSizeOfCodePoint(' ') == 1);
@@ -60,7 +59,9 @@ void BoxController::reloadBannerView() {
int numberOfChar = PoincareHelpers::ConvertFloatToText<double>(calculation, buffer, bufferSize - 1, Constant::LargeNumberOfSignificantDigits);
buffer[numberOfChar++] = ' ';
buffer[numberOfChar] = 0;
m_view.editableBannerView()->setLegendAtIndex(buffer, 2);
m_view.bannerView()->calculationValue()->setText(buffer);
m_view.bannerView()->reload();
}
}

View File

@@ -5,23 +5,19 @@
namespace Statistics {
HistogramBannerView::HistogramBannerView() :
m_intervalLegendView(KDFont::SmallFont, I18n::Message::Interval, 0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle),
m_intervalView(KDFont::SmallFont, 0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle),
m_sizeLegendView(KDFont::SmallFont, I18n::Message::Size, 0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle),
m_sizeView(KDFont::SmallFont, 0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle),
m_frequencyLegendView(KDFont::SmallFont, I18n::Message::Frequency, 1.0f, 0.5f, KDColorBlack, Palette::GreyMiddle),
m_frequencyView(KDFont::SmallFont, 0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle)
m_intervalLegendView(Font(), I18n::Message::Interval, 0.0f, 0.5f, TextColor(), BackgroundColor()),
m_intervalView(Font(), 0.0f, 0.5f, TextColor(), BackgroundColor()),
m_sizeLegendView(Font(), I18n::Message::Size, 0.0f, 0.5f, TextColor(), BackgroundColor()),
m_sizeView(Font(), 0.0f, 0.5f, TextColor(), BackgroundColor()),
m_frequencyLegendView(Font(), I18n::Message::Frequency, 1.0f, 0.5f, TextColor(), BackgroundColor()),
m_frequencyView(Font(), 0.0f, 0.5f, TextColor(), BackgroundColor())
{
}
TextView * HistogramBannerView::textViewAtIndex(int i) const {
const TextView * textViews[k_numberOfSubviews] = {&m_intervalLegendView, &m_intervalView, &m_sizeLegendView, &m_sizeView, &m_frequencyLegendView, &m_frequencyView};
return (TextView *)textViews[i];
}
MessageTextView * HistogramBannerView::messageTextViewAtIndex(int index) const {
const MessageTextView * textViews[k_numberOfSubviews] = {&m_intervalLegendView, nullptr, &m_sizeLegendView, nullptr, &m_frequencyLegendView, nullptr};
return (MessageTextView *)textViews[index];
View * HistogramBannerView::subviewAtIndex(int index) {
assert(0 <= index && index < numberOfSubviews());
View * subviews[] = {&m_intervalLegendView, &m_intervalView, &m_sizeLegendView, &m_sizeView, &m_frequencyLegendView, &m_frequencyView};
return subviews[index];
}
}

View File

@@ -10,11 +10,13 @@ namespace Statistics {
class HistogramBannerView : public Shared::BannerView {
public:
HistogramBannerView();
BufferTextView * intervalView() { return &m_intervalView; }
BufferTextView * sizeView() { return &m_sizeView; }
BufferTextView * frequencyView() { return &m_frequencyView; }
private:
static constexpr int k_numberOfSubviews = 6;
int numberOfSubviews() const override { return k_numberOfSubviews; }
TextView * textViewAtIndex(int i) const override;
MessageTextView * messageTextViewAtIndex(int index) const override;
View * subviewAtIndex(int index) override;
MessageTextView m_intervalLegendView;
BufferTextView m_intervalView;
MessageTextView m_sizeLegendView;

View File

@@ -124,7 +124,7 @@ void HistogramController::reloadBannerView() {
numberOfChar+= UTF8Decoder::CodePointToChars(' ', buffer + numberOfChar, bufferSize - numberOfChar);
}
buffer[k_maxIntervalLegendLength] = 0;
m_view.editableBannerView()->setLegendAtIndex(buffer, 1);
m_view.bannerView()->intervalView()->setText(buffer);
// Add Size Data
numberOfChar = 0;
@@ -142,7 +142,7 @@ void HistogramController::reloadBannerView() {
numberOfChar+= UTF8Decoder::CodePointToChars(' ', buffer + numberOfChar, bufferSize - numberOfChar);
}
buffer[k_maxLegendLength] = 0;
m_view.editableBannerView()->setLegendAtIndex(buffer, 3);
m_view.bannerView()->sizeView()->setText(buffer);
// Add Frequency Data
numberOfChar = 0;
@@ -159,7 +159,9 @@ void HistogramController::reloadBannerView() {
numberOfChar+= UTF8Decoder::CodePointToChars(' ', buffer + numberOfChar, bufferSize - numberOfChar);
}
buffer[k_maxLegendLength] = 0;
m_view.editableBannerView()->setLegendAtIndex(buffer, 5);
m_view.bannerView()->frequencyView()->setText(buffer);
m_view.bannerView()->reload();
}
bool HistogramController::moveSelectionHorizontally(int deltaIndex) {

View File

@@ -17,7 +17,7 @@ public:
MultipleBoxesView(Store * store, BoxView::Quantile * selectedQuantile);
// MultipleDataView
int seriesOfSubviewAtIndex(int index) override;
const BoxBannerView * bannerView() const override { return &m_bannerView; }
BoxBannerView * bannerView() override { return &m_bannerView; }
BoxView * dataViewAtIndex(int index) override;
void layoutDataSubviews() override;
void reload() override;

View File

@@ -49,7 +49,7 @@ int MultipleDataView::numberOfSubviews() const {
View * MultipleDataView::subviewAtIndex(int index) {
if (index == MultipleDataView::numberOfSubviews() -1) {
return editableBannerView();
return bannerView();
}
int seriesIndex = 0;
int nonEmptySeriesIndex = -1;
@@ -68,7 +68,7 @@ View * MultipleDataView::subviewAtIndex(int index) {
void MultipleDataView::layoutSubviews() {
// We need to set the banner width first, so its height can be computed
editableBannerView()->setFrame(KDRect(0, 0, bounds().width(), 0));
bannerView()->setFrame(KDRect(0, 0, bounds().width(), 0));
layoutDataSubviews();
layoutBanner();
}
@@ -76,7 +76,7 @@ void MultipleDataView::layoutSubviews() {
void MultipleDataView::layoutDataSubviews() {
int numberDataSubviews = m_store->numberOfNonEmptySeries();
assert(numberDataSubviews > 0);
KDCoordinate bannerHeight = bannerFrame().height();
KDCoordinate bannerHeight = bannerView()->minimalSizeForOptimalDisplay().height();
KDCoordinate subviewHeight = (bounds().height() - bannerHeight)/numberDataSubviews + 1; // +1 to make sure that all pixel rows are drawn
int displayedSubviewIndex = 0;
for (int i = 0; i < Store::k_numberOfSeries; i++) {
@@ -95,18 +95,18 @@ void MultipleDataView::changeDataViewSelection(int index, bool select) {
}
KDRect MultipleDataView::bannerFrame() const {
KDCoordinate bannerHeight = bannerView()->minimalSizeForOptimalDisplay().height();
KDCoordinate bannerHeight = const_cast<MultipleDataView *>(this)->bannerView()->minimalSizeForOptimalDisplay().height();
KDRect frame = KDRect(0, bounds().height() - bannerHeight, bounds().width(), bannerHeight);
return frame;
}
void MultipleDataView::layoutBanner() {
KDCoordinate bannerHeight = bannerFrame().height();
KDCoordinate bannerHeight = bannerView()->minimalSizeForOptimalDisplay().height();
if (m_displayBanner) {
editableBannerView()->setFrame(bannerFrame());
bannerView()->setFrame(bannerFrame());
} else {
KDRect frame = KDRect(0, bounds().height() - bannerHeight, bounds().width(), 0);
editableBannerView()->setFrame(frame);
bannerView()->setFrame(frame);
}
}

View File

@@ -19,7 +19,6 @@ public:
void selectDataView(int index);
void deselectDataView(int index);
virtual Shared::CurveView * dataViewAtIndex(int index) = 0;
Shared::BannerView * editableBannerView() { return const_cast<Shared::BannerView *>(bannerView()); }
// Index/series
int indexOfSubviewAtSeries(int series);
@@ -32,7 +31,7 @@ public:
// View
int numberOfSubviews() const override;
protected:
virtual const Shared::BannerView * bannerView() const = 0;
virtual Shared::BannerView * bannerView() = 0;
void layoutSubviews() override;
virtual void layoutDataSubviews();
View * subviewAtIndex(int index) override;

View File

@@ -16,7 +16,7 @@ public:
MultipleHistogramsView(HistogramController * controller, Store * store);
// MultipleDataView
int seriesOfSubviewAtIndex(int index) override;
const HistogramBannerView * bannerView() const override { return &m_bannerView; }
HistogramBannerView * bannerView() override { return &m_bannerView; }
HistogramView * dataViewAtIndex(int index) override;
private:
void layoutSubviews() override;

View File

@@ -359,10 +359,11 @@ CodePoint TextField::XNTCodePoint(CodePoint defaultXNTCodePoint) {
/* Let's assume everything before the cursor is nested correctly, which is
* reasonable if the expression is being entered left-to-right. */
const char * text = this->text();
assert(text == m_contentView.draftTextBuffer());
const char * location = cursorLocation();
UTF8Decoder decoder(text, text + (location - m_contentView.draftTextBuffer()));
UTF8Decoder decoder(text, location);
unsigned level = 0;
while (location > m_contentView.draftTextBuffer()) {
while (location > text) {
CodePoint c = decoder.previousCodePoint();
location = decoder.stringPosition();
switch (c) {
@@ -373,14 +374,15 @@ CodePoint TextField::XNTCodePoint(CodePoint defaultXNTCodePoint) {
break;
}
// Skip over whitespace.
while (location > m_contentView.draftTextBuffer() && decoder.previousCodePoint() == ' ') {
while (location > text && decoder.previousCodePoint() == ' ') {
location = decoder.stringPosition();
}
location = decoder.stringPosition();
// We found the next innermost function we are currently in.
for (size_t i = 0; i < sizeof(sFunctions)/sizeof(sFunctions[0]); i++) {
const char * name = sFunctions[i].name;
size_t length = strlen(name);
if (location >= (m_contentView.draftTextBuffer() + length) && memcmp(&text[(location - m_contentView.draftTextBuffer()) - length], name, length) == 0) {
if ((location >= text + length) && memcmp(&text[(location - text) - length], name, length) == 0) {
return sFunctions[i].xnt;
}
}

View File

@@ -37,7 +37,7 @@ Event getPlatformEvent() {
return Events::BatteryCharging;
}
return Events::None;
return None;
}
}

View File

@@ -22,7 +22,6 @@ public:
// Layout
Type type() const override { return Type::CodePointLayout; }
bool isIdenticalTo(Layout l) override;
// CodePointLayout
CodePoint codePoint() const { return m_codePoint; }
@@ -63,6 +62,7 @@ protected:
private:
void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override;
bool isMultiplicationCodePoint() const;
bool protectedIsIdenticalTo(Layout l) override;
CodePoint m_codePoint;
const KDFont * m_font;
};

View File

@@ -15,7 +15,6 @@ public:
// Layout
Type type() const override { return Type::EmptyLayout; }
bool isIdenticalTo(Layout l) override;
EmptyLayoutNode(Color color = Color::Yellow, bool visible = true, const KDFont * font = KDFont::LargeFont, bool margins = true) :
LayoutNode(),
@@ -67,6 +66,7 @@ private:
void moveCursorVertically(VerticalDirection direction, LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited) override;
bool willAddSibling(LayoutCursor * cursor, LayoutNode * sibling, bool moveCursor) override;
void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override;
bool protectedIsIdenticalTo(Layout l) override;
bool m_isVisible;
Color m_color;

View File

@@ -129,13 +129,14 @@ public:
bool isRationalZero() const;
bool isRationalOne() const;
bool isRandom() const { return node()->isRandom(); }
static bool IsRandom(const Expression e, Context & context, bool replaceSymbols);
typedef bool (*ExpressionTest)(const Expression e, Context & context, bool replaceSymbols);
bool recursivelyMatches(ExpressionTest test, Context & context, bool replaceSymbols) const;
bool isApproximate(Context & context) const;
bool recursivelyMatchesInfinity(Context & context) { return recursivelyMatches([](const Expression e, Context & context, bool replaceSymbols) { return e.type() == ExpressionNode::Type::Infinity; }, context, true); }
static bool IsMatrix(const Expression e, Context & context, bool replaceSymbols);
bool isParameteredExpression() const { return node()->isParameteredExpression(); }
typedef bool (*ExpressionTest)(const Expression e, Context & context);
bool recursivelyMatches(ExpressionTest test, Context & context, bool replaceSymbols = true) const;
// Set of ExpressionTest that can be used with recursivelyMatches
static bool IsApproximate(const Expression e, Context & context);
static bool IsRandom(const Expression e, Context & context);
static bool IsMatrix(const Expression e, Context & context);
static bool IsInfinity(const Expression e, Context & context);
/* 'characteristicXRange' tries to assess the range on x where the expression
* (considered as a function on x) has an interesting evolution. For example,
* the period of the function on 'x' if it is periodic. If

View File

@@ -56,7 +56,7 @@ public:
virtual Type type() const = 0;
// Comparison
virtual bool isIdenticalTo(Layout l);
bool isIdenticalTo(Layout l);
// Rendering
void draw(KDContext * ctx, KDPoint p, KDColor expressionColor = KDColorBlack, KDColor backgroundColor = KDColorWhite);
@@ -141,6 +141,8 @@ public:
virtual void didRemoveChildAtIndex(int index, LayoutCursor * cursor, bool force) {}
protected:
virtual bool protectedIsIdenticalTo(Layout l);
// Tree navigation
virtual void moveCursorVertically(VerticalDirection direction, LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited);

View File

@@ -84,10 +84,9 @@ public:
/* createInverse can be called on any matrix reduce or not, approximate or not. */
Expression inverse(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const;
#endif
private:
// TODO: find another solution for inverse and determinant (avoid capping the matrix)
static constexpr int k_maxNumberOfCoefficients = 100;
private:
MatrixNode * node() const { return static_cast<MatrixNode *>(Expression::node()); }
void setNumberOfRows(int rows) { assert(rows >= 0); node()->setNumberOfRows(rows); }
void setNumberOfColumns(int columns) { assert(columns >= 0); node()->setNumberOfColumns(columns); }

View File

@@ -21,7 +21,6 @@ public:
// Layout
Type type() const override { return Type::NthRootLayout; }
bool isIdenticalTo(Layout l) override;
// LayoutNode
void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override;
@@ -56,6 +55,7 @@ private:
constexpr static KDCoordinate k_radixLineThickness = 1;
KDSize adjustedIndexSize();
void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override;
bool protectedIsIdenticalTo(Layout l) override;
LayoutNode * radicandLayout() { return childAtIndex(0); }
LayoutNode * indexLayout() { return m_hasIndex ? childAtIndex(1) : nullptr; }
bool m_hasIndex;

Some files were not shown because too many files have changed in this diff Show More