mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-19 00:37:25 +01:00
Merge branch 'version-11' into f7
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -23,7 +23,7 @@ const Image * App::Descriptor::icon() {
|
||||
App::Snapshot::Snapshot() :
|
||||
Shared::FunctionApp::Snapshot::Snapshot(),
|
||||
m_functionStore(),
|
||||
m_graphRange(&m_cursor)
|
||||
m_graphRange()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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; };
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -21,7 +21,7 @@ const Image * App::Descriptor::icon() {
|
||||
App::Snapshot::Snapshot() :
|
||||
Shared::FunctionApp::Snapshot::Snapshot(),
|
||||
m_sequenceStore(),
|
||||
m_graphRange(&m_cursor)
|
||||
m_graphRange()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 \
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) :
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
36
apps/shared/xy_banner_view.cpp
Normal file
36
apps/shared/xy_banner_view.cpp
Normal 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];
|
||||
}
|
||||
|
||||
}
|
||||
31
apps/shared/xy_banner_view.h
Normal file
31
apps/shared/xy_banner_view.h
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ Event getPlatformEvent() {
|
||||
return Events::BatteryCharging;
|
||||
}
|
||||
|
||||
return Events::None;
|
||||
return None;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user