Merge branch 'master' into python_turtle

This commit is contained in:
Léa Saviot
2018-12-17 15:49:46 +01:00
739 changed files with 13354 additions and 5974 deletions

View File

@@ -35,6 +35,7 @@ include escher/Makefile
# Executable Makefiles
include apps/Makefile
include build/struct_layout/Makefile
include build/scenario/Makefile
include quiz/Makefile # Quiz needs to be included at the end
products += $(objs)

View File

@@ -29,7 +29,7 @@ app_objs += $(addprefix apps/,\
suspend_timer.o\
title_bar_view.o\
variable_box_controller.o\
variable_box_leaf_cell.o\
variable_box_empty_controller.o\
)
snapshots_declaration = $(foreach i,$(apps),$(i)::Snapshot m_snapshot$(subst :,,$(i))Snapshot;)

View File

@@ -57,7 +57,7 @@ AppsContainer::AppsContainer() :
m_window(),
m_emptyBatteryWindow(),
m_globalContext(),
m_variableBoxController(&m_globalContext),
m_variableBoxController(),
m_examPopUpController(this),
#if EPSILON_BOOT_PROMPT == EPSILON_BETA_PROMPT
m_promptController(sPromptMessages, sPromptColors, 8),
@@ -73,7 +73,20 @@ AppsContainer::AppsContainer() :
m_usbConnectedSnapshot()
{
m_emptyBatteryWindow.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height));
#if __EMSCRIPTEN__
/* AppsContainer::poincareCircuitBreaker uses Ion::Keyboard::scan(), which
* calls emscripten_sleep. If we set the poincare circuit breaker, we would
* need to whitelist all the methods that might be in the call stack when
* poincareCircuitBreaker is run. This means either whitelisting all Epsilon
* (which makes bigger files to download and slower execution), or
* whitelisting all the symbols (that's a big amount of symbols to find and
* quite painy to maintain).
* We just remove the circuit breaker for now.
* TODO: Put the Poincare circuit breaker back on epsilon's web emulator */
#else
Poincare::Expression::setCircuitBreaker(AppsContainer::poincareCircuitBreaker);
#endif
Ion::Storage::sharedStorage()->setDelegate(this);
}
bool AppsContainer::poincareCircuitBreaker() {
@@ -94,6 +107,9 @@ App::Snapshot * AppsContainer::usbConnectedAppSnapshot() {
}
void AppsContainer::reset() {
// Empty storage (delete functions, variables, python scripts)
Ion::Storage::sharedStorage()->destroyAllRecords();
// Empty clipboard
Clipboard::sharedClipboard()->reset();
for (int i = 0; i < numberOfApps(); i++) {
appSnapshotAtIndex(i)->reset();
@@ -213,28 +229,45 @@ void AppsContainer::switchTo(App::Snapshot * snapshot) {
void AppsContainer::run() {
window()->setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height));
refreshPreferences();
#if EPSILON_ONBOARDING_APP
switchTo(onBoardingAppSnapshot());
#else
if (numberOfApps() == 2) {
switchTo(appSnapshotAtIndex(1));
} else {
switchTo(appSnapshotAtIndex(0));
}
#endif
/* ExceptionCheckpoint stores the value of the stack pointer when setjump is
* called. During a longjump, the stack pointer is set to this stored stack
* pointer value, so the method where we call setjump must remain in the call
* tree for the jump to work. */
Poincare::ExceptionCheckpoint ecp;
if (!ExceptionRun(ecp)) {
if (ExceptionRun(ecp)) {
/* Normal execution. The exception checkpoint must be created before
* switching to the first app, because the first app might create nodes on
* the pool. */
#if EPSILON_ONBOARDING_APP
switchTo(onBoardingAppSnapshot());
#else
if (numberOfApps() == 2) {
switchTo(appSnapshotAtIndex(1));
} else {
switchTo(appSnapshotAtIndex(0));
}
#endif
} else {
// Exception
if (activeApp() != nullptr) {
/* The app models can reference layouts or expressions that have been
* destroyed from the pool. To avoid using them before packing the app
* (in App::willBecomeInactive for instance), we tidy them early on. */
activeApp()->snapshot()->tidy();
/* When an app encoutered an exception due to a full pool, the next time
* the user enters the app, the same exception could happen again which
* would prevent from reopening the app. To avoid being stuck outside the
* app causing the issue, we reset its snapshot when leaving it due to
* exception. For instance, the calculation app can encounter an
* exception when displaying too many huge layouts, if we don't clean the
* history here, we will be stuck outside the calculation app. */
activeApp()->snapshot()->reset();
}
switchTo(appSnapshotAtIndex(0));
Poincare::Tidy();
activeApp()->displayWarning(I18n::Message::AppMemoryFull, true);
activeApp()->displayWarning(I18n::Message::PoolMemoryFull1, I18n::Message::PoolMemoryFull2, true);
}
Container::run();
switchTo(nullptr);
@@ -293,6 +326,18 @@ void AppsContainer::examDeactivatingPopUpIsDismissed() {
}
}
void AppsContainer::storageDidChangeForRecord(const Ion::Storage::Record record) {
if (activeApp()) {
activeApp()->snapshot()->storageDidChangeForRecord(record);
}
}
void AppsContainer::storageIsFull() {
if (activeApp()) {
activeApp()->displayWarning(I18n::Message::StorageMemoryFull1, I18n::Message::StorageMemoryFull2, true);
}
}
Window * AppsContainer::window() {
return &m_window;
}

View File

@@ -14,6 +14,7 @@
#include "battery_timer.h"
#include "suspend_timer.h"
#include "backlight_dimming_timer.h"
#include "shared/global_context.h"
#define USE_PIC_VIEW_APP 0
#if USE_PIC_VIEW_APP
@@ -26,7 +27,7 @@
#include <ion/events.h>
class AppsContainer : public Container, ExamPopUpControllerDelegate {
class AppsContainer : public Container, ExamPopUpControllerDelegate, Ion::StorageDelegate {
public:
AppsContainer();
static bool poincareCircuitBreaker();
@@ -54,6 +55,9 @@ public:
void redrawWindow();
// Exam pop-up controller delegate
void examDeactivatingPopUpIsDismissed() override;
// Ion::StorageDelegate
void storageDidChangeForRecord(const Ion::Storage::Record record) override;
void storageIsFull() override;
protected:
Home::App::Snapshot * homeAppSnapshot() { return &m_homeSnapshot; }
private:
@@ -69,7 +73,7 @@ private:
#if USE_PIC_VIEW_APP
PicViewApp m_picViewApp;
#endif
Poincare::GlobalContext m_globalContext;
Shared::GlobalContext m_globalContext;
MathToolbox m_mathToolbox;
VariableBoxController m_variableBoxController;
ExamPopUpController m_examPopUpController;

View File

@@ -1,8 +1,8 @@
#include "app.h"
#include "../apps_container.h"
#include "../shared/poincare_helpers.h"
#include "calculation_icon.h"
#include "../i18n.h"
#include <poincare/symbol.h>
using namespace Poincare;
@@ -35,10 +35,6 @@ App::Descriptor * App::Snapshot::descriptor() {
return &descriptor;
}
CalculationStore * App::Snapshot::calculationStore() {
return &m_calculationStore;
}
void App::Snapshot::tidy() {
m_calculationStore.tidy();
}
@@ -46,68 +42,36 @@ void App::Snapshot::tidy() {
App::App(Container * container, Snapshot * snapshot) :
ExpressionFieldDelegateApp(container, snapshot, &m_editExpressionController),
m_historyController(&m_editExpressionController, snapshot->calculationStore()),
m_editExpressionController(&m_modalViewController, &m_historyController, snapshot->calculationStore())
m_editExpressionController(&m_modalViewController, this, &m_historyController, snapshot->calculationStore())
{
}
bool App::textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) {
if ((event == Ion::Events::Var || event == Ion::Events::XNT) && TextFieldDelegateApp::textFieldDidReceiveEvent(textField, event)) {
if (textField->isEditing() && textField->shouldFinishEditing(event) && textField->text()[0] == 0) {
return true;
}
if (textField->isEditing() && textField->textFieldShouldFinishEditing(event)) {
if (textField->text()[0] == 0) {
return true;
}
if (!textInputIsCorrect(textField->text())) {
displayWarning(I18n::Message::SyntaxError);
return true;
}
}
return false;
return Shared::ExpressionFieldDelegateApp::textFieldDidReceiveEvent(textField, event);
}
bool App::layoutFieldDidReceiveEvent(::LayoutField * layoutField, Ion::Events::Event event) {
if ((event == Ion::Events::Var || event == Ion::Events::XNT) && ExpressionFieldDelegateApp::layoutFieldDidReceiveEvent(layoutField, event)) {
if (layoutField->isEditing() && layoutField->shouldFinishEditing(event) && !layoutField->hasText()) {
return true;
}
if (layoutField->isEditing() && layoutField->layoutFieldShouldFinishEditing(event)) {
if (!layoutField->hasText()) {
return true;
}
return Shared::ExpressionFieldDelegateApp::layoutFieldDidReceiveEvent(layoutField, event);
}
char bufferForParsing[Calculation::k_printedExpressionSize];
layoutField->serialize(bufferForParsing, Calculation::k_printedExpressionSize);
if (!textInputIsCorrect(bufferForParsing)) {
displayWarning(I18n::Message::SyntaxError);
return true;
bool App::isAcceptableExpression(const Poincare::Expression expression) {
{
Expression ansExpression = static_cast<Snapshot *>(snapshot())->calculationStore()->ansExpression(localContext());
if (!TextFieldDelegateApp::ExpressionCanBeSerialized(expression, true, ansExpression)) {
return false;
}
}
return false;
return TextFieldDelegateApp::isAcceptableExpression(expression);
}
bool App::textInputIsCorrect(const char * text) {
/* Here, we check that the expression entered by the user can be printed with
* less than k_printedExpressionLength characters. Otherwise, we prevent the
* user from adding this expression to the calculation store. */
Expression exp = Expression::parse(text);
if (exp.isUninitialized()) {
return false;
}
Expression ansExpression = static_cast<Snapshot *>(snapshot())->calculationStore()->ansExpression(localContext());
exp = exp.replaceSymbolWithExpression(Symbol::SpecialSymbols::Ans, ansExpression);
char buffer[Calculation::k_printedExpressionSize];
int length = PoincareHelpers::Serialize(exp, buffer, sizeof(buffer));
/* if the buffer is totally full, it is VERY likely that writeTextInBuffer
* escaped before printing utterly the expression. */
if (length >= Calculation::k_printedExpressionSize-1) {
return false;
}
return true;
}
const char * App::XNT() {
return "x";
char App::XNT() {
return 'x';
}
}

View File

@@ -22,15 +22,17 @@ public:
App * unpack(Container * container) override;
void reset() override;
Descriptor * descriptor() override;
CalculationStore * calculationStore();
CalculationStore * calculationStore() { return &m_calculationStore; }
private:
void tidy() override;
CalculationStore m_calculationStore;
};
bool textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) override;
bool textInputIsCorrect(const char * text);
bool layoutFieldDidReceiveEvent(::LayoutField * layoutField, Ion::Events::Event event) override;
const char * XNT() override;
// TextFieldDelegateApp
bool isAcceptableExpression(const Poincare::Expression expression) override;
bool storeExpressionAllowed() const override { return true; }
char XNT() override;
private:
App(Container * container, Snapshot * snapshot);
HistoryController m_historyController;

View File

@@ -1,10 +1,10 @@
#include "calculation.h"
#include "calculation_store.h"
#include "../shared/poincare_helpers.h"
#include <string.h>
#include <cmath>
#include <poincare/symbol.h>
#include <poincare/undefined.h>
#include <string.h>
#include <cmath>
using namespace Poincare;
using namespace Shared;
@@ -34,10 +34,13 @@ void Calculation::reset() {
void Calculation::setContent(const char * c, Context * context, Expression ansExpression) {
reset();
Expression input = Expression::parse(c).replaceSymbolWithExpression(Symbol::SpecialSymbols::Ans, ansExpression);
/* We do not store directly the text enter by the user because we do not want
* to keep Ans symbol in the calculation store. */
PoincareHelpers::Serialize(input, m_inputText, sizeof(m_inputText));
{
Symbol ansSymbol = Symbol::Ans();
Expression input = Expression::Parse(c).replaceSymbolWithExpression(ansSymbol, ansExpression);
/* We do not store directly the text enter by the user because we do not want
* to keep Ans symbol in the calculation store. */
PoincareHelpers::Serialize(input, m_inputText, sizeof(m_inputText));
}
Expression exactOutput = PoincareHelpers::ParseAndSimplify(m_inputText, *context);
PoincareHelpers::Serialize(exactOutput, m_exactOutputText, sizeof(m_exactOutputText));
Expression approximateOutput = PoincareHelpers::Approximate<double>(exactOutput, *context);
@@ -49,11 +52,15 @@ KDCoordinate Calculation::height(Context * context) {
Layout inputLayout = createInputLayout();
KDCoordinate inputHeight = inputLayout.layoutSize().height();
Layout approximateLayout = createApproximateOutputLayout(context);
KDCoordinate approximateOutputHeight = approximateLayout.layoutSize().height();
if (shouldOnlyDisplayApproximateOutput(context)) {
Layout exactLayout = createExactOutputLayout();
if (shouldOnlyDisplayExactOutput()) {
KDCoordinate exactOutputHeight = exactLayout.layoutSize().height();
m_height = inputHeight+exactOutputHeight;
} else if (shouldOnlyDisplayApproximateOutput(context)) {
KDCoordinate approximateOutputHeight = approximateLayout.layoutSize().height();
m_height = inputHeight+approximateOutputHeight;
} else {
Layout exactLayout = createExactOutputLayout(context);
KDCoordinate approximateOutputHeight = approximateLayout.layoutSize().height();
KDCoordinate exactOutputHeight = exactLayout.layoutSize().height();
KDCoordinate outputHeight = max(exactLayout.baseline(), approximateLayout.baseline()) + max(exactOutputHeight-exactLayout.baseline(), approximateOutputHeight-approximateLayout.baseline());
m_height = inputHeight + outputHeight;
@@ -75,7 +82,7 @@ const char * Calculation::approximateOutputText() {
}
Expression Calculation::input() {
return Expression::parse(m_inputText);
return Expression::Parse(m_inputText);
}
Layout Calculation::createInputLayout() {
@@ -102,26 +109,26 @@ void Calculation::tidy() {
m_equalSign = EqualSign::Unknown;
}
Expression Calculation::exactOutput(Context * context) {
Expression Calculation::exactOutput() {
/* Because the angle unit might have changed, we do not simplify again. We
* thereby avoid turning cos(Pi/4) into sqrt(2)/2 and displaying
* 'sqrt(2)/2 = 0.999906' (which is totally wrong) instead of
* 'cos(pi/4) = 0.999906' (which is true in degree). */
Expression exactOutput = Expression::parse(m_exactOutputText);
Expression exactOutput = Expression::Parse(m_exactOutputText);
if (exactOutput.isUninitialized()) {
return Undefined();
}
return exactOutput;
}
Layout Calculation::createExactOutputLayout(Context * context) {
return PoincareHelpers::CreateLayout(exactOutput(context));
Layout Calculation::createExactOutputLayout() {
return PoincareHelpers::CreateLayout(exactOutput());
}
Expression Calculation::approximateOutput(Context * context) {
/* To ensure that the expression 'm_output' is a matrix or a complex, we
* call 'evaluate'. */
Expression exp = Expression::parse(m_approximateOutputText);
Expression exp = Expression::Parse(m_approximateOutputText);
return PoincareHelpers::Approximate<double>(exp, *context);
}
@@ -130,22 +137,41 @@ Layout Calculation::createApproximateOutputLayout(Context * context) {
}
bool Calculation::shouldOnlyDisplayApproximateOutput(Context * context) {
if (shouldOnlyDisplayExactOutput()) {
return false;
}
if (strcmp(m_exactOutputText, m_approximateOutputText) == 0) {
/* If the exact and approximate results' texts are equal and their layouts
* too, do not display the exact result. If, because of the number of
* significant digits, the two layouts are not equal, we display both. */
return exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal;
}
if (strcmp(m_exactOutputText, Undefined::Name()) == 0) {
return true;
}
if (strcmp(m_exactOutputText, "undef") == 0) {
return true;
}
return input().isApproximate(*context) || exactOutput(context).isApproximate(*context);
return input().isApproximate(*context) || exactOutput().isApproximate(*context);
}
bool Calculation::shouldOnlyDisplayExactOutput() {
/* If the approximateOutput is undef, we not not want to display it.
* This prevents:
* x->f(x) from displaying x = undef
* x+x form displaying 2x = undef */
return strcmp(m_approximateOutputText, Undefined::Name()) == 0;
}
Calculation::EqualSign Calculation::exactAndApproximateDisplayedOutputsAreEqual(Poincare::Context * context) {
if (m_equalSign != EqualSign::Unknown) {
return m_equalSign;
}
char buffer[k_printedExpressionSize];
constexpr int bufferSize = Constant::MaxSerializedExpressionSize;
char buffer[bufferSize];
Preferences * preferences = Preferences::sharedPreferences();
m_equalSign = exactOutput(context).isEqualToItsApproximationLayout(approximateOutput(context), buffer, k_printedExpressionSize, preferences->angleUnit(), preferences->displayMode(), preferences->numberOfSignificantDigits(), *context) ? EqualSign::Equal : EqualSign::Approximation;
Expression exactOutputExpression = Expression::ParseAndSimplify(m_exactOutputText, *context, preferences->angleUnit());
if (exactOutputExpression.isUninitialized()) {
exactOutputExpression = Undefined();
}
m_equalSign = exactOutputExpression.isEqualToItsApproximationLayout(approximateOutput(context), buffer, bufferSize, preferences->angleUnit(), preferences->displayMode(), preferences->numberOfSignificantDigits(), *context) ? EqualSign::Equal : EqualSign::Approximation;
return m_equalSign;
}

View File

@@ -1,6 +1,7 @@
#ifndef CALCULATION_CALCULATION_H
#define CALCULATION_CALCULATION_H
#include <apps/constant.h>
#include <escher.h>
#include <poincare/context.h>
#include <poincare/expression.h>
@@ -28,22 +29,22 @@ public:
Poincare::Expression input();
Poincare::Layout createInputLayout();
Poincare::Expression approximateOutput(Poincare::Context * context);
Poincare::Expression exactOutput(Poincare::Context * context);
Poincare::Layout createExactOutputLayout(Poincare::Context * context);
Poincare::Expression exactOutput();
Poincare::Layout createExactOutputLayout();
Poincare::Layout createApproximateOutputLayout(Poincare::Context * context);
bool isEmpty();
void tidy();
bool shouldOnlyDisplayApproximateOutput(Poincare::Context * context);
bool shouldOnlyDisplayExactOutput();
EqualSign exactAndApproximateDisplayedOutputsAreEqual(Poincare::Context * context);
constexpr static int k_printedExpressionSize = 2*::TextField::maxBufferSize();
private:
static constexpr KDCoordinate k_heightComputationFailureHeight = 50;
/* Buffers holding text expressions have to be longer than the text written
* by user (of maximum length TextField::maxBufferSize()) because when we
* print an expression we add omitted signs (multiplications, parenthesis...) */
char m_inputText[k_printedExpressionSize];
char m_exactOutputText[k_printedExpressionSize];
char m_approximateOutputText[k_printedExpressionSize];
char m_inputText[Constant::MaxSerializedExpressionSize];
char m_exactOutputText[Constant::MaxSerializedExpressionSize];
char m_approximateOutputText[Constant::MaxSerializedExpressionSize];
KDCoordinate m_height;
EqualSign m_equalSign;
};

View File

@@ -6,11 +6,6 @@ using namespace Poincare;
namespace Calculation {
CalculationStore::CalculationStore() :
m_startIndex(0)
{
}
Calculation * CalculationStore::push(const char * text, Context * context) {
Calculation * result = &m_calculations[m_startIndex];
result->setContent(text, context, ansExpression(context));
@@ -101,13 +96,13 @@ 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 in when any Store or Equal
* expression appears.*/
bool exactOuptutInvolvesStoreEqual = lastCalculation->exactOutput(context).recursivelyMatches([](const Expression e, Context & context) {
bool exactOuptutInvolvesStoreEqual = lastCalculation->exactOutput().recursivelyMatches([](const Expression e, Context & context, bool replaceSymbols) {
return e.type() == ExpressionNode::Type::Store || e.type() == ExpressionNode::Type::Equal;
}, *context);
}, *context, false);
if (lastCalculation->input().isApproximate(*context) || exactOuptutInvolvesStoreEqual) {
return lastCalculation->approximateOutput(context);
}
return lastCalculation->exactOutput(context);
return lastCalculation->exactOutput();
}
}

View File

@@ -7,7 +7,7 @@ namespace Calculation {
class CalculationStore {
public:
CalculationStore();
CalculationStore() : m_startIndex(0) {}
Calculation * calculationAtIndex(int i);
Calculation * push(const char * text, Poincare::Context * context);
void deleteCalculationAtIndex(int i);

View File

@@ -10,10 +10,10 @@ using namespace Poincare;
namespace Calculation {
EditExpressionController::ContentView::ContentView(Responder * parentResponder, TableView * subview, TextFieldDelegate * textFieldDelegate, LayoutFieldDelegate * layoutFieldDelegate) :
EditExpressionController::ContentView::ContentView(Responder * parentResponder, TableView * subview, InputEventHandlerDelegate * inputEventHandlerDelegate, TextFieldDelegate * textFieldDelegate, LayoutFieldDelegate * layoutFieldDelegate) :
View(),
m_mainView(subview),
m_expressionField(parentResponder, m_textBody, k_bufferLength, textFieldDelegate, layoutFieldDelegate)
m_expressionField(parentResponder, m_textBody, k_bufferLength, inputEventHandlerDelegate, textFieldDelegate, layoutFieldDelegate)
{
m_textBody[0] = 0;
}
@@ -40,11 +40,11 @@ void EditExpressionController::ContentView::reload() {
markRectAsDirty(bounds());
}
EditExpressionController::EditExpressionController(Responder * parentResponder, HistoryController * historyController, CalculationStore * calculationStore) :
EditExpressionController::EditExpressionController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, HistoryController * historyController, CalculationStore * calculationStore) :
ViewController(parentResponder),
m_historyController(historyController),
m_calculationStore(calculationStore),
m_contentView(this, (TableView *)m_historyController->view(), this, this),
m_contentView(this, (TableView *)m_historyController->view(), inputEventHandlerDelegate, this, this),
m_inputViewHeightIsMaximal(false)
{
m_cacheBuffer[0] = 0;
@@ -58,18 +58,6 @@ void EditExpressionController::insertTextBody(const char * text) {
((ContentView *)view())->expressionField()->handleEventWithText(text, false, true);
}
bool EditExpressionController::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::Up) {
if (m_calculationStore->numberOfCalculations() > 0) {
m_cacheBuffer[0] = 0;
((ContentView *)view())->expressionField()->setEditing(false, false);
app()->setFirstResponder(m_historyController);
}
return true;
}
return false;
}
void EditExpressionController::didBecomeFirstResponder() {
int lastRow = m_calculationStore->numberOfCalculations() > 0 ? m_calculationStore->numberOfCalculations()-1 : 0;
m_historyController->scrollToCell(0, lastRow);
@@ -78,8 +66,9 @@ void EditExpressionController::didBecomeFirstResponder() {
}
bool EditExpressionController::textFieldDidReceiveEvent(::TextField * textField, Ion::Events::Event event) {
if (textField->isEditing() && textField->textFieldShouldFinishEditing(event) && textField->draftTextLength() == 0 && m_cacheBuffer[0] != 0) {
return inputViewDidReceiveEvent(event);
bool shouldDuplicateLastCalculation = textField->isEditing() && textField->shouldFinishEditing(event) && textField->draftTextLength() == 0;
if (inputViewDidReceiveEvent(event, shouldDuplicateLastCalculation)) {
return true;
}
return textFieldDelegateApp()->textFieldDidReceiveEvent(textField, event);
}
@@ -93,8 +82,9 @@ bool EditExpressionController::textFieldDidAbortEditing(::TextField * textField)
}
bool EditExpressionController::layoutFieldDidReceiveEvent(::LayoutField * layoutField, Ion::Events::Event event) {
if (layoutField->isEditing() && layoutField->layoutFieldShouldFinishEditing(event) && !layoutField->hasText() && m_calculationStore->numberOfCalculations() > 0) {
return inputViewDidReceiveEvent(event);
bool shouldDuplicateLastCalculation = layoutField->isEditing() && layoutField->shouldFinishEditing(event) && !layoutField->hasText();
if (inputViewDidReceiveEvent(event, shouldDuplicateLastCalculation)) {
return true;
}
return expressionFieldDelegateApp()->layoutFieldDidReceiveEvent(layoutField, event);
}
@@ -135,17 +125,29 @@ void EditExpressionController::reloadView() {
}
}
bool EditExpressionController::inputViewDidReceiveEvent(Ion::Events::Event event) {
App * calculationApp = (App *)app();
/* The input text store in m_cacheBuffer might have beed correct the first
* time but then be too long when replacing ans in another context */
if (!calculationApp->textInputIsCorrect(m_cacheBuffer)) {
bool EditExpressionController::inputViewDidReceiveEvent(Ion::Events::Event event, bool shouldDuplicateLastCalculation) {
if (shouldDuplicateLastCalculation && m_cacheBuffer[0] != 0) {
App * calculationApp = (App *)app();
/* The input text store in m_cacheBuffer might have beed correct the first
* time but then be too long when replacing ans in another context */
if (!calculationApp->isAcceptableText(m_cacheBuffer)) {
calculationApp->displayWarning(I18n::Message::SyntaxError);
return true;
}
m_calculationStore->push(m_cacheBuffer, calculationApp->localContext());
m_historyController->reload();
((ContentView *)view())->mainView()->scrollToCell(0, m_historyController->numberOfRows()-1);
return true;
}
m_calculationStore->push(m_cacheBuffer, calculationApp->localContext());
m_historyController->reload();
((ContentView *)view())->mainView()->scrollToCell(0, m_historyController->numberOfRows()-1);
return true;
if (event == Ion::Events::Up) {
if (m_calculationStore->numberOfCalculations() > 0) {
m_cacheBuffer[0] = 0;
((ContentView *)view())->expressionField()->setEditing(false, false);
app()->setFirstResponder(m_historyController);
}
return true;
}
return false;
}
@@ -153,9 +155,9 @@ bool EditExpressionController::inputViewDidFinishEditing(const char * text, Layo
App * calculationApp = (App *)app();
if (layoutR.isUninitialized()) {
assert(text);
strlcpy(m_cacheBuffer, text, Calculation::k_printedExpressionSize);
strlcpy(m_cacheBuffer, text, k_cacheBufferSize);
} else {
layoutR.serialize(m_cacheBuffer, Calculation::k_printedExpressionSize);
layoutR.serializeParsedExpression(m_cacheBuffer, k_cacheBufferSize);
}
m_calculationStore->push(m_cacheBuffer, calculationApp->localContext());
m_historyController->reload();

View File

@@ -15,11 +15,10 @@ class HistoryController;
/* TODO: implement a split view */
class EditExpressionController : public ViewController, public Shared::TextFieldDelegate, public Shared::LayoutFieldDelegate {
public:
EditExpressionController(Responder * parentResponder, HistoryController * historyController, CalculationStore * calculationStore);
EditExpressionController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, HistoryController * historyController, CalculationStore * calculationStore);
View * view() override;
void didBecomeFirstResponder() override;
void viewDidDisappear() override;
bool handleEvent(Ion::Events::Event event) override;
void insertTextBody(const char * text);
/* TextFieldDelegate */
@@ -36,7 +35,7 @@ public:
private:
class ContentView : public View {
public:
ContentView(Responder * parentResponder, TableView * subview, TextFieldDelegate * textFieldDelegate, LayoutFieldDelegate * layoutFieldDelegate);
ContentView(Responder * parentResponder, TableView * subview, InputEventHandlerDelegate * inputEventHandlerDelegate, TextFieldDelegate * textFieldDelegate, LayoutFieldDelegate * layoutFieldDelegate);
void reload();
TableView * mainView() { return m_mainView; }
ExpressionField * expressionField() { return &m_expressionField; }
@@ -51,12 +50,13 @@ private:
ExpressionField m_expressionField;
};
void reloadView();
bool inputViewDidReceiveEvent(Ion::Events::Event event);
bool inputViewDidReceiveEvent(Ion::Events::Event event, bool shouldDuplicateLastCalculation);
bool inputViewDidFinishEditing(const char * text, Poincare::Layout layoutR);
bool inputViewDidAbortEditing(const char * text);
Shared::TextFieldDelegateApp * textFieldDelegateApp() override;
Shared::ExpressionFieldDelegateApp * expressionFieldDelegateApp() override;
char m_cacheBuffer[Calculation::k_printedExpressionSize];
static constexpr int k_cacheBufferSize = Constant::MaxSerializedExpressionSize;
char m_cacheBuffer[k_cacheBufferSize];
HistoryController * m_historyController;
CalculationStore * m_calculationStore;
ContentView m_contentView;

View File

@@ -1,4 +1,5 @@
#include "expression_field.h"
#include <poincare/symbol.h>
namespace Calculation {
@@ -7,7 +8,7 @@ bool ExpressionField::handleEvent(Ion::Events::Event event) {
return false;
}
if (event == Ion::Events::Ans) {
handleEventWithText("ans");
handleEventWithText(Poincare::Symbol::k_ans);
return true;
}
if (isEditing() && isEmpty() &&
@@ -17,7 +18,7 @@ bool ExpressionField::handleEvent(Ion::Events::Event event) {
event == Ion::Events::Square ||
event == Ion::Events::Division ||
event == Ion::Events::Sto)) {
handleEventWithText("ans");
handleEventWithText(Poincare::Symbol::k_ans);
}
return(::ExpressionField::handleEvent(event));
}

View File

@@ -15,6 +15,7 @@ HistoryController::HistoryController(Responder * parentResponder, CalculationSto
{
for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) {
m_calculationHistory[i].setParentResponder(&m_selectableTableView);
m_calculationHistory[i].setDataSource(this);
}
}
@@ -45,27 +46,28 @@ bool HistoryController::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::OK || event == Ion::Events::EXE) {
int focusRow = selectedRow();
HistoryViewCell * selectedCell = (HistoryViewCell *)m_selectableTableView.selectedCell();
HistoryViewCell::SubviewType subviewType = selectedCell->selectedSubviewType();
SubviewType subviewType = selectedSubviewType();
EditExpressionController * editController = (EditExpressionController *)parentResponder();
m_selectableTableView.deselectTable();
app()->setFirstResponder(editController);
Calculation * calculation = m_calculationStore->calculationAtIndex(focusRow);
if (subviewType == HistoryViewCell::SubviewType::Input) {
if (subviewType == SubviewType::Input) {
editController->insertTextBody(calculation->inputText());
} else {
ScrollableExactApproximateExpressionsView::SubviewType outputSubviewType = selectedCell->outputView()->selectedSubviewType();
if (outputSubviewType == ScrollableExactApproximateExpressionsView::SubviewType::ExactOutput) {
editController->insertTextBody(calculation->exactOutputText());
} else {
ScrollableExactApproximateExpressionsView::SubviewPosition outputSubviewPosition = selectedCell->outputView()->selectedSubviewPosition();
if (outputSubviewPosition == ScrollableExactApproximateExpressionsView::SubviewPosition::Right
&& !calculation->shouldOnlyDisplayExactOutput())
{
editController->insertTextBody(calculation->approximateOutputText());
} else {
editController->insertTextBody(calculation->exactOutputText());
}
}
return true;
}
if (event == Ion::Events::Backspace) {
int focusRow = selectedRow();
HistoryViewCell * selectedCell = (HistoryViewCell *)m_selectableTableView.selectedCell();
HistoryViewCell::SubviewType subviewType = selectedCell->selectedSubviewType();
SubviewType subviewType = selectedSubviewType();
m_selectableTableView.deselectTable();
EditExpressionController * editController = (EditExpressionController *)parentResponder();
m_calculationStore->deleteCalculationAtIndex(focusRow);
@@ -79,7 +81,7 @@ bool HistoryController::handleEvent(Ion::Events::Event event) {
} else {
m_selectableTableView.selectCellAtLocation(0, 0);
}
if (subviewType == HistoryViewCell::SubviewType::Input) {
if (subviewType == SubviewType::Input) {
tableViewDidChangeSelection(&m_selectableTableView, 0, selectedRow());
} else {
tableViewDidChangeSelection(&m_selectableTableView, 0, -1);
@@ -104,17 +106,17 @@ bool HistoryController::handleEvent(Ion::Events::Event event) {
}
void HistoryController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) {
if (previousSelectedCellY == -1) {
setSelectedSubviewType(SubviewType::Output);
} else if (selectedRow() < previousSelectedCellY) {
setSelectedSubviewType(SubviewType::Output);
} else if (selectedRow() > previousSelectedCellY) {
setSelectedSubviewType(SubviewType::Input);
}
HistoryViewCell * selectedCell = (HistoryViewCell *)(t->selectedCell());
if (selectedCell == nullptr) {
return;
}
if (previousSelectedCellY == -1) {
selectedCell->setSelectedSubviewType(HistoryViewCell::SubviewType::Output);
} else if (selectedRow() < previousSelectedCellY) {
selectedCell->setSelectedSubviewType(HistoryViewCell::SubviewType::Output);
} else if (selectedRow() > previousSelectedCellY) {
selectedCell->setSelectedSubviewType(HistoryViewCell::SubviewType::Input);
}
app()->setFirstResponder(selectedCell);
selectedCell->reloadCell();
}

View File

@@ -10,7 +10,7 @@ namespace Calculation {
class App;
class HistoryController : public ViewController, public ListViewDataSource, public SelectableTableViewDataSource, public SelectableTableViewDelegate {
class HistoryController : public ViewController, public ListViewDataSource, public SelectableTableViewDataSource, public SelectableTableViewDelegate, public HistoryViewCellDataSource {
public:
HistoryController(Responder * parentResponder, CalculationStore * calculationStore);
View * view() override { return &m_selectableTableView; }

View File

@@ -7,15 +7,28 @@
namespace Calculation {
/* HistoryViewCellDataSource */
HistoryViewCellDataSource::HistoryViewCellDataSource() :
m_selectedSubviewType(HistoryViewCellDataSource::SubviewType::Output) {}
void HistoryViewCellDataSource::setSelectedSubviewType(HistoryViewCellDataSource::SubviewType subviewType, HistoryViewCell * cell) {
m_selectedSubviewType = subviewType;
if (cell) {
cell->setHighlighted(cell->isHighlighted());
}
}
/* HistoryViewCell */
HistoryViewCell::HistoryViewCell(Responder * parentResponder) :
Responder(parentResponder),
m_calculation(),
m_inputLayout(),
m_exactOutputLayout(),
m_approximateOutputLayout(),
m_leftOutputLayout(),
m_rightOutputLayout(),
m_inputView(this),
m_scrollableOutputView(this),
m_selectedSubviewType(HistoryViewCell::SubviewType::Output)
m_scrollableOutputView(this)
{
}
@@ -30,11 +43,12 @@ void HistoryViewCell::setEven(bool even) {
}
void HistoryViewCell::setHighlighted(bool highlight) {
assert(m_dataSource);
m_highlighted = highlight;
m_inputView.setBackgroundColor(backgroundColor());
m_scrollableOutputView.evenOddCell()->setHighlighted(false);
if (isHighlighted()) {
if (m_selectedSubviewType == SubviewType::Input) {
if (m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Input) {
m_inputView.setBackgroundColor(Palette::Select);
} else {
m_scrollableOutputView.evenOddCell()->setHighlighted(true);
@@ -44,7 +58,8 @@ void HistoryViewCell::setHighlighted(bool highlight) {
}
Poincare::Layout HistoryViewCell::layout() const {
if (m_selectedSubviewType == SubviewType::Input) {
assert(m_dataSource);
if (m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Input) {
return m_inputLayout;
} else {
return m_scrollableOutputView.layout();
@@ -105,43 +120,43 @@ void HistoryViewCell::setCalculation(Calculation * calculation) {
/* Both output expressions have to be updated at the same time. Otherwise,
* when updating one layout, if the second one still points to a deleted
* layout, calling to layoutSubviews() would fail. */
if (!m_exactOutputLayout.isUninitialized()) {
m_exactOutputLayout = Poincare::Layout();
if (!m_leftOutputLayout.isUninitialized()) {
m_leftOutputLayout = Poincare::Layout();
}
if (!calculation->shouldOnlyDisplayApproximateOutput(calculationApp->localContext())) {
m_exactOutputLayout = calculation->createExactOutputLayout(calculationApp->localContext());
if (!m_rightOutputLayout.isUninitialized()) {
m_rightOutputLayout = Poincare::Layout();
}
m_approximateOutputLayout = calculation->createApproximateOutputLayout(calculationApp->localContext());
m_scrollableOutputView.setLayouts(m_approximateOutputLayout, m_exactOutputLayout);
if (calculation->shouldOnlyDisplayExactOutput()) {
m_rightOutputLayout = calculation->createExactOutputLayout();
} else {
m_rightOutputLayout = calculation->createApproximateOutputLayout(calculationApp->localContext());
if (!calculation->shouldOnlyDisplayApproximateOutput(calculationApp->localContext())) {
m_leftOutputLayout = calculation->createExactOutputLayout();
}
}
m_scrollableOutputView.setLayouts(m_rightOutputLayout, m_leftOutputLayout);
I18n::Message equalMessage = calculation->exactAndApproximateDisplayedOutputsAreEqual(calculationApp->localContext()) == Calculation::EqualSign::Equal ? I18n::Message::Equal : I18n::Message::AlmostEqual;
m_scrollableOutputView.setEqualMessage(equalMessage);
}
void HistoryViewCell::didBecomeFirstResponder() {
if (m_selectedSubviewType == SubviewType::Input) {
assert(m_dataSource);
if (m_dataSource->selectedSubviewType() == HistoryViewCellDataSource::SubviewType::Input) {
app()->setFirstResponder(&m_inputView);
} else {
app()->setFirstResponder(&m_scrollableOutputView);
}
}
HistoryViewCell::SubviewType HistoryViewCell::selectedSubviewType() {
return m_selectedSubviewType;
}
void HistoryViewCell::setSelectedSubviewType(HistoryViewCell::SubviewType subviewType) {
m_selectedSubviewType = subviewType;
setHighlighted(isHighlighted());
}
bool HistoryViewCell::handleEvent(Ion::Events::Event event) {
if ((event == Ion::Events::Down && m_selectedSubviewType == SubviewType::Input) ||
(event == Ion::Events::Up && m_selectedSubviewType == SubviewType::Output)) {
SubviewType otherSubviewType = m_selectedSubviewType == SubviewType::Input ? SubviewType::Output : SubviewType::Input;
assert(m_dataSource);
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;
CalculationSelectableTableView * tableView = (CalculationSelectableTableView *)parentResponder();
tableView->scrollToSubviewOfTypeOfCellAtLocation(otherSubviewType, tableView->selectedColumn(), tableView->selectedRow());
HistoryViewCell * selectedCell = (HistoryViewCell *)(tableView->selectedCell());
selectedCell->setSelectedSubviewType(otherSubviewType);
m_dataSource->setSelectedSubviewType(otherSubviewType, selectedCell);
app()->setFirstResponder(selectedCell);
return true;
}

View File

@@ -8,17 +8,29 @@
namespace Calculation {
class HistoryViewCell : public ::EvenOddCell, public Responder {
class HistoryViewCell;
class HistoryViewCellDataSource {
public:
enum class SubviewType {
Input,
Output
};
HistoryViewCellDataSource();
void setSelectedSubviewType(HistoryViewCellDataSource::SubviewType subviewType, HistoryViewCell * cell = nullptr);
SubviewType selectedSubviewType() { return m_selectedSubviewType; }
private:
SubviewType m_selectedSubviewType;
};
class HistoryViewCell : public ::EvenOddCell, public Responder {
public:
HistoryViewCell(Responder * parentResponder = nullptr);
void reloadCell() override;
void reloadScroll();
void setEven(bool even) override;
void setHighlighted(bool highlight) override;
void setDataSource(HistoryViewCellDataSource * dataSource) { m_dataSource = dataSource; }
Responder * responder() override {
return this;
}
@@ -31,18 +43,16 @@ public:
void didBecomeFirstResponder() override;
bool handleEvent(Ion::Events::Event event) override;
constexpr static KDCoordinate k_digitVerticalMargin = 5;
SubviewType selectedSubviewType();
void setSelectedSubviewType(HistoryViewCell::SubviewType subviewType);
Shared::ScrollableExactApproximateExpressionsView * outputView();
private:
constexpr static KDCoordinate k_resultWidth = 80;
Calculation m_calculation;
Poincare::Layout m_inputLayout;
Poincare::Layout m_exactOutputLayout;
Poincare::Layout m_approximateOutputLayout;
Poincare::Layout m_leftOutputLayout;
Poincare::Layout m_rightOutputLayout;
ScrollableExpressionView m_inputView;
Shared::ScrollableExactApproximateExpressionsView m_scrollableOutputView;
SubviewType m_selectedSubviewType;
HistoryViewCellDataSource * m_dataSource;
};
}

View File

@@ -38,7 +38,7 @@ void CalculationSelectableTableView::scrollToCell(int i, int j) {
}
}
void CalculationSelectableTableView::scrollToSubviewOfTypeOfCellAtLocation(HistoryViewCell::SubviewType subviewType, int i, int j) {
void CalculationSelectableTableView::scrollToSubviewOfTypeOfCellAtLocation(HistoryViewCellDataSource::SubviewType subviewType, int i, int j) {
if (dataSource()->rowHeight(j) <= bounds().height()) {
return;
}
@@ -51,7 +51,7 @@ void CalculationSelectableTableView::scrollToSubviewOfTypeOfCellAtLocation(Histo
/* Main part of the scroll */
KDCoordinate contentOffsetX = contentOffset().x();
KDCoordinate contentOffsetY = dataSource()->cumulatedHeightFromIndex(j+1) - maxContentHeightDisplayableWithoutScrolling();
if (subviewType == HistoryViewCell::SubviewType::Input) {
if (subviewType == HistoryViewCellDataSource::SubviewType::Input) {
if (j == 0) {
contentOffsetY = 0;
} else {
@@ -63,6 +63,7 @@ void CalculationSelectableTableView::scrollToSubviewOfTypeOfCellAtLocation(Histo
* selected calculation has not changed. */
setContentOffset(KDPoint(contentOffsetX, contentOffsetY));
HighlightCell * cell = cellAtLocation(i, j);
assert(cell);
cell->setHighlighted(true);
if (m_delegate) {
m_delegate->tableViewDidChangeSelection(this, selectedColumn(), selectedRow());

View File

@@ -10,7 +10,7 @@ public:
CalculationSelectableTableView(Responder * parentResponder, TableViewDataSource * dataSource,
SelectableTableViewDataSource * selectionDataSource, SelectableTableViewDelegate * delegate = nullptr);
void scrollToCell(int i, int j) override;
void scrollToSubviewOfTypeOfCellAtLocation(HistoryViewCell::SubviewType subviewType, int i, int j);
void scrollToSubviewOfTypeOfCellAtLocation(HistoryViewCellDataSource::SubviewType subviewType, int i, int j);
};
}

View File

@@ -1,5 +1,5 @@
#include <quiz.h>
#include <poincare/global_context.h>
#include <apps/shared/global_context.h>
#include <string.h>
#include <assert.h>
#include "../calculation_store.h"
@@ -14,7 +14,7 @@ void assert_store_is(CalculationStore * store, const char * result[10]) {
}
QUIZ_CASE(calculation_store) {
GlobalContext globalContext;
Shared::GlobalContext globalContext;
CalculationStore store;
quiz_assert(CalculationStore::k_maxNumberOfCalculations == 10);
for (int i = 0; i < CalculationStore::k_maxNumberOfCalculations; i++) {
@@ -48,6 +48,12 @@ QUIZ_CASE(calculation_store) {
assert_store_is(&store, result3);
store.deleteAll();
}
QUIZ_CASE(calculation_display_exact_approximate) {
Shared::GlobalContext globalContext;
CalculationStore store;
store.push("1+3/4", &globalContext);
store.push("ans+2/3", &globalContext);
::Calculation::Calculation * lastCalculation = store.calculationAtIndex(1);
@@ -58,4 +64,50 @@ QUIZ_CASE(calculation_store) {
lastCalculation = store.calculationAtIndex(2);
quiz_assert(lastCalculation->shouldOnlyDisplayApproximateOutput(&globalContext) == true);
quiz_assert(strcmp(lastCalculation->approximateOutputText(),"2.6366666666667") == 0);
store.deleteAll();
store.push("1/2", &globalContext);
lastCalculation = store.calculationAtIndex(1);
quiz_assert(lastCalculation->exactAndApproximateDisplayedOutputsAreEqual(&globalContext) == ::Calculation::Calculation::EqualSign::Equal);
quiz_assert(lastCalculation->shouldOnlyDisplayExactOutput() == false);
quiz_assert(lastCalculation->shouldOnlyDisplayApproximateOutput(&globalContext) == false);
store.deleteAll();
store.push("1/3", &globalContext);
lastCalculation = store.calculationAtIndex(1);
quiz_assert(lastCalculation->exactAndApproximateDisplayedOutputsAreEqual(&globalContext) == ::Calculation::Calculation::EqualSign::Approximation);
quiz_assert(lastCalculation->shouldOnlyDisplayExactOutput() == false);
quiz_assert(lastCalculation->shouldOnlyDisplayApproximateOutput(&globalContext) == false);
store.deleteAll();
store.push("1/0", &globalContext);
lastCalculation = store.calculationAtIndex(1);
quiz_assert(lastCalculation->shouldOnlyDisplayExactOutput() == true);
quiz_assert(strcmp(lastCalculation->approximateOutputText(),"undef") == 0);
store.deleteAll();
store.push("2x-x", &globalContext);
lastCalculation = store.calculationAtIndex(1);
quiz_assert(lastCalculation->shouldOnlyDisplayExactOutput() == true);
quiz_assert(strcmp(lastCalculation->exactOutputText(),"x") == 0);
store.deleteAll();
store.push("[[1,2,3]]", &globalContext);
lastCalculation = store.calculationAtIndex(1);
quiz_assert(lastCalculation->shouldOnlyDisplayExactOutput() == false);
quiz_assert(lastCalculation->shouldOnlyDisplayApproximateOutput(&globalContext) == true);
store.deleteAll();
store.push("[[1,x,3]]", &globalContext);
lastCalculation = store.calculationAtIndex(1);
quiz_assert(lastCalculation->shouldOnlyDisplayExactOutput() == false);
quiz_assert(lastCalculation->shouldOnlyDisplayApproximateOutput(&globalContext) == true);
store.deleteAll();
store.push("28^7", &globalContext);
lastCalculation = store.calculationAtIndex(1);
quiz_assert(lastCalculation->shouldOnlyDisplayExactOutput() == false);
quiz_assert(lastCalculation->shouldOnlyDisplayApproximateOutput(&globalContext) == false);
store.deleteAll();
}

View File

@@ -15,6 +15,7 @@ app_objs += $(addprefix apps/code/,\
python_text_area.o\
sandbox_controller.o\
script.o\
script_name_cell.o\
script_node_cell.o\
script_parameter_controller.o\
script_store.o\

View File

@@ -30,10 +30,6 @@ App * App::Snapshot::unpack(Container * container) {
return new (container->currentAppBuffer()) App(container, this);
}
void App::Snapshot::reset() {
m_scriptStore.deleteAllScripts();
}
App::Descriptor * App::Snapshot::descriptor() {
static Descriptor descriptor;
return &descriptor;
@@ -76,7 +72,7 @@ void App::Snapshot::setOpt(const char * name, char * value) {
#endif
App::App(Container * container, Snapshot * snapshot) :
::App(container, snapshot, &m_codeStackViewController, I18n::Message::Warning),
Shared::InputEventHandlerDelegateApp(container, snapshot, &m_codeStackViewController),
m_pythonHeap{},
m_pythonUser(nullptr),
m_consoleController(nullptr, this, snapshot->scriptStore()
@@ -109,17 +105,20 @@ bool App::handleEvent(Ion::Events::Event event) {
return false;
}
bool App::textInputDidReceiveEvent(TextInput * textInput, Ion::Events::Event event) {
Toolbox * App::toolboxForInputEventHandler(InputEventHandler * textInput) {
return &m_toolbox;
}
VariableBoxController * App::variableBoxForInputEventHandler(InputEventHandler * textInput) {
return &m_variableBoxController;
}
bool App::textInputDidReceiveEvent(InputEventHandler * textInput, Ion::Events::Event event) {
const char * pythonText = Helpers::PythonTextForEvent(event);
if (pythonText != nullptr) {
textInput->handleEventWithText(pythonText);
return true;
}
if (event == Ion::Events::Var) {
m_variableBoxController.setTextInputCaller(textInput);
displayModalViewController(&m_variableBoxController, 0.f, 0.f, Metric::PopUpTopMargin, Metric::PopUpLeftMargin, 0, Metric::PopUpRightMargin);
return true;
}
return false;
}

View File

@@ -3,6 +3,7 @@
#include <escher.h>
#include <ion/events.h>
#include "../shared/input_event_handler_delegate_app.h"
#include "console_controller.h"
#include "menu_controller.h"
#include "script_store.h"
@@ -11,9 +12,9 @@
namespace Code {
class App : public ::App {
class App : public Shared::InputEventHandlerDelegateApp {
public:
class Descriptor : public ::App::Descriptor {
class Descriptor : public Shared::InputEventHandlerDelegateApp::Descriptor {
public:
I18n::Message name() override;
I18n::Message upperName() override;
@@ -23,7 +24,6 @@ public:
public:
Snapshot();
App * unpack(Container * container) override;
void reset() override;
Descriptor * descriptor() override;
ScriptStore * scriptStore();
#if EPSILON_GETOPT
@@ -39,14 +39,25 @@ public:
~App();
StackViewController * stackViewController() { return &m_codeStackViewController; }
ConsoleController * consoleController() { return &m_consoleController; }
PythonToolbox * pythonToolbox() { return &m_toolbox; }
/* Responder */
bool handleEvent(Ion::Events::Event event) override;
bool textInputDidReceiveEvent(TextInput * textInput, Ion::Events::Event event);
/* InputEventHandlerDelegate */
Toolbox * toolboxForInputEventHandler(InputEventHandler * textInput) override;
VariableBoxController * variableBoxForInputEventHandler(InputEventHandler * textInput) override;
/* TextInputDelegate */
bool textInputDidReceiveEvent(InputEventHandler * textInput, Ion::Events::Event event);
/* Code::App */
// Python delegate
bool pythonIsInited() { return m_pythonUser != nullptr; }
bool isPythonUser(const void * pythonUser) { return m_pythonUser == pythonUser; }
void initPythonWithUser(const void * pythonUser);
void deinitPython();
VariableBoxController * variableBoxController() { return &m_variableBoxController; }
private:
/* Python delegate:
* MicroPython requires a heap. To avoid dynamic allocation, we keep a working

View File

@@ -2,10 +2,7 @@ Console = "Interaktive Konsole"
AddScript = "Skript hinzufuegen"
ScriptOptions = "Skriptoptionen"
ExecuteScript = "Skript ausfuehren"
RenameScript = "Skript umbenennen"
AutoImportScript = "Automatischer Import in Konsole"
DeleteScript = "Skript loeschen"
FunctionsAndVariables = "Funktionen und Variablen"
NameTaken = "Dieser Name ist bereits vergeben"
NameTooLong = "Der Name ist zu lang"
NonCompliantName = "Erlaubte Zeichen: a-z, 0-9, _"

View File

@@ -2,10 +2,7 @@ Console = "Python shell"
AddScript = "Add a script"
ScriptOptions = "Script options"
ExecuteScript = "Execute script"
RenameScript = "Rename script"
AutoImportScript = "Auto import in shell"
DeleteScript = "Delete script"
FunctionsAndVariables = "Functions and variables"
NameTaken = "This name has already been taken"
NameTooLong = "This name is too long"
NonCompliantName = "Allowed characters: a-z, 0-9, _"

View File

@@ -2,10 +2,7 @@ Console = "Interprete de comandos"
AddScript = "Agregar un archivo"
ScriptOptions = "Opciones del archivo"
ExecuteScript = "Ejecutar el archivo"
RenameScript = "Renombrar el archivo"
AutoImportScript = "Importacion auto en interprete"
DeleteScript = "Eliminar el archivo"
FunctionsAndVariables = "Funciones y variables"
NameTaken = "Este nombre ya está en uso"
NameTooLong = "Ese nombre es demasiado largo"
NonCompliantName = "Caracteres permitidos : a-z, 0-9, _"

View File

@@ -2,10 +2,7 @@ Console = "Console d'execution"
AddScript = "Ajouter un script"
ScriptOptions = "Options de script"
ExecuteScript = "Executer le script"
RenameScript = "Renommer le script"
AutoImportScript = "Importation auto dans la console"
DeleteScript = "Supprimer le script"
FunctionsAndVariables = "Fonctions et variables"
NameTaken = "Il existe déjà un script portant le même nom"
NameTooLong = "Le nom choisi est trop long"
NonCompliantName = "Caratères autorisés : a-z, 0-9, _"
NonCompliantName = "Caractères autorisés : a-z, 0-9, _"

View File

@@ -2,10 +2,7 @@ Console = "Interpretador interativo"
AddScript = "Adicionar um script"
ScriptOptions = "Opcoes de script"
ExecuteScript = "Executar o script"
RenameScript = "Renomear o script"
AutoImportScript = "Importacao auto no interpretador"
DeleteScript = "Eliminar o script"
FunctionsAndVariables = "Funções e variáveis"
NameTaken = "Já existe um script com o mesmo nome"
NameTooLong = "O nome é muito longo"
NonCompliantName = "Caracteres permitidos : a-z, 0-9, _"

View File

@@ -32,6 +32,7 @@ PythonDrawString = "Display a text from pixel (x,y)"
PythonConstantE = "2.718281828459046"
PythonErf = "Error function"
PythonErfc = "Complementary error function"
PythonEval = "Returns the evaluated expression"
PythonExp = "Exponential function"
PythonExpm1 = "Compute exp(x)-1"
PythonFabs = "Absolute value"

View File

@@ -32,6 +32,7 @@ PythonDrawString = "Display a text from pixel (x,y)"
PythonConstantE = "2.718281828459046"
PythonErf = "Error function"
PythonErfc = "Complementary error function"
PythonEval = "Returns the evaluated expression"
PythonExp = "Exponential function"
PythonExpm1 = "Compute exp(x)-1"
PythonFabs = "Absolute value"

View File

@@ -32,6 +32,7 @@ PythonDrawString = "Display a text from pixel (x,y)"
PythonConstantE = "2.718281828459046"
PythonErf = "Error function"
PythonErfc = "Complementary error function"
PythonEval = "Returns the evaluated expression"
PythonExp = "Exponential function"
PythonExpm1 = "Compute exp(x)-1"
PythonFabs = "Absolute value"

View File

@@ -32,6 +32,7 @@ PythonDrawString = "Affiche un texte au pixel (x,y)"
PythonConstantE = "2.718281828459045"
PythonErf = "Fonction d'erreur"
PythonErfc = "Fonction d'erreur complémentaire"
PythonEval = "Evalue l'expression en argument "
PythonExp = "Fonction exponentielle"
PythonExpm1 = "Calcul de exp(x)-1"
PythonFabs = "Valeur absolue"

View File

@@ -32,6 +32,7 @@ PythonDrawString = "Display a text from pixel (x,y)"
PythonConstantE = "2.718281828459046"
PythonErf = "Error function"
PythonErfc = "Complementary error function"
PythonEval = "Returns the evaluated expression"
PythonExp = "Exponential function"
PythonExpm1 = "Compute exp(x)-1"
PythonFabs = "Absolute value"

View File

@@ -33,6 +33,7 @@ PythonCommandDrawString = "draw_string(\"text\",x,y)"
PythonCommandConstantE = "e"
PythonCommandErf = "erf(x)"
PythonCommandErfc = "erfc(x)"
PythonCommandEval = "eval(\"expression\")"
PythonCommandExp = "exp(x)"
PythonCommandExpComplex = "exp(z)"
PythonCommandExpm1 = "expm1(x)"

View File

@@ -28,7 +28,7 @@ ConsoleController::ConsoleController(Responder * parentResponder, App * pythonDe
m_rowHeight(k_font->glyphSize().height()),
m_importScriptsWhenViewAppears(false),
m_selectableTableView(this, this, this, this),
m_editCell(this, this),
m_editCell(this, pythonDelegate, this),
m_scriptStore(scriptStore),
m_sandboxController(this),
m_inputRunLoopActive(false)
@@ -45,27 +45,27 @@ ConsoleController::ConsoleController(Responder * parentResponder, App * pythonDe
}
bool ConsoleController::loadPythonEnvironment() {
if(pythonEnvironmentIsLoaded()) {
if (m_pythonDelegate->isPythonUser(this)) {
return true;
}
emptyOutputAccumulationBuffer();
m_pythonDelegate->initPythonWithUser(this);
MicroPython::registerScriptProvider(m_scriptStore);
m_importScriptsWhenViewAppears = m_autoImportScripts;
/* We load functions and variables names in the variable box before running
* any other python code to avoid failling to load functions and variables
* due to memory exhaustion. */
static_cast<App *>(app())->variableBoxController()->loadFunctionsAndVariables();
return true;
}
void ConsoleController::unloadPythonEnvironment() {
if (pythonEnvironmentIsLoaded()) {
if (!m_pythonDelegate->isPythonUser(nullptr)) {
m_consoleStore.startNewSession();
m_pythonDelegate->deinitPython();
}
}
bool ConsoleController::pythonEnvironmentIsLoaded() {
return m_pythonDelegate->isPythonUser(this);
}
void ConsoleController::autoImport() {
for (int i = 0; i < m_scriptStore->numberOfScripts(); i++) {
autoImportScript(m_scriptStore->scriptAtIndex(i));
@@ -121,19 +121,7 @@ void ConsoleController::didBecomeFirstResponder() {
}
bool ConsoleController::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::Up && inputRunLoopActive()) {
askInputRunLoopTermination();
// We need to return true here because we want to actually exit from the
// input run loop, which requires ending a dispatchEvent cycle.
return true;
}
if (event == Ion::Events::Up) {
if (m_consoleStore.numberOfLines() > 0 && m_selectableTableView.selectedRow() == m_consoleStore.numberOfLines()) {
m_editCell.setEditing(false);
m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines()-1);
return true;
}
} else if (event == Ion::Events::OK || event == Ion::Events::EXE) {
if (event == Ion::Events::OK || event == Ion::Events::EXE) {
if (m_consoleStore.numberOfLines() > 0 && m_selectableTableView.selectedRow() < m_consoleStore.numberOfLines()) {
const char * text = m_consoleStore.lineAtIndex(m_selectableTableView.selectedRow()).text();
m_editCell.setEditing(true);
@@ -231,7 +219,9 @@ void ConsoleController::tableViewDidChangeSelection(SelectableTableView * t, int
if (previousSelectedCellY > -1 && previousSelectedCellY < m_consoleStore.numberOfLines()) {
// Reset the scroll of the previous cell
ConsoleLineCell * previousCell = (ConsoleLineCell *)(t->cellAtLocation(previousSelectedCellX, previousSelectedCellY));
previousCell->reloadCell();
if (previousCell) {
previousCell->reloadCell();
}
}
ConsoleLineCell * selectedCell = (ConsoleLineCell *)(t->selectedCell());
selectedCell->reloadCell();
@@ -245,9 +235,17 @@ bool ConsoleController::textFieldShouldFinishEditing(TextField * textField, Ion:
}
bool ConsoleController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) {
if (event == Ion::Events::Var) {
if (!textField->isEditing()) {
textField->setEditing(true);
if (event == Ion::Events::Up && inputRunLoopActive()) {
askInputRunLoopTermination();
// We need to return true here because we want to actually exit from the
// input run loop, which requires ending a dispatchEvent cycle.
return true;
}
if (event == Ion::Events::Up) {
if (m_consoleStore.numberOfLines() > 0 && m_selectableTableView.selectedRow() == m_consoleStore.numberOfLines()) {
m_editCell.setEditing(false);
m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines()-1);
return true;
}
}
return static_cast<App *>(textField->app())->textInputDidReceiveEvent(textField, event);
@@ -291,11 +289,6 @@ bool ConsoleController::textFieldDidAbortEditing(TextField * textField) {
return true;
}
Toolbox * ConsoleController::toolboxForTextInput(TextInput * textInput) {
Code::App * codeApp = static_cast<Code::App *>(app());
return codeApp->pythonToolbox();
}
void ConsoleController::displaySandbox() {
if (m_sandboxIsDisplayed) {
return;
@@ -338,15 +331,25 @@ void ConsoleController::printText(const char * text, size_t length) {
void ConsoleController::autoImportScript(Script script, bool force) {
if (script.importationStatus() || force) {
// Create the command "from scriptName import *".
assert(strlen(k_importCommand1) + strlen(script.name()) - strlen(ScriptStore::k_scriptExtension) + strlen(k_importCommand2) + 1 <= k_maxImportCommandSize);
// Step 1 - Create the command "from scriptName import *".
assert(strlen(k_importCommand1) + strlen(script.fullName()) - strlen(ScriptStore::k_scriptExtension) - 1 + strlen(k_importCommand2) + 1 <= k_maxImportCommandSize);
char command[k_maxImportCommandSize];
// Copy "from "
size_t currentChar = strlcpy(command, k_importCommand1, k_maxImportCommandSize);
const char * scriptName = script.name();
currentChar += strlcpy(command+currentChar, scriptName, k_maxImportCommandSize - currentChar);
// Remove the name extension ".py"
currentChar -= strlen(ScriptStore::k_scriptExtension);
currentChar += strlcpy(command+currentChar, k_importCommand2, k_maxImportCommandSize - currentChar);
const char * scriptName = script.fullName();
/* Copy the script name without the extension ".py". The '.' is overwritten
* by the null terminating char. */
int copySizeWithNullTerminatingZero = min(k_maxImportCommandSize - currentChar, strlen(scriptName) - strlen(ScriptStore::k_scriptExtension));
strlcpy(command+currentChar, scriptName, copySizeWithNullTerminatingZero);
currentChar += copySizeWithNullTerminatingZero-1;
// Copy " import *"
strlcpy(command+currentChar, k_importCommand2, k_maxImportCommandSize - currentChar);
// Step 2 - Run the command
runAndPrintForCommand(command);
}
if (force) {

View File

@@ -26,7 +26,6 @@ public:
bool loadPythonEnvironment();
void unloadPythonEnvironment();
bool pythonEnvironmentIsLoaded();
void setAutoImport(bool autoImport) { m_autoImportScripts = autoImport; }
void autoImport();
@@ -60,7 +59,6 @@ public:
bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override;
bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override;
bool textFieldDidAbortEditing(TextField * textField) override;
Toolbox * toolboxForTextInput(TextInput * textInput) override;
// MicroPython::ExecutionEnvironment
void displaySandbox() override;

View File

@@ -6,12 +6,12 @@
namespace Code {
ConsoleEditCell::ConsoleEditCell(Responder * parentResponder, TextFieldDelegate * delegate) :
ConsoleEditCell::ConsoleEditCell(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, TextFieldDelegate * delegate) :
HighlightCell(),
Responder(parentResponder),
m_textBuffer{0},
m_promptView(ConsoleController::k_font, nullptr, 0, 0.5),
m_textField(this, m_textBuffer, m_textBuffer, TextField::maxBufferSize(), delegate, false, ConsoleController::k_font)
m_textField(this, m_textBuffer, m_textBuffer, TextField::maxBufferSize(), inputEventHandlerDelegate, delegate, false, ConsoleController::k_font)
{
}

View File

@@ -11,7 +11,7 @@ namespace Code {
class ConsoleEditCell : public HighlightCell, public Responder {
public:
ConsoleEditCell(Responder * parentResponder = nullptr, TextFieldDelegate * delegate = nullptr);
ConsoleEditCell(Responder * parentResponder = nullptr, InputEventHandlerDelegate * inputEventHandlerDelegate = nullptr, TextFieldDelegate * delegate = nullptr);
// View
int numberOfSubviews() const override;

View File

@@ -1,11 +1,12 @@
#include "editor_controller.h"
#include "menu_controller.h"
#include "script_parameter_controller.h"
#include "variable_box_controller.h"
#include <apps/code/app.h>
#include "app.h"
#include <escher/metric.h>
#include <ion.h>
using namespace Shared;
namespace Code {
EditorController::EditorController(MenuController * menuController, App * pythonDelegate) :
@@ -14,7 +15,7 @@ EditorController::EditorController(MenuController * menuController, App * python
m_script(Ion::Storage::Record()),
m_menuController(menuController)
{
m_editorView.setTextAreaDelegate(this);
m_editorView.setTextAreaDelegates(this, this);
}
void EditorController::setScript(Script script) {
@@ -30,19 +31,13 @@ void EditorController::setScript(Script script) {
// TODO: this should be done in textAreaDidFinishEditing maybe??
bool EditorController::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::OK || event == Ion::Events::Back || event == Ion::Events::Home) {
size_t sizeOfValue = strlen(m_areaBuffer+1)+1+1; // size of scriptContent + size of importation status
Script::ErrorStatus err = m_script.setValue({.buffer=m_areaBuffer, .size=sizeOfValue});
if (err == Script::ErrorStatus::NotEnoughSpaceAvailable || err == Script::ErrorStatus::RecordDoesNotExist) {
assert(false); // This should not happen as we set the text area according to the available space in the Kallax
} else {
stackController()->pop();
}
saveScript();
stackController()->pop();
return event != Ion::Events::Home;
}
return false;
}
void EditorController::didBecomeFirstResponder() {
app()->setFirstResponder(&m_editorView);
}
@@ -57,6 +52,11 @@ void EditorController::viewDidDisappear() {
}
bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) {
if (event == Ion::Events::Var) {
// We save script before displaying the Variable box to add new functions or variables
saveScript();
return false;
}
if (static_cast<App *>(textArea->app())->textInputDidReceiveEvent(textArea, event)) {
return true;
}
@@ -127,13 +127,26 @@ bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events:
return false;
}
Toolbox * EditorController::toolboxForTextInput(TextInput * textInput) {
Code::App * codeApp = static_cast<Code::App *>(app());
return codeApp->pythonToolbox();
VariableBoxController * EditorController::variableBoxForInputEventHandler(InputEventHandler * textInput) {
VariableBoxController * varBox = static_cast<App *>(app())->variableBoxController();
varBox->loadFunctionsAndVariables();
return varBox;
}
InputEventHandlerDelegateApp * EditorController::inputEventHandlerDelegateApp() {
return static_cast<App *>(app());
}
StackViewController * EditorController::stackController() {
return static_cast<StackViewController *>(parentResponder());
}
void EditorController::saveScript() {
size_t sizeOfValue = strlen(m_areaBuffer+1)+1+1; // size of scriptContent + size of importation status
Script::ErrorStatus err = m_script.setValue({.buffer=m_areaBuffer, .size=sizeOfValue});
if (err == Script::ErrorStatus::NotEnoughSpaceAvailable || err == Script::ErrorStatus::RecordDoesNotExist) {
assert(false); // This should not happen as we set the text area according to the available space in the Kallax
}
}
}

View File

@@ -4,13 +4,16 @@
#include <escher.h>
#include "script.h"
#include "editor_view.h"
#include "variable_box_controller.h"
#include "../shared/input_event_handler_delegate.h"
namespace Code {
class MenuController;
class ScriptParameterController;
class App;
class EditorController : public ViewController, public TextAreaDelegate {
class EditorController : public ViewController, public TextAreaDelegate, public Shared::InputEventHandlerDelegate {
public:
EditorController(MenuController * menuController, App * pythonDelegate);
void setScript(Script script);
@@ -25,11 +28,15 @@ public:
/* TextAreaDelegate */
bool textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) override;
Toolbox * toolboxForTextInput(TextInput * textInput) override;
/* InputEventHandlerDelegate */
VariableBoxController * variableBoxForInputEventHandler(InputEventHandler * textInput) override;
private:
Shared::InputEventHandlerDelegateApp * inputEventHandlerDelegateApp() override;
static constexpr int k_indentationSpacesNumber = 2;
StackViewController * stackController();
void saveScript();
EditorView m_editorView;
/* m_areaBuffer first character is dedicated to the importation status.
* Thereby, we avoid wasteful copy while adding the Script to the storage

View File

@@ -1,7 +1,7 @@
#ifndef CODE_EDITOR_VIEW_H
#define CODE_EDITOR_VIEW_H
#include <escher/view.h>
#include <escher.h>
#include "python_text_area.h"
namespace Code {
@@ -9,8 +9,8 @@ namespace Code {
class EditorView : public Responder, public View, public ScrollViewDelegate {
public:
EditorView(Responder * parentResponder, App * pythonDelegate);
void setTextAreaDelegate(TextAreaDelegate * delegate) {
m_textArea.setDelegate(delegate);
void setTextAreaDelegates(InputEventHandlerDelegate * inputEventHandlerDelegate, TextAreaDelegate * delegate) {
m_textArea.setDelegates(inputEventHandlerDelegate, delegate);
}
const char * text() const { return m_textArea.text(); }
void setText(char * textBuffer, size_t textBufferSize) {

View File

@@ -17,7 +17,7 @@ MenuController::MenuController(Responder * parentResponder, App * pythonDelegate
MenuController * menu = (MenuController *)context;
menu->consoleController()->setAutoImport(true);
menu->stackViewController()->push(menu->consoleController());
return;
return true;
}, this), KDFont::LargeFont),
m_selectableTableView(this, this, this, this),
m_scriptParameterController(nullptr, I18n::Message::ScriptOptions, this),
@@ -30,10 +30,7 @@ MenuController::MenuController(Responder * parentResponder, App * pythonDelegate
m_addNewScriptCell.setMessage(I18n::Message::AddScript);
for (int i = 0; i < k_maxNumberOfDisplayableScriptCells; i++) {
m_scriptCells[i].setParentResponder(&m_selectableTableView);
m_scriptCells[i].editableTextCell()->textField()->setDelegate(this);
m_scriptCells[i].editableTextCell()->textField()->setDraftTextBuffer(m_draftTextBuffer);
m_scriptCells[i].editableTextCell()->textField()->setAlignment(0.0f, 0.5f);
m_scriptCells[i].editableTextCell()->setMargins(0, 0, 0, Metric::HistoryHorizontalMargin);
m_scriptCells[i].textField()->setDelegates(nullptr, this);
}
}
@@ -49,9 +46,9 @@ void MenuController::willExitResponderChain(Responder * nextFirstResponder) {
int selectedRow = m_selectableTableView.selectedRow();
int selectedColumn = m_selectableTableView.selectedColumn();
if (selectedRow >= 0 && selectedRow < m_scriptStore->numberOfScripts() && selectedColumn == 0) {
TextField * tf = static_cast<EvenOddEditableTextCell *>(m_selectableTableView.selectedCell())->editableTextCell()->textField();
TextField * tf = static_cast<ScriptNameCell *>(m_selectableTableView.selectedCell())->textField();
if (tf->isEditing()) {
tf->setEditing(false);
tf->setEditing(false, false);
textFieldDidAbortEditing(tf);
}
}
@@ -124,13 +121,11 @@ void MenuController::renameSelectedScript() {
assert(m_selectableTableView.selectedRow() < m_scriptStore->numberOfScripts());
static_cast<AppsContainer *>(const_cast<Container *>(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::AlphaLock);
m_selectableTableView.selectCellAtLocation(0, (m_selectableTableView.selectedRow()));
EvenOddEditableTextCell * myCell = static_cast<EvenOddEditableTextCell *>(m_selectableTableView.selectedCell());
ScriptNameCell * myCell = static_cast<ScriptNameCell *>(m_selectableTableView.selectedCell());
app()->setFirstResponder(myCell);
myCell->setHighlighted(false);
const char * previousText = myCell->editableTextCell()->textField()->text();
myCell->editableTextCell()->textField()->setEditing(true);
myCell->editableTextCell()->textField()->setText(previousText);
myCell->editableTextCell()->textField()->setCursorLocation(strlen(previousText) - strlen(ScriptStore::k_scriptExtension));
myCell->textField()->setEditing(true, false);
myCell->textField()->setCursorLocation(strlen(myCell->textField()->text()));
}
void MenuController::deleteScript(Script script) {
@@ -152,7 +147,7 @@ void MenuController::openConsoleWithScript(Script script) {
m_reloadConsoleWhenBecomingFirstResponder = true;
}
void MenuController::scriptContentEditionDidFinish(){
void MenuController::scriptContentEditionDidFinish() {
reloadConsole();
}
@@ -272,8 +267,7 @@ int MenuController::typeAtLocation(int i, int j) {
void MenuController::willDisplayScriptTitleCellForIndex(HighlightCell * cell, int index) {
assert(index >= 0 && index < m_scriptStore->numberOfScripts());
EditableTextCell * editableTextCell = static_cast<EvenOddEditableTextCell *>(cell)->editableTextCell();
editableTextCell->textField()->setText(m_scriptStore->scriptAtIndex(index).name());
(static_cast<ScriptNameCell *>(cell))->textField()->setText(m_scriptStore->scriptAtIndex(index).fullName());
}
void MenuController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) {
@@ -292,7 +286,11 @@ bool MenuController::textFieldDidReceiveEvent(TextField * textField, Ion::Events
return true;
}
if (event == Ion::Events::Clear && textField->isEditing()) {
textField->setText(ScriptStore::k_scriptExtension);
constexpr size_t k_bufferSize = 4;
char buffer[k_bufferSize] = {'.', 0, 0, 0};
assert(k_bufferSize >= 1 + strlen(ScriptStore::k_scriptExtension) + 1);
strlcpy(&buffer[1], ScriptStore::k_scriptExtension, strlen(ScriptStore::k_scriptExtension) + 1);
textField->setText(buffer);
textField->setCursorLocation(0);
return true;
}
@@ -301,13 +299,25 @@ bool MenuController::textFieldDidReceiveEvent(TextField * textField, Ion::Events
bool MenuController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) {
const char * newName;
char numberedDefaultName[k_defaultScriptNameMaxSize];
if (strlen(text) <= strlen(ScriptStore::k_scriptExtension)) {
// The user entered an empty name. Use a numbered default script name.
numberedDefaultScriptName(numberedDefaultName);
newName = const_cast<const char *>(numberedDefaultName);
} else {
static constexpr int bufferSize = Script::k_defaultScriptNameMaxSize + 1 + ScriptStore::k_scriptExtensionLength; //"script99" + "." + "py"
char numberedDefaultName[bufferSize];
if (strlen(text) > 1 + strlen(ScriptStore::k_scriptExtension)) {
newName = text;
} else {
// The user entered an empty name. Use a numbered default script name.
bool foundDefaultName = Script::DefaultName(numberedDefaultName, Script::k_defaultScriptNameMaxSize);
int defaultNameLength = strlen(numberedDefaultName);
numberedDefaultName[defaultNameLength++] = '.';
strlcpy(&numberedDefaultName[defaultNameLength], ScriptStore::k_scriptExtension, bufferSize - defaultNameLength);
/* If there are already scripts named script1.py, script2.py,... until
* Script::k_maxNumberOfDefaultScriptNames, we want to write the last tried
* default name and let the user modify it. */
if (!foundDefaultName) {
textField->setText(numberedDefaultName);
textField->setCursorLocation(defaultNameLength);
}
newName = const_cast<const char *>(numberedDefaultName);
}
Script::ErrorStatus error = Script::nameCompliant(newName) ? m_scriptStore->scriptAtIndex(m_selectableTableView.selectedRow()).setName(newName) : Script::ErrorStatus::NonCompliantName;
if (error == Script::ErrorStatus::None) {
@@ -336,28 +346,35 @@ bool MenuController::textFieldDidFinishEditing(TextField * textField, const char
}
bool MenuController::textFieldDidAbortEditing(TextField * textField) {
if (strlen(textField->text()) <= strlen(ScriptStore::k_scriptExtension)) {
Script script = m_scriptStore->scriptAtIndex(m_selectableTableView.selectedRow());
const char * scriptName = script.fullName();
if (strlen(scriptName) <= 1 + strlen(ScriptStore::k_scriptExtension)) {
// The previous text was an empty name. Use a numbered default script name.
char numberedDefaultName[k_defaultScriptNameMaxSize];
numberedDefaultScriptName(numberedDefaultName);
Script::ErrorStatus error = m_scriptStore->scriptAtIndex(m_selectableTableView.selectedRow()).setName(numberedDefaultName);
if (error != Script::ErrorStatus::None) {
assert(false);
/* Because we use the numbered default name, the name should not be
* already taken. Plus, the script could be added only if the storage has
* enough available space to add a script named 'script99.py' */
char numberedDefaultName[Script::k_defaultScriptNameMaxSize];
bool foundDefaultName = Script::DefaultName(numberedDefaultName, Script::k_defaultScriptNameMaxSize);
if (!foundDefaultName) {
// If we did not find a default name, delete the script
deleteScript(script);
return true;
}
Script::ErrorStatus error = script.setBaseNameWithExtension(numberedDefaultName, ScriptStore::k_scriptExtension);
scriptName = m_scriptStore->scriptAtIndex(m_selectableTableView.selectedRow()).fullName();
/* Because we use the numbered default name, the name should not be
* already taken. Plus, the script could be added only if the storage has
* enough available space to add a script named 'script99.py' */
(void) error; // Silence the "variable unused" warning if assertions are not enabled
assert(error == Script::ErrorStatus::None);
updateAddScriptRowDisplay();
}
textField->setText(scriptName);
m_selectableTableView.selectCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow());
app()->setFirstResponder(&m_selectableTableView);
static_cast<AppsContainer *>(const_cast<Container *>(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default);
return true;
}
bool MenuController::textFieldDidHandleEvent(TextField * textField, bool returnValue, bool textHasChanged) {
int scriptExtensionLength = strlen(ScriptStore::k_scriptExtension);
bool MenuController::textFieldDidHandleEvent(TextField * textField, bool returnValue, bool textSizeDidChange) {
int scriptExtensionLength = 1 + strlen(ScriptStore::k_scriptExtension);
if (textField->isEditing() && textField->cursorLocation() > textField->draftTextLength() - scriptExtensionLength) {
textField->setCursorLocation(textField->draftTextLength() - scriptExtensionLength);
}
@@ -388,43 +405,6 @@ void MenuController::editScriptAtIndex(int scriptIndex) {
stackViewController()->push(&m_editorController);
}
void MenuController::numberedDefaultScriptName(char * buffer) {
bool foundNewScriptNumber = false;
int currentScriptNumber = 1;
char newName[k_defaultScriptNameMaxSize];
memcpy(newName, ScriptStore::k_defaultScriptName, strlen(ScriptStore::k_defaultScriptName)+1);
// We will only name scripts from script1.py to script99.py.
while (!foundNewScriptNumber && currentScriptNumber < 100) {
// Change the number in the script name.
intToText(currentScriptNumber, &newName[strlen(ScriptStore::k_defaultScriptName)-strlen(ScriptStore::k_scriptExtension)]);
memcpy(&newName[strlen(newName)], ScriptStore::k_scriptExtension, strlen(ScriptStore::k_scriptExtension)+1);
if (m_scriptStore->scriptNamed(const_cast<const char *>(newName)).isNull()) {
foundNewScriptNumber = true;
}
currentScriptNumber++;
}
if (foundNewScriptNumber) {
memcpy(buffer, newName, strlen(newName)+1);
return;
}
memcpy(buffer, ScriptStore::k_defaultScriptName, strlen(ScriptStore::k_defaultScriptName)+1);
}
void MenuController::intToText(int i, char * buffer) {
// We only support integers from 0 to 99
// buffer should have the space for three chars.
assert(i>=0);
assert(i<100);
if (i/10 == 0) {
buffer[0] = i+'0';
buffer[1] = 0;
return;
}
buffer[0] = i/10+'0';
buffer[1] = i-10*(i/10)+'0';
buffer[2] = 0;
}
void MenuController::updateAddScriptRowDisplay() {
m_shouldDisplayAddScriptRow = !m_scriptStore->isFull();
m_selectableTableView.reloadData();

View File

@@ -4,6 +4,7 @@
#include <escher.h>
#include "console_controller.h"
#include "editor_controller.h"
#include "script_name_cell.h"
#include "script_parameter_controller.h"
#include "script_store.h"
@@ -52,8 +53,7 @@ public:
bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override;
bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override;
bool textFieldDidAbortEditing(TextField * textField) override;
bool textFieldDidHandleEvent(TextField * textField, bool returnValue, bool textHasChanged) override;
Toolbox * toolboxForTextInput(TextInput * textInput) override { return nullptr; }
bool textFieldDidHandleEvent(TextField * textField, bool returnValue, bool textSizeDidChange) override;
/* ButtonRowDelegate */
int numberOfButtons(ButtonRowController::Position position) const override { return 1; }
@@ -69,33 +69,13 @@ private:
static constexpr int ScriptCellType = 1;
static constexpr int ScriptParameterCellType = 2;
static constexpr int EmptyCellType = 3;
static constexpr int k_defaultScriptNameMaxSize = 9 + 2 + 1;
// k_defaultScriptNameMaxSize is the length of a name between script1.py and
// script99.py.
// 9 = strlen("script.py")
// 2 = maxLength of integers between 1 and 99.
// 1 = length of null terminating char.
void addScript();
void configureScript();
void editScriptAtIndex(int scriptIndex);
void numberedDefaultScriptName(char * buffer);
void intToText(int i, char * buffer);
void updateAddScriptRowDisplay();
ScriptStore * m_scriptStore;
class EvenOddEditableTextCell : public ::EvenOddEditableTextCell {
public:
Responder * responder() override {
if (editableTextCell()->textField()->isEditing()) {
return this;
}
return nullptr;
}
};
EvenOddEditableTextCell m_scriptCells[k_maxNumberOfDisplayableScriptCells];
/* In the initializer list of the MenuController constructor, we initialize
* m_scriptCells by copying k_maxNumberOfDisplayableScriptCells times the
* constructor of an EvenOddEditableTextCell. */
char m_draftTextBuffer[TextField::maxBufferSize()];
ScriptNameCell m_scriptCells[k_maxNumberOfDisplayableScriptCells];
EvenOddCellWithEllipsis m_scriptParameterCells[k_maxNumberOfDisplayableScriptCells];
EvenOddMessageTextCell m_addNewScriptCell;
EvenOddCell m_emptyCell;

View File

@@ -8,7 +8,7 @@ extern "C" {
namespace Code {
static constexpr int catalogChildrenCount = 124;
static constexpr int catalogChildrenCount = 125;
static constexpr int MathModuleChildrenCount = 43;
static constexpr int KandinskyModuleChildrenCount = 7;
static constexpr int CMathModuleChildrenCount = 13;
@@ -227,6 +227,7 @@ const ToolboxMessageTree catalogChildren[catalogChildrenCount] = {
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandConstantE, I18n::Message::PythonConstantE, false),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandErf, I18n::Message::PythonErf),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandErfc, I18n::Message::PythonErfc),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandEval, I18n::Message::PythonEval),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandExp, I18n::Message::PythonExp),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandExpm1, I18n::Message::PythonExpm1),
ToolboxMessageTree::Leaf(I18n::Message::PythonCommandFabs, I18n::Message::PythonFabs),
@@ -332,7 +333,7 @@ const ToolboxMessageTree toolboxModel = ToolboxMessageTree::Node(I18n::Message::
PythonToolbox::PythonToolbox() :
Toolbox(nullptr, I18n::translate(rootModel()->label()))
Toolbox(nullptr, rootModel()->label())
{
}
@@ -364,9 +365,9 @@ KDCoordinate PythonToolbox::rowHeight(int j) {
return Toolbox::rowHeight(j);
}
bool PythonToolbox::selectLeaf(ToolboxMessageTree * selectedMessageTree) {
bool PythonToolbox::selectLeaf(int selectedRow) {
m_selectableTableView.deselectTable();
ToolboxMessageTree * node = selectedMessageTree;
ToolboxMessageTree * node = (ToolboxMessageTree *)m_messageTreeModel->children(selectedRow);
const char * editedText = I18n::translate(node->insertedText());
if (node->stripInsertedText()) {
int strippedEditedTextMaxLength = strlen(editedText)+1;

View File

@@ -14,7 +14,7 @@ public:
bool handleEvent(Ion::Events::Event event) override;
protected:
KDCoordinate rowHeight(int j) override;
bool selectLeaf(ToolboxMessageTree * selectedMessageTree) override;
bool selectLeaf(int selectedRow) override;
const ToolboxMessageTree * rootModel() override;
MessageTableCellWithMessage * leafCellAtIndex(int index) override;
MessageTableCellWithChevron* nodeCellAtIndex(int index) override;

View File

@@ -23,6 +23,7 @@ void SandboxController::viewWillAppear() {
}
bool SandboxController::handleEvent(Ion::Events::Event event) {
// The sandbox handles or "absorbs" all keyboard events except Home and OnOff
if (event == Ion::Events::Home || event == Ion::Events::OnOff) {
stackViewController()->pop();
return false;
@@ -30,7 +31,7 @@ bool SandboxController::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::OK || event == Ion::Events::Back) {
stackViewController()->pop();
}
return true;
return event.isKeyboardEvent();
}
void SandboxController::redrawWindow() {

View File

@@ -1,10 +1,53 @@
#include "script.h"
#include "script_store.h"
namespace Code {
Script::Script(Record f) :
Record(f)
{
static inline void intToText(int i, char * buffer, int bufferSize) {
// We only support integers from 0 to 99.
assert(i>=0);
assert(i<100);
assert(bufferSize >= 3);
if (i/10 == 0) {
buffer[0] = i+'0';
buffer[1] = 0;
return;
}
buffer[0] = i/10+'0';
buffer[1] = i-10*(i/10)+'0';
buffer[2] = 0;
}
bool Script::DefaultName(char buffer[], size_t bufferSize) {
assert(bufferSize >= k_defaultScriptNameMaxSize);
static constexpr char defaultScriptName[] = "script";
static constexpr int defaultScriptNameLength = 6;
strlcpy(buffer, defaultScriptName, bufferSize);
int currentScriptNumber = 1;
while (currentScriptNumber <= k_maxNumberOfDefaultScriptNames) {
// Change the number in the script name.
intToText(currentScriptNumber, &buffer[defaultScriptNameLength], bufferSize - defaultScriptNameLength );
if (ScriptStore::ScriptNameIsFree(buffer)) {
return true;
}
currentScriptNumber++;
}
// We did not find a new script name
return false;
}
bool Script::nameCompliant(const char * name) {
/* The name format is [a-z0-9_\.]+ */
const char * currentChar = name;
while (*currentChar != 0) {
if ((*currentChar >= 'a' && *currentChar <= 'z') || *currentChar == '_' || (*currentChar >= '0' && *currentChar <= '9') || *currentChar == '.') {
currentChar++;
continue;
}
return false;
}
return name != currentChar;
}
bool Script::importationStatus() const {
@@ -25,17 +68,4 @@ const char * Script::readContent() const {
return (const char *)d.buffer+k_importationStatusSize;
}
bool Script::nameCompliant(const char * name) {
/* The name format is [a-z0-9_\.]+ */
const char * currentChar = name;
while (*currentChar != 0) {
if ((*currentChar >= 'a' && *currentChar <= 'z') || *currentChar == '_' || (*currentChar >= '0' && *currentChar <= '9') || *currentChar == '.') {
currentChar++;
continue;
}
return false;
}
return name != currentChar;
}
}

View File

@@ -9,17 +9,24 @@ namespace Code {
* Script: | AutoImportationStatus | Content |*/
class Script : public Ion::Storage::Record {
private:
// Default script names are chosen between script1 and script99
static constexpr int k_maxNumberOfDefaultScriptNames = 99;
static constexpr int k_defaultScriptNameNumberMaxSize = 2; // Numbers from 1 to 99 have 2 digits max
public:
Script(Ion::Storage::Record r);
bool importationStatus() const;
void toggleImportationStatus();
const char * readContent() const;
static constexpr size_t k_importationStatusSize = 1;
static constexpr int k_defaultScriptNameMaxSize = 6 + k_defaultScriptNameNumberMaxSize + 1;
/* 6 = strlen("script")
* k_defaultScriptNameNumberMaxSize = maxLength of integers between 1 and 99
* 1 = null-terminating char */
static bool DefaultName(char buffer[], size_t bufferSize);
static bool nameCompliant(const char * name);
constexpr static size_t k_importationStatusSize = 1;
Script(Ion::Storage::Record r) : Record(r) {}
bool importationStatus() const;
void toggleImportationStatus();
const char * readContent() const;
};
}

View File

@@ -0,0 +1,40 @@
#include "script_name_cell.h"
#include "app.h"
#include <assert.h>
namespace Code {
void ScriptNameCell::setEven(bool even) {
EvenOddCell::setEven(even);
m_textField.setBackgroundColor(backgroundColor());
}
void ScriptNameCell::setHighlighted(bool highlight) {
EvenOddCell::setHighlighted(highlight);
m_textField.setBackgroundColor(backgroundColor());
}
const char * ScriptNameCell::text() const {
if (!m_textField.isEditing()) {
return m_textField.text();
}
return nullptr;
}
KDSize ScriptNameCell::minimalSizeForOptimalDisplay() const {
return m_textField.minimalSizeForOptimalDisplay();
}
void ScriptNameCell::didBecomeFirstResponder() {
app()->setFirstResponder(&m_textField);
}
void ScriptNameCell::layoutSubviews() {
KDRect cellBounds = bounds();
m_textField.setFrame(KDRect(cellBounds.x() + k_leftMargin,
cellBounds.y(),
cellBounds.width() - k_leftMargin,
cellBounds.height()));
}
}

View File

@@ -0,0 +1,57 @@
#ifndef CODE_SCRIPT_NAME_CELL_H
#define CODE_SCRIPT_NAME_CELL_H
#include <escher/even_odd_cell.h>
#include <escher/metric.h>
#include <escher/responder.h>
#include <escher/text_field_delegate.h>
#include <apps/shared/text_field_with_extension.h>
#include "script_store.h"
namespace Code {
class ScriptNameCell : public EvenOddCell, public Responder {
public:
ScriptNameCell(Responder * parentResponder = nullptr, TextFieldDelegate * delegate = nullptr) :
EvenOddCell(),
Responder(parentResponder),
m_textField(k_extensionLength, this, m_textBody, m_textBody, TextField::maxBufferSize(), nullptr, delegate, false)
{}
Shared::TextFieldWithExtension * textField() { return &m_textField; }
// EvenOddCell
void setEven(bool even) override;
// HighlightCell
void setHighlighted(bool highlight) override;
Responder * responder() override {
if (m_textField.isEditing()) {
return this;
}
return nullptr;
}
const char * text() const override;
// View
KDSize minimalSizeForOptimalDisplay() const override;
// Responder
void didBecomeFirstResponder() override;
private:
constexpr static size_t k_extensionLength = 1+ScriptStore::k_scriptExtensionLength; // '.' + "py"
constexpr static KDCoordinate k_leftMargin = Metric::HistoryHorizontalMargin;
// View
int numberOfSubviews() const override { return 1; }
View * subviewAtIndex(int index) override {
assert(index == 0);
return &m_textField;
}
void layoutSubviews() override;
Shared::TextFieldWithExtension m_textField;
char m_textBody[TextField::maxBufferSize()];
};
}
#endif

View File

@@ -28,7 +28,7 @@ void ScriptNodeCell::ScriptNodeView::drawRect(KDContext * ctx, KDRect rect) cons
if (m_scriptNode->type() == ScriptNode::Type::Function) {
ctx->drawString(ScriptNodeCell::k_parentheses, KDPoint(nameSize.width(), Metric::TableCellLabelTopMargin), k_font, KDColorBlack, isHighlighted()? Palette::Select : KDColorWhite);
}
ctx->drawString(m_scriptStore->scriptAtIndex(m_scriptNode->scriptIndex()).name(), KDPoint(0, Metric::TableCellLabelTopMargin + nameSize.height() + k_verticalMargin), k_font, Palette::GreyDark, isHighlighted()? Palette::Select : KDColorWhite);
ctx->drawString(m_scriptStore->scriptAtIndex(m_scriptNode->scriptIndex()).fullName(), KDPoint(0, Metric::TableCellLabelTopMargin + nameSize.height() + k_verticalMargin), k_font, Palette::GreyDark, isHighlighted()? Palette::Select : KDColorWhite);
}
KDSize ScriptNodeCell::ScriptNodeView::minimalSizeForOptimalDisplay() const {
@@ -36,7 +36,7 @@ KDSize ScriptNodeCell::ScriptNodeView::minimalSizeForOptimalDisplay() const {
return KDSizeZero;
}
KDSize size1 = k_font->stringSize(m_scriptNode->name());
KDSize size2 = k_font->stringSize(m_scriptStore->scriptAtIndex(m_scriptNode->scriptIndex()).name());
KDSize size2 = k_font->stringSize(m_scriptStore->scriptAtIndex(m_scriptNode->scriptIndex()).fullName());
KDSize size3 = KDSizeZero;
if (m_scriptNode->type() == ScriptNode::Type::Function) {
size3 = k_font->stringSize(ScriptNodeCell::k_parentheses);

View File

@@ -34,7 +34,7 @@ protected:
void drawRect(KDContext * ctx, KDRect rect) const override;
virtual KDSize minimalSizeForOptimalDisplay() const override;
const char * text() const override {
return m_scriptStore->scriptAtIndex(m_scriptNode->scriptIndex()).name();
return m_scriptStore->scriptAtIndex(m_scriptNode->scriptIndex()).fullName();
}
private:
constexpr static const KDFont * k_font = KDFont::SmallFont;

View File

@@ -7,7 +7,7 @@ ScriptParameterController::ScriptParameterController(Responder * parentResponder
ViewController(parentResponder),
m_pageTitle(title),
m_executeScript(I18n::Message::ExecuteScript),
m_renameScript(I18n::Message::RenameScript),
m_renameScript(I18n::Message::Rename),
m_autoImportScript(I18n::Message::AutoImportScript),
m_deleteScript(I18n::Message::DeleteScript),
m_selectableTableView(this),

View File

@@ -10,7 +10,11 @@ extern "C" {
namespace Code {
constexpr char ScriptStore::k_scriptExtension[];
constexpr char ScriptStore::k_defaultScriptName[];
bool ScriptStore::ScriptNameIsFree(const char * baseName) {
return Ion::Storage::sharedStorage()->recordBaseNamedWithExtension(baseName, k_scriptExtension).isNull();
}
ScriptStore::ScriptStore()
{
@@ -28,7 +32,7 @@ void ScriptStore::deleteAllScripts() {
}
bool ScriptStore::isFull() {
return (numberOfScripts() >= k_maxNumberOfScripts || Ion::Storage::sharedStorage()->availableSize() < k_fullFreeSpaceSizeLimit);
return Ion::Storage::sharedStorage()->availableSize() < k_fullFreeSpaceSizeLimit;
}
void ScriptStore::scanScriptsForFunctionsAndVariables(void * context, ScanCallback storeFunction, ScanCallback storeVariable) {
@@ -124,7 +128,7 @@ const char * ScriptStore::contentOfScript(const char * name) {
Script::ErrorStatus ScriptStore::addScriptFromTemplate(const ScriptTemplate * scriptTemplate) {
size_t valueSize = strlen(scriptTemplate->content())+1+1;// scriptcontent size + 1 char for the importation status
assert(Script::nameCompliant(scriptTemplate->name()));
Script::ErrorStatus err = Ion::Storage::sharedStorage()->createRecord(scriptTemplate->name(), scriptTemplate->value(), valueSize);
Script::ErrorStatus err = Ion::Storage::sharedStorage()->createRecordWithFullName(scriptTemplate->name(), scriptTemplate->value(), valueSize);
assert(err != Script::ErrorStatus::NonCompliantName);
return err;
}

View File

@@ -13,9 +13,11 @@ namespace Code {
class ScriptStore : public MicroPython::ScriptProvider {
public:
static constexpr char k_scriptExtension[] = ".py";
static constexpr char k_defaultScriptName[] = "script.py";
static constexpr int k_maxNumberOfScripts = 8;
static constexpr char k_scriptExtension[] = "py";
static constexpr size_t k_scriptExtensionLength = 2;
// Storage information
static bool ScriptNameIsFree(const char * baseName);
ScriptStore();
Script scriptAtIndex(int index) {
@@ -45,10 +47,10 @@ private:
/* If the storage available space has a smaller size than
* k_fullFreeSpaceSizeLimit, we consider the script store as full.
* To be able to add a new empty record, the available space should at least
* stores a Script with default name "script99.py" (12 char), the importation
* status (1 char), the default content "from math import *\n" (20 char) and
* 10 char of free space. */
static constexpr int k_fullFreeSpaceSizeLimit = sizeof(Ion::Storage::record_size_t)+12+1+20+10;
* be able to store a Script with default name and its extension, the
* importation status (1 char), the default content "from math import *\n"
* (20 char) and 10 char of free space. */
static constexpr int k_fullFreeSpaceSizeLimit = sizeof(Ion::Storage::record_size_t)+Script::k_defaultScriptNameMaxSize+k_scriptExtensionLength+1+20+10;
static constexpr size_t k_fileInput2ParseNodeStructKind = 1;
static constexpr size_t k_functionDefinitionParseNodeStructKind = 3;
static constexpr size_t k_expressionStatementParseNodeStructKind = 5;

View File

@@ -10,45 +10,25 @@
namespace Code {
/* ContentViewController */
VariableBoxController::ContentViewController::ContentViewController(Responder * parentResponder, App * pythonDelegate, ScriptStore * scriptStore) :
ViewController(parentResponder),
m_scriptNodesCount(0),
VariableBoxController::VariableBoxController(App * pythonDelegate, ScriptStore * scriptStore) :
NestedMenuController(nullptr, I18n::Message::FunctionsAndVariables),
m_pythonDelegate(pythonDelegate),
m_scriptStore(scriptStore),
m_selectableTableView(this)
m_scriptNodesCount(0),
m_scriptStore(scriptStore)
{
m_selectableTableView.setMargins(0);
m_selectableTableView.setShowsIndicators(false);
for (int i = 0; i < k_maxNumberOfDisplayedRows; i++) {
m_leafCells[i].setScriptStore(scriptStore);
}
}
void VariableBoxController::ContentViewController::setTextInputCaller(TextInput * textInput) {
m_textInputCaller = textInput;
bool VariableBoxController::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::Left) {
return true;
}
return NestedMenuController::handleEvent(event);
}
void VariableBoxController::ContentViewController::reloadData() {
m_selectableTableView.reloadData();
}
void VariableBoxController::ContentViewController::addFunctionAtIndex(const char * functionName, int scriptIndex) {
m_scriptNodes[m_scriptNodesCount] = ScriptNode::FunctionNode(functionName, scriptIndex);
m_scriptNodesCount++;
}
void VariableBoxController::ContentViewController::addVariableAtIndex(const char * variableName, int scriptIndex) {
m_scriptNodes[m_scriptNodesCount] = ScriptNode::VariableNode(variableName, scriptIndex);
m_scriptNodesCount++;
}
const char * VariableBoxController::ContentViewController::title() {
return I18n::translate(I18n::Message::FunctionsAndVariables);
}
void VariableBoxController::ContentViewController::didEnterResponderChain(Responder * previousFirstResponder) {
void VariableBoxController::didEnterResponderChain(Responder * previousFirstResponder) {
/* This Code::VariableBoxController should always be called from an
* environment where Python has already been inited. This way, we do not
* deinit Python when leaving the VariableBoxController, so we do not lose the
@@ -57,7 +37,7 @@ void VariableBoxController::ContentViewController::didEnterResponderChain(Respon
}
static bool shouldAddObject(const char * name, int maxLength) {
if (strlen(name)+1 > maxLength) {
if (strlen(name)+1 > (size_t)maxLength) {
return false;
}
assert(name != nullptr);
@@ -67,7 +47,28 @@ static bool shouldAddObject(const char * name, int maxLength) {
return true;
}
void VariableBoxController::ContentViewController::viewWillAppear() {
int VariableBoxController::numberOfRows() {
assert(m_scriptNodesCount <= k_maxScriptNodesCount);
return m_scriptNodesCount;
}
int VariableBoxController::reusableCellCount(int type) {
assert(type == 0);
return k_maxNumberOfDisplayedRows;
}
void VariableBoxController::willDisplayCellForIndex(HighlightCell * cell, int index) {
assert(index < m_scriptNodesCount);
assert(m_scriptNodesCount <= k_maxScriptNodesCount);
ScriptNodeCell * myCell = static_cast<ScriptNodeCell *>(cell);
myCell->setScriptNode(&m_scriptNodes[index]);
}
int VariableBoxController::typeAtLocation(int i, int j) {
return 0;
}
void VariableBoxController::loadFunctionsAndVariables() {
m_scriptNodesCount = 0;
m_scriptStore->scanScriptsForFunctionsAndVariables(
this,
@@ -75,102 +76,54 @@ void VariableBoxController::ContentViewController::viewWillAppear() {
if (!shouldAddObject(functionName, k_maxScriptObjectNameSize)) {
return;
}
VariableBoxController::ContentViewController * cvc = static_cast<VariableBoxController::ContentViewController *>(context);
VariableBoxController * cvc = static_cast<VariableBoxController *>(context);
cvc->addFunctionAtIndex(functionName, scriptIndex);},
[](void * context, const char * variableName, int scriptIndex) {
if (!shouldAddObject(variableName, k_maxScriptObjectNameSize)) {
return;
}
VariableBoxController::ContentViewController * cvc = static_cast<VariableBoxController::ContentViewController *>(context);
VariableBoxController * cvc = static_cast<VariableBoxController *>(context);
cvc->addVariableAtIndex(variableName, scriptIndex);});
}
void VariableBoxController::ContentViewController::viewDidDisappear() {
m_selectableTableView.deselectTable();
for (int i = 0; i < k_maxScriptNodesCount; i++) {
m_scriptNodes[i] = ScriptNode();
}
ViewController::viewDidDisappear();
}
void VariableBoxController::ContentViewController::didBecomeFirstResponder() {
m_selectableTableView.reloadData();
m_selectableTableView.scrollToCell(0,0);
selectCellAtLocation(0, 0);
app()->setFirstResponder(&m_selectableTableView);
}
bool VariableBoxController::ContentViewController::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::Back) {
app()->dismissModalViewController();
return true;
}
if (event == Ion::Events::Left) {
return true;
}
if (event == Ion::Events::OK || event == Ion::Events::EXE) {
if (m_selectableTableView.selectedRow() < 0 || m_selectableTableView.selectedRow() >= m_scriptNodesCount) {
return false;
}
ScriptNode selectedScriptNode = m_scriptNodes[m_selectableTableView.selectedRow()];
insertTextInCaller(selectedScriptNode.name());
if (selectedScriptNode.type() == ScriptNode::Type::Function) {
insertTextInCaller(ScriptNodeCell::k_parenthesesWithEmpty);
}
m_selectableTableView.deselectTable();
app()->dismissModalViewController();
return true;
}
return false;
}
int VariableBoxController::ContentViewController::numberOfRows() {
return m_scriptNodesCount < k_maxScriptNodesCount ? m_scriptNodesCount : k_maxScriptNodesCount;
}
HighlightCell * VariableBoxController::ContentViewController::reusableCell(int index) {
HighlightCell * VariableBoxController::leafCellAtIndex(int index) {
assert(index >= 0 && index < k_maxNumberOfDisplayedRows);
return &m_leafCells[index];
}
int VariableBoxController::ContentViewController::reusableCellCount() {
return k_maxNumberOfDisplayedRows;
bool VariableBoxController::selectLeaf(int rowIndex) {
assert(rowIndex >= 0 && rowIndex < m_scriptNodesCount);
assert(m_scriptNodesCount <= k_maxScriptNodesCount);
m_selectableTableView.deselectTable();
ScriptNode selectedScriptNode = m_scriptNodes[rowIndex];
insertTextInCaller(selectedScriptNode.name());
if (selectedScriptNode.type() == ScriptNode::Type::Function) {
insertTextInCaller(ScriptNodeCell::k_parenthesesWithEmpty);
}
app()->dismissModalViewController();
return true;
}
void VariableBoxController::ContentViewController::willDisplayCellForIndex(HighlightCell * cell, int index) {
ScriptNodeCell * myCell = static_cast<ScriptNodeCell *>(cell);
myCell->setScriptNode(&m_scriptNodes[index]);
}
void VariableBoxController::ContentViewController::insertTextInCaller(const char * text) {
void VariableBoxController::insertTextInCaller(const char * text) {
int commandBufferMaxSize = strlen(text)+1;
char commandBuffer[k_maxScriptObjectNameSize];
assert(commandBufferMaxSize <= k_maxScriptObjectNameSize);
Shared::ToolboxHelpers::TextToInsertForCommandText(text, commandBuffer, commandBufferMaxSize, true);
m_textInputCaller->handleEventWithText(commandBuffer);
sender()->handleEventWithText(commandBuffer);
}
VariableBoxController::VariableBoxController(App * pythonDelegate, ScriptStore * scriptStore) :
StackViewController(nullptr, &m_contentViewController, KDColorWhite, Palette::PurpleBright, Palette::PurpleDark),
m_contentViewController(this, pythonDelegate, scriptStore)
{
void VariableBoxController::addFunctionAtIndex(const char * functionName, int scriptIndex) {
if (m_scriptNodesCount < k_maxScriptNodesCount) {
m_scriptNodes[m_scriptNodesCount] = ScriptNode::FunctionNode(functionName, scriptIndex);
m_scriptNodesCount++;
}
}
void VariableBoxController::didBecomeFirstResponder() {
app()->setFirstResponder(&m_contentViewController);
}
void VariableBoxController::setTextInputCaller(TextInput * textInput) {
m_contentViewController.setTextInputCaller(textInput);
}
void VariableBoxController::viewWillAppear() {
StackViewController::viewWillAppear();
m_contentViewController.reloadData();
}
void VariableBoxController::viewDidDisappear() {
StackViewController::viewDidDisappear();
void VariableBoxController::addVariableAtIndex(const char * variableName, int scriptIndex) {
if (m_scriptNodesCount < k_maxScriptNodesCount) {
m_scriptNodes[m_scriptNodesCount] = ScriptNode::VariableNode(variableName, scriptIndex);
m_scriptNodesCount++;
}
}
}

View File

@@ -2,61 +2,45 @@
#define CODE_VARIABLE_BOX_CONTROLLER_H
#include <escher.h>
#include "menu_controller.h"
#include "script_node.h"
#include "script_node_cell.h"
#include "script_store.h"
namespace Code {
class VariableBoxController : public StackViewController {
class App;
class VariableBoxController : public NestedMenuController {
public:
VariableBoxController(App * pythonDelegate, ScriptStore * scriptStore);
void didBecomeFirstResponder() override;
void setTextInputCaller(TextInput * textInput);
void viewWillAppear() override;
void viewDidDisappear() override;
/* Responder */
bool handleEvent(Ion::Events::Event event) override;
void didEnterResponderChain(Responder * previousFirstResponder) override;
/* ListViewDataSource */
int numberOfRows() override;
int reusableCellCount(int type) override;
void willDisplayCellForIndex(HighlightCell * cell, int index) override;
int typeAtLocation(int i, int j) override;
/* VariableBoxController */
void loadFunctionsAndVariables();
private:
class ContentViewController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource {
public:
ContentViewController(Responder * parentResponder, App * pythonDelegate, ScriptStore * scriptStore);
void setTextInputCaller(TextInput * textInput);
void reloadData();
void addFunctionAtIndex(const char * functionName, int scriptIndex);
void addVariableAtIndex(const char * variableName, int scriptIndex);
/* ViewController */
const char * title() override;
View * view() override { return &m_selectableTableView; }
void viewWillAppear() override;
void viewDidDisappear() override;
/* Responder */
void didEnterResponderChain(Responder * previousFirstResponder) override;
void didBecomeFirstResponder() override;
bool handleEvent(Ion::Events::Event event) override;
/* SimpleListViewDataSource */
KDCoordinate cellHeight() override { return Metric::ToolboxRowHeight; }
int numberOfRows() override;
HighlightCell * reusableCell(int index) override;
int reusableCellCount() override;
void willDisplayCellForIndex(HighlightCell * cell, int index) override;
private:
constexpr static int k_maxScriptObjectNameSize = 100;
constexpr static int k_maxNumberOfDisplayedRows = 6; //240/40
constexpr static int k_maxScriptNodesCount = 32;
void insertTextInCaller(const char * text);
int m_scriptNodesCount;
ScriptNode m_scriptNodes[k_maxScriptNodesCount];
App * m_pythonDelegate;
ScriptStore * m_scriptStore;
TextInput * m_textInputCaller;
ScriptNodeCell m_leafCells[k_maxNumberOfDisplayedRows];
SelectableTableView m_selectableTableView;
};
ContentViewController m_contentViewController;
constexpr static int k_maxScriptObjectNameSize = 100;
constexpr static int k_maxNumberOfDisplayedRows = 6; //240/40
constexpr static int k_maxScriptNodesCount = 32;
HighlightCell * leafCellAtIndex(int index) override;
HighlightCell * nodeCellAtIndex(int index) override { return nullptr; }
bool selectLeaf(int rowIndex) override;
void insertTextInCaller(const char * text);
void addFunctionAtIndex(const char * functionName, int scriptIndex);
void addVariableAtIndex(const char * variableName, int scriptIndex);
App * m_pythonDelegate;
ScriptNode m_scriptNodes[k_maxScriptNodesCount];
int m_scriptNodesCount;
ScriptStore * m_scriptStore;
ScriptNodeCell m_leafCells[k_maxNumberOfDisplayedRows];
};
}

View File

@@ -1,11 +1,14 @@
#ifndef APPS_CONSTANT_H
#define APPS_CONSTANT_H
#include <escher/text_field.h>
class Constant {
public:
constexpr static int LargeNumberOfSignificantDigits = 7;
constexpr static int MediumNumberOfSignificantDigits = 5;
constexpr static int ShortNumberOfSignificantDigits = 4;
constexpr static int MaxSerializedExpressionSize = 2*::TextField::maxBufferSize();
};
#endif

View File

@@ -50,10 +50,11 @@ ExamPopUpController::ContentView::ContentView(Responder * parentResponder) :
ExamPopUpController * controller = (ExamPopUpController *)context;
Container * container = (Container *)controller->app()->container();
container->activeApp()->dismissModalViewController();
return true;
}, parentResponder), KDFont::SmallFont),
m_okButton(parentResponder, I18n::Message::Ok, Invocation([](void * context, void * sender) {
ExamPopUpController * controller = (ExamPopUpController *)context;
GlobalPreferences::ExamMode nextExamMode = controller->isActivatingExamMode() ? GlobalPreferences::ExamMode::Activate : GlobalPreferences::ExamMode::Desactivate;
GlobalPreferences::ExamMode nextExamMode = controller->isActivatingExamMode() ? GlobalPreferences::ExamMode::Activate : GlobalPreferences::ExamMode::Deactivate;
GlobalPreferences::sharedGlobalPreferences()->setExamMode(nextExamMode);
AppsContainer * container = (AppsContainer *)controller->app()->container();
if (controller->isActivatingExamMode()) {
@@ -65,6 +66,7 @@ ExamPopUpController::ContentView::ContentView(Responder * parentResponder) :
}
container->refreshPreferences();
container->activeApp()->dismissModalViewController();
return true;
}, parentResponder), KDFont::SmallFont),
m_warningTextView(KDFont::SmallFont, I18n::Message::Warning, 0.5, 0.5, KDColorWhite, KDColorBlack),
m_messageTextView1(KDFont::SmallFont, I18n::Message::Default, 0.5, 0.5, KDColorWhite, KDColorBlack),

View File

@@ -5,34 +5,6 @@ GlobalPreferences * GlobalPreferences::sharedGlobalPreferences() {
return &globalPreferences;
}
I18n::Language GlobalPreferences::language() const {
return m_language;
}
void GlobalPreferences::setLanguage(I18n::Language language) {
m_language = language;
}
GlobalPreferences::ExamMode GlobalPreferences::examMode() const {
return m_examMode;
}
void GlobalPreferences::setExamMode(ExamMode examMode) {
m_examMode = examMode;
}
#ifdef EPSILON_BOOT_PROMPT
void GlobalPreferences::setShowPopUp(bool showPopUp) {
m_showPopUp = showPopUp;
}
#endif
int GlobalPreferences::brightnessLevel() const {
return m_brightnessLevel;
}
void GlobalPreferences::setBrightnessLevel(int brightnessLevel) {
if (m_brightnessLevel != brightnessLevel) {
brightnessLevel = brightnessLevel < 0 ? 0 : brightnessLevel;

View File

@@ -7,24 +7,24 @@ class GlobalPreferences {
public:
enum class ExamMode {
Activate,
Desactivate
Deactivate
};
static GlobalPreferences * sharedGlobalPreferences();
I18n::Language language() const;
void setLanguage(I18n::Language language);
ExamMode examMode() const;
void setExamMode(ExamMode examMode);
I18n::Language language() const { return m_language; }
void setLanguage(I18n::Language language) { m_language = language; }
ExamMode examMode() const { return m_examMode; }
void setExamMode(ExamMode examMode) { m_examMode = examMode; }
#ifdef EPSILON_BOOT_PROMPT
bool showPopUp() const { return m_showPopUp; }
void setShowPopUp(bool showPopUp);
void setShowPopUp(bool showPopUp) { m_showPopUp = showPopUp; }
#endif
int brightnessLevel() const;
int brightnessLevel() const { return m_brightnessLevel; }
void setBrightnessLevel(int brightnessLevel);
constexpr static int NumberOfBrightnessStates = 5;
private:
GlobalPreferences() :
m_language(I18n::Language::EN),
m_examMode(ExamMode::Desactivate),
m_examMode(ExamMode::Deactivate),
#ifdef EPSILON_BOOT_PROMPT
m_showPopUp(true),
#endif

View File

@@ -3,8 +3,7 @@ app_headers += apps/graph/app.h
app_objs += $(addprefix apps/graph/,\
app.o\
cartesian_function.o\
cartesian_function_store.o\
storage_cartesian_function_store.o\
graph/banner_view.o\
graph/calculation_graph_controller.o\
graph/calculation_parameter_controller.o\
@@ -15,12 +14,14 @@ app_objs += $(addprefix apps/graph/,\
graph/graph_view.o\
graph/integral_graph_controller.o\
graph/intersection_graph_controller.o\
graph/tangent_graph_controller.o\
graph/root_graph_controller.o\
list/list_controller.o\
values/derivative_parameter_controller.o\
values/function_parameter_controller.o\
values/values_controller.o\
graph/tangent_graph_controller.o\
list/list_parameter_controller.o\
list/storage_list_controller.o\
list/text_field_function_title_cell.o\
values/storage_derivative_parameter_controller.o\
values/storage_function_parameter_controller.o\
values/storage_values_controller.o\
)
i18n_files += $(addprefix apps/graph/,\

View File

@@ -21,7 +21,7 @@ const Image * App::Descriptor::icon() {
}
App::Snapshot::Snapshot() :
Shared::FunctionApp::Snapshot::Snapshot(),
Shared::StorageFunctionApp::Snapshot::Snapshot(),
m_functionStore(),
m_graphRange(&m_cursor)
{
@@ -32,9 +32,13 @@ App * App::Snapshot::unpack(Container * container) {
}
void App::Snapshot::reset() {
FunctionApp::Snapshot::reset();
m_functionStore.removeAll();
m_graphRange.setDefault();
StorageFunctionApp::Snapshot::reset();
*(modelVersion()) = 0;
*(rangeVersion()) = 0;
}
void App::Snapshot::storageDidChangeForRecord(const Ion::Storage::Record record) {
m_functionStore.storageDidChangeForRecord(record);
}
App::Descriptor * App::Snapshot::descriptor() {
@@ -42,7 +46,7 @@ App::Descriptor * App::Snapshot::descriptor() {
return &descriptor;
}
CartesianFunctionStore * App::Snapshot::functionStore() {
StorageCartesianFunctionStore * App::Snapshot::functionStore() {
return &m_functionStore;
}
@@ -56,21 +60,21 @@ void App::Snapshot::tidy() {
}
App::App(Container * container, Snapshot * snapshot) :
FunctionApp(container, snapshot, &m_inputViewController),
m_listController(&m_listFooter, snapshot->functionStore(), &m_listHeader, &m_listFooter),
StorageFunctionApp(container, snapshot, &m_inputViewController),
m_listController(&m_listFooter, &m_listHeader, &m_listFooter),
m_listFooter(&m_listHeader, &m_listController, &m_listController, ButtonRowController::Position::Bottom, ButtonRowController::Style::EmbossedGrey),
m_listHeader(&m_listStackViewController, &m_listFooter, &m_listController),
m_listStackViewController(&m_tabViewController, &m_listHeader),
m_graphController(&m_graphAlternateEmptyViewController, snapshot->functionStore(), snapshot->graphRange(), snapshot->cursor(), snapshot->indexFunctionSelectedByCursor(), snapshot->modelVersion(), snapshot->rangeVersion(), snapshot->angleUnitVersion(), &m_graphHeader),
m_graphController(&m_graphAlternateEmptyViewController, this, snapshot->functionStore(), snapshot->graphRange(), snapshot->cursor(), snapshot->indexFunctionSelectedByCursor(), snapshot->modelVersion(), snapshot->rangeVersion(), snapshot->angleUnitVersion(), &m_graphHeader),
m_graphAlternateEmptyViewController(&m_graphHeader, &m_graphController, &m_graphController),
m_graphHeader(&m_graphStackViewController, &m_graphAlternateEmptyViewController, &m_graphController),
m_graphStackViewController(&m_tabViewController, &m_graphHeader),
m_valuesController(&m_valuesAlternateEmptyViewController, snapshot->functionStore(), snapshot->interval(), &m_valuesHeader),
m_valuesController(&m_valuesAlternateEmptyViewController, this, snapshot->interval(), &m_valuesHeader),
m_valuesAlternateEmptyViewController(&m_valuesHeader, &m_valuesController, &m_valuesController),
m_valuesHeader(&m_valuesStackViewController, &m_valuesAlternateEmptyViewController, &m_valuesController),
m_valuesStackViewController(&m_tabViewController, &m_valuesHeader),
m_tabViewController(&m_inputViewController, snapshot, &m_listStackViewController, &m_graphStackViewController, &m_valuesStackViewController),
m_inputViewController(&m_modalViewController, &m_tabViewController, this, this)
m_inputViewController(&m_modalViewController, &m_tabViewController, this, this, this)
{
}
@@ -78,8 +82,15 @@ InputViewController * App::inputViewController() {
return &m_inputViewController;
}
const char * App::XNT() {
return "x";
char App::XNT() {
return 'x';
}
NestedMenuController * App::variableBoxForInputEventHandler(InputEventHandler * textInput) {
VariableBoxController * varBox = container()->variableBoxController();
varBox->setSender(textInput);
varBox->lockDeleteEvent(VariableBoxController::Page::Function);
return varBox;
}
}

View File

@@ -2,15 +2,15 @@
#define GRAPH_APP_H
#include <escher.h>
#include "cartesian_function_store.h"
#include "storage_cartesian_function_store.h"
#include "graph/graph_controller.h"
#include "list/list_controller.h"
#include "values/values_controller.h"
#include "../shared/function_app.h"
#include "list/storage_list_controller.h"
#include "values/storage_values_controller.h"
#include "../shared/storage_function_app.h"
namespace Graph {
class App : public Shared::FunctionApp {
class App : public Shared::StorageFunctionApp {
public:
class Descriptor : public ::App::Descriptor {
public:
@@ -18,24 +18,27 @@ public:
I18n::Message upperName() override;
const Image * icon() override;
};
class Snapshot : public Shared::FunctionApp::Snapshot {
class Snapshot : public Shared::StorageFunctionApp::Snapshot {
public:
Snapshot();
App * unpack(Container * container) override;
void reset() override;
void storageDidChangeForRecord(const Ion::Storage::Record record) override;
Descriptor * descriptor() override;
CartesianFunctionStore * functionStore();
StorageCartesianFunctionStore * functionStore();
Shared::InteractiveCurveViewRange * graphRange();
private:
void tidy() override;
CartesianFunctionStore m_functionStore;
StorageCartesianFunctionStore m_functionStore;
Shared::InteractiveCurveViewRange m_graphRange;
};
InputViewController * inputViewController() override;
const char * XNT() override;
char XNT() override;
NestedMenuController * variableBoxForInputEventHandler(InputEventHandler * textInput) override;
StorageCartesianFunctionStore * functionStore() override { return static_cast<Snapshot *>(snapshot())->functionStore(); }
private:
App(Container * container, Snapshot * snapshot);
ListController m_listController;
StorageListController m_listController;
ButtonRowController m_listFooter;
ButtonRowController m_listHeader;
StackViewController m_listStackViewController;
@@ -43,7 +46,7 @@ private:
AlternateEmptyViewController m_graphAlternateEmptyViewController;
ButtonRowController m_graphHeader;
StackViewController m_graphStackViewController;
ValuesController m_valuesController;
StorageValuesController m_valuesController;
AlternateEmptyViewController m_valuesAlternateEmptyViewController;
ButtonRowController m_valuesHeader;
StackViewController m_valuesStackViewController;

View File

@@ -17,7 +17,8 @@ NoMaximumFound = "Kein Maximalwert gefunden"
NoMinimumFound = "Kein Mindestwert gefunden"
NoZeroFound = "Keine Nullstelle gefunden"
NoIntersectionFound = "Kein Schnittpunkt gefunden"
XColumn = "X Spalte"
DerivativeColumn = "0'(x) Spalte"
DerivativeFunctionColumn = "Spalte der Ableitungsfunktion"
HideDerivativeColumn = "Ableitungsfunktion ausblenden"
AllowedCharactersAZaz09 = "Erlaubte Zeichen: A-Z, a-z, 0-9, _"
ReservedName = "Reserviertes Wort"
NameCannotStartWithNumber = "Ein name darf nicht mit einer Zahl beginnen"

View File

@@ -17,7 +17,8 @@ NoMaximumFound = "No maximum found"
NoMinimumFound = "No minimum found"
NoZeroFound = "No zero found"
NoIntersectionFound = "No intersection found"
XColumn = "x column"
DerivativeColumn = "0'(x) column"
DerivativeFunctionColumn = "Derivative function column"
HideDerivativeColumn = "Hide the derivative function"
AllowedCharactersAZaz09 = "Allowed characters: A-Z, a-z, 0-9, _"
ReservedName = "Reserved name"
NameCannotStartWithNumber = "A name cannot start with a number"

View File

@@ -17,7 +17,8 @@ NoMaximumFound = "Níngun máximo encontrado"
NoMinimumFound = "Níngun mínimo encontrado"
NoZeroFound = "Ninguna raiz encontrada"
NoIntersectionFound = "Ninguna intersección encontrada"
XColumn = "Columna x"
DerivativeColumn = "Columna 0'(x)"
DerivativeFunctionColumn = "Columna de la derivada"
HideDerivativeColumn = "Ocultar la derivada"
AllowedCharactersAZaz09 = "Caracteres permitidos : A-Z, a-z, 0-9, _"
ReservedName = "Nombre reservado"
NameCannotStartWithNumber = "Un nombre no puede empezar con un número"

View File

@@ -17,7 +17,8 @@ NoMaximumFound = "Aucun maximum trouve"
NoMinimumFound = "Aucun minimum trouve"
NoZeroFound = "Aucun zero trouve"
NoIntersectionFound = "Aucune intersection trouvée"
XColumn = "Colonne x"
DerivativeColumn = "Colonne 0'(x)"
DerivativeFunctionColumn = "Colonne de la fonction derivee"
HideDerivativeColumn = "Masquer la fonction derivee"
AllowedCharactersAZaz09 = "Caractères autorisés : A-Z, a-z, 0-9, _"
ReservedName = "Nom réservé"
NameCannotStartWithNumber = "Un nom ne peut pas commencer par un chiffre"

View File

@@ -17,7 +17,8 @@ NoMaximumFound = "Nenhum máximo encontrado"
NoMinimumFound = "Nenhum mínimo encontrado"
NoZeroFound = "Nenhuma raiz encontrada"
NoIntersectionFound = "Nenhuma interseção encontrada"
XColumn = "Coluna X"
DerivativeColumn = "Coluna 0'(x)"
DerivativeFunctionColumn = "Coluna da funcao derivada"
HideDerivativeColumn = "Esconder funcao derivada"
AllowedCharactersAZaz09 = "Caracteres permitidos : A-Z, a-z, 0-9, _"
ReservedName = "Nome reservado"
NameCannotStartWithNumber = "Um nome não pode começar com um número"

View File

@@ -1,64 +0,0 @@
#include "cartesian_function.h"
#include "../shared/poincare_helpers.h"
#include <float.h>
#include <cmath>
#include <poincare/derivative.h>
#include <poincare/integral.h>
using namespace Poincare;
using namespace Shared;
namespace Graph {
CartesianFunction::CartesianFunction(const char * text, KDColor color) :
Shared::Function(text, color),
m_displayDerivative(false)
{
}
bool CartesianFunction::displayDerivative() {
return m_displayDerivative;
}
void CartesianFunction::setDisplayDerivative(bool display) {
m_displayDerivative = display;
}
double CartesianFunction::approximateDerivative(double x, Poincare::Context * context) const {
Poincare::Derivative derivative(expression(context).clone(), Poincare::Float<double>(x)); // derivative takes ownership of Poincare::Float<double>(x) and the clone of expression
/* TODO: when we will approximate derivative, we might want to simplify the
* derivative here. However, we might want to do it once for all x (to avoid
* lagging in the derivative table. */
return PoincareHelpers::ApproximateToScalar<double>(derivative, *context);
}
double CartesianFunction::sumBetweenBounds(double start, double end, Poincare::Context * context) const {
Poincare::Integral integral(expression(context).clone(), Poincare::Float<double>(start), Poincare::Float<double>(end)); // Integral takes ownership of args
/* TODO: when we will approximate integral, we might want to simplify the
* integral here. However, we might want to do it once for all x (to avoid
* lagging in the derivative table. */
return PoincareHelpers::ApproximateToScalar<double>(integral, *context);
}
Expression::Coordinate2D CartesianFunction::nextMinimumFrom(double start, double step, double max, Context * context) const {
return expression(context).nextMinimum(symbol(), start, step, max, *context, Preferences::sharedPreferences()->angleUnit());
}
Expression::Coordinate2D CartesianFunction::nextMaximumFrom(double start, double step, double max, Context * context) const {
return expression(context).nextMaximum(symbol(), start, step, max, *context, Preferences::sharedPreferences()->angleUnit());
}
double CartesianFunction::nextRootFrom(double start, double step, double max, Context * context) const {
return expression(context).nextRoot(symbol(), start, step, max, *context, Preferences::sharedPreferences()->angleUnit());
}
Expression::Coordinate2D CartesianFunction::nextIntersectionFrom(double start, double step, double max, Poincare::Context * context, const Shared::Function * function) const {
return expression(context).nextIntersection(symbol(), start, step, max, *context, Preferences::sharedPreferences()->angleUnit(), function->expression(context));
}
char CartesianFunction::symbol() const {
return 'x';
}
}

View File

@@ -1,27 +0,0 @@
#ifndef GRAPH_CARTESIAN_FUNCTION_H
#define GRAPH_CARTESIAN_FUNCTION_H
#include "../shared/function.h"
namespace Graph {
class CartesianFunction : public Shared::Function {
public:
using Shared::Function::Function;
CartesianFunction(const char * text = nullptr, KDColor color = KDColorBlack);
bool displayDerivative();
void setDisplayDerivative(bool display);
double approximateDerivative(double x, Poincare::Context * context) const;
double sumBetweenBounds(double start, double end, Poincare::Context * context) const override;
Poincare::Expression::Coordinate2D nextMinimumFrom(double start, double step, double max, Poincare::Context * context) const;
Poincare::Expression::Coordinate2D nextMaximumFrom(double start, double step, double max, Poincare::Context * context) const;
double nextRootFrom(double start, double step, double max, Poincare::Context * context) const;
Poincare::Expression::Coordinate2D nextIntersectionFrom(double start, double step, double max, Poincare::Context * context, const Shared::Function * function) const;
char symbol() const override;
private:
bool m_displayDerivative;
};
}
#endif

View File

@@ -1,55 +0,0 @@
#include "cartesian_function_store.h"
extern "C" {
#include <assert.h>
#include <stddef.h>
}
#include <ion.h>
namespace Graph {
constexpr int CartesianFunctionStore::k_maxNumberOfFunctions;
constexpr const char * CartesianFunctionStore::k_functionNames[k_maxNumberOfFunctions];
CartesianFunctionStore::CartesianFunctionStore() :
Shared::FunctionStore()
{
addEmptyModel();
}
uint32_t CartesianFunctionStore::storeChecksum() {
size_t dataLengthInBytes = k_maxNumberOfFunctions*sizeof(uint32_t);
assert((dataLengthInBytes & 0x3) == 0); // Assert that dataLengthInBytes is a multiple of 4
uint32_t checksums[k_maxNumberOfFunctions];
for (int i = 0; i < k_maxNumberOfFunctions; i++) {
checksums[i] = m_functions[i].checksum();
}
return Ion::crc32((uint32_t *)checksums, dataLengthInBytes/sizeof(uint32_t));
}
char CartesianFunctionStore::symbol() const {
return 'x';
}
void CartesianFunctionStore::removeAll() {
FunctionStore::removeAll();
addEmptyModel();
}
CartesianFunction * CartesianFunctionStore::emptyModel() {
static CartesianFunction addedFunction("", KDColorBlack);
addedFunction = CartesianFunction(firstAvailableName(), firstAvailableColor());
return &addedFunction;
}
CartesianFunction * CartesianFunctionStore::nullModel() {
static CartesianFunction emptyFunction("", KDColorBlack);
return &emptyFunction;
}
void CartesianFunctionStore::setModelAtIndex(Shared::ExpressionModel * e, int i) {
assert(i>=0 && i<m_numberOfModels);
m_functions[i] = *(static_cast<CartesianFunction *>(e));
}
}

View File

@@ -1,39 +0,0 @@
#ifndef GRAPH_CARTESIAN_FUNCTION_STORE_H
#define GRAPH_CARTESIAN_FUNCTION_STORE_H
#include "cartesian_function.h"
#include "../shared/function_store.h"
#include <stdint.h>
#include <escher.h>
namespace Graph {
class CartesianFunctionStore : public Shared::FunctionStore {
public:
CartesianFunctionStore();
uint32_t storeChecksum() override;
CartesianFunction * modelAtIndex(int i) override { return &m_functions[i]; }
CartesianFunction * activeFunctionAtIndex(int i) override { return (CartesianFunction *)Shared::FunctionStore::activeFunctionAtIndex(i); }
CartesianFunction * definedFunctionAtIndex(int i) override { return (CartesianFunction *)Shared::FunctionStore::definedFunctionAtIndex(i); }
int maxNumberOfModels() const override {
return k_maxNumberOfFunctions;
}
char symbol() const override;
void removeAll() override;
static constexpr int k_maxNumberOfFunctions = 4;
private:
static constexpr const char * k_functionNames[k_maxNumberOfFunctions] = {
"f", "g", "h", "p",
};
CartesianFunction * emptyModel() override;
CartesianFunction * nullModel() override;
void setModelAtIndex(Shared::ExpressionModel * f, int i) override;
const char * firstAvailableName() override {
return firstAvailableAttribute(k_functionNames, FunctionStore::name);
}
CartesianFunction m_functions[k_maxNumberOfFunctions];
};
}
#endif

View File

@@ -12,7 +12,7 @@ CalculationGraphController::CalculationGraphController(Responder * parentRespond
m_bannerView(bannerView),
m_graphRange(curveViewRange),
m_cursor(cursor),
m_function(nullptr),
m_record(),
m_defaultBannerView(KDFont::SmallFont, defaultMessage, 0.5f, 0.5f, KDColorBlack, Palette::GreyMiddle),
m_isActive(false)
{
@@ -23,7 +23,7 @@ View * CalculationGraphController::view() {
}
void CalculationGraphController::viewWillAppear() {
assert(m_function != nullptr);
assert(!m_record.isNull());
Expression::Coordinate2D pointOfInterest = computeNewPointOfInteresetFromAbscissa(m_graphRange->xMin(), 1);
if (std::isnan(pointOfInterest.abscissa)) {
m_isActive = false;
@@ -56,14 +56,14 @@ bool CalculationGraphController::handleEvent(Ion::Events::Event event) {
return false;
}
void CalculationGraphController::setFunction(CartesianFunction * function) {
m_graphView->selectFunction(function);
m_function = function;
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_function, 'x');
reloadBannerViewForCursorOnFunction(m_cursor, m_record, functionStore(), StorageCartesianFunctionStore::Symbol());
}
bool CalculationGraphController::moveCursor(int direction) {
@@ -77,11 +77,16 @@ bool CalculationGraphController::moveCursor(int direction) {
}
Expression::Coordinate2D CalculationGraphController::computeNewPointOfInteresetFromAbscissa(double start, int direction) {
TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app();
App * myApp = static_cast<App *>(app());
double step = m_graphRange->xGridUnit()/10.0;
step = direction < 0 ? -step : step;
double max = direction > 0 ? m_graphRange->xMax() : m_graphRange->xMin();
return computeNewPointOfInterest(start, step, max, myApp->localContext());
}
StorageCartesianFunctionStore * CalculationGraphController::functionStore() const {
App * a = static_cast<App *>(app());
return a->functionStore();
}
}

View File

@@ -5,18 +5,20 @@
#include "banner_view.h"
#include "../../shared/curve_view_cursor.h"
#include "../../shared/interactive_curve_view_range.h"
#include "../../shared/function_banner_delegate.h"
#include "../cartesian_function.h"
#include "../../shared/storage_function_banner_delegate.h"
#include "../storage_cartesian_function_store.h"
namespace Graph {
class CalculationGraphController : public ViewController, public Shared::FunctionBannerDelegate {
class App;
class CalculationGraphController : public ViewController, public Shared::StorageFunctionBannerDelegate {
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 setFunction(CartesianFunction * function);
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
@@ -24,12 +26,13 @@ protected:
virtual void reloadBannerView();
bool moveCursor(int direction);
Poincare::Expression::Coordinate2D computeNewPointOfInteresetFromAbscissa(double start, int direction);
StorageCartesianFunctionStore * 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;
CartesianFunction * m_function;
Ion::Storage::Record m_record;
MessageTextView m_defaultBannerView;
bool m_isActive;
};

View File

@@ -7,16 +7,16 @@ using namespace Shared;
namespace Graph {
CalculationParameterController::CalculationParameterController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, InteractiveCurveViewRange * range, CurveViewCursor * cursor, CartesianFunctionStore * functionStore) :
CalculationParameterController::CalculationParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, GraphView * graphView, BannerView * bannerView, InteractiveCurveViewRange * range, CurveViewCursor * cursor) :
ViewController(parentResponder),
m_selectableTableView(this),
m_function(nullptr),
m_record(),
m_tangentGraphController(nullptr, graphView, bannerView, range, cursor),
m_integralGraphController(nullptr, graphView, range, cursor),
m_integralGraphController(nullptr, inputEventHandlerDelegate, graphView, range, cursor),
m_minimumGraphController(nullptr, graphView, bannerView, range, cursor),
m_maximumGraphController(nullptr, graphView, bannerView, range, cursor),
m_rootGraphController(nullptr, graphView, bannerView, range, cursor),
m_intersectionGraphController(nullptr, graphView, bannerView, range, cursor, functionStore)
m_intersectionGraphController(nullptr, graphView, bannerView, range, cursor)
{
}
@@ -38,27 +38,27 @@ bool CalculationParameterController::handleEvent(Ion::Events::Event event) {
ViewController * controller = nullptr;
switch(selectedRow()) {
case 0:
m_intersectionGraphController.setFunction(m_function);
m_intersectionGraphController.setRecord(m_record);
controller = &m_intersectionGraphController;
break;
case 1:
m_maximumGraphController.setFunction(m_function);
m_maximumGraphController.setRecord(m_record);
controller = &m_maximumGraphController;
break;
case 2:
m_minimumGraphController.setFunction(m_function);
m_minimumGraphController.setRecord(m_record);
controller = &m_minimumGraphController;
break;
case 3:
m_rootGraphController.setFunction(m_function);
m_rootGraphController.setRecord(m_record);
controller = &m_rootGraphController;
break;
case 4:
m_tangentGraphController.setFunction(m_function);
m_tangentGraphController.setRecord(m_record);
controller = &m_tangentGraphController;
break;
case 5:
m_integralGraphController.setFunction(m_function);
m_integralGraphController.setRecord(m_record);
controller = &m_integralGraphController;
break;
default:
@@ -103,8 +103,8 @@ void CalculationParameterController::willDisplayCellForIndex(HighlightCell * cel
myCell->setMessage(titles[index]);
}
void CalculationParameterController::setFunction(CartesianFunction * function) {
m_function = function;
void CalculationParameterController::setRecord(Ion::Storage::Record record) {
m_record = record;
}
}

View File

@@ -2,7 +2,7 @@
#define GRAPH_CALCULATION_PARAMETER_CONTROLLER_H
#include <escher.h>
#include "../cartesian_function.h"
#include "../storage_cartesian_function_store.h"
#include "tangent_graph_controller.h"
#include "extremum_graph_controller.h"
#include "integral_graph_controller.h"
@@ -16,7 +16,7 @@ namespace Graph {
class CalculationParameterController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource {
public:
CalculationParameterController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * range, Shared::CurveViewCursor * cursor, CartesianFunctionStore * functionStore);
CalculationParameterController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * range, Shared::CurveViewCursor * cursor);
View * view() override;
const char * title() override;
bool handleEvent(Ion::Events::Event event) override;
@@ -26,12 +26,12 @@ public:
HighlightCell * reusableCell(int index) override;
int reusableCellCount() override;
void willDisplayCellForIndex(HighlightCell * cell, int index) override;
void setFunction(CartesianFunction * function);
void setRecord(Ion::Storage::Record record);
private:
constexpr static int k_totalNumberOfCells = 6;
MessageTableCell m_cells[k_totalNumberOfCells];
SelectableTableView m_selectableTableView;
CartesianFunction * m_function;
Ion::Storage::Record m_record;
TangentGraphController m_tangentGraphController;
IntegralGraphController m_integralGraphController;
MinimumGraphController m_minimumGraphController;

View File

@@ -7,13 +7,13 @@ using namespace Shared;
namespace Graph {
CurveParameterController::CurveParameterController(InteractiveCurveViewRange * graphRange, BannerView * bannerView, CurveViewCursor * cursor, GraphView * graphView, GraphController * graphController, CartesianFunctionStore * functionStore) :
FunctionCurveParameterController(graphRange, cursor),
m_goToParameterController(this, graphRange, cursor, I18n::Message::X),
CurveParameterController::CurveParameterController(InputEventHandlerDelegate * inputEventHandlerDelegate, InteractiveCurveViewRange * graphRange, BannerView * bannerView, CurveViewCursor * cursor, GraphView * graphView, GraphController * graphController) :
StorageFunctionCurveParameterController(graphRange, cursor),
m_goToParameterController(this, inputEventHandlerDelegate, graphRange, cursor, I18n::Message::X),
m_graphController(graphController),
m_calculationCell(I18n::Message::Compute),
m_derivativeCell(I18n::Message::DerivateNumber),
m_calculationParameterController(this, graphView, bannerView, graphRange, cursor, functionStore)
m_calculationParameterController(this, inputEventHandlerDelegate, graphView, bannerView, graphRange, cursor)
{
}
@@ -33,7 +33,7 @@ bool CurveParameterController::handleEvent(Ion::Events::Event event) {
switch (selectedRow()) {
case 0:
{
m_calculationParameterController.setFunction(static_cast<CartesianFunction *>(m_function));
m_calculationParameterController.setRecord(m_record);
StackViewController * stack = (StackViewController *)parentResponder();
stack->push(&m_calculationParameterController);
return true;
@@ -68,7 +68,7 @@ int CurveParameterController::reusableCellCount() {
return k_totalNumberOfCells;
}
FunctionGoToParameterController * CurveParameterController::goToParameterController() {
StorageFunctionGoToParameterController * CurveParameterController::goToParameterController() {
return &m_goToParameterController;
}

View File

@@ -1,7 +1,7 @@
#ifndef GRAPH_GRAPH_CURVE_PARAMETER_CONTROLLER_H
#define GRAPH_GRAPH_CURVE_PARAMETER_CONTROLLER_H
#include "../../shared/function_curve_parameter_controller.h"
#include "../../shared/storage_function_curve_parameter_controller.h"
#include "calculation_parameter_controller.h"
#include "banner_view.h"
@@ -9,9 +9,9 @@ namespace Graph {
class GraphController;
class CurveParameterController : public Shared::FunctionCurveParameterController {
class CurveParameterController : public Shared::StorageFunctionCurveParameterController {
public:
CurveParameterController(Shared::InteractiveCurveViewRange * graphRange, BannerView * bannerView, Shared::CurveViewCursor * cursor, GraphView * graphView, GraphController * graphController, CartesianFunctionStore * functionStore);
CurveParameterController(InputEventHandlerDelegate * inputEventHandlerDelegate, Shared::InteractiveCurveViewRange * graphRange, BannerView * bannerView, Shared::CurveViewCursor * cursor, GraphView * graphView, GraphController * graphController);
const char * title() override;
bool handleEvent(Ion::Events::Event event) override;
int numberOfRows() override;
@@ -19,8 +19,8 @@ public:
int reusableCellCount() override;
void willDisplayCellForIndex(HighlightCell * cell, int index) override;
private:
Shared::FunctionGoToParameterController * goToParameterController() override;
Shared::FunctionGoToParameterController m_goToParameterController;
Shared::StorageFunctionGoToParameterController * goToParameterController() override;
Shared::StorageFunctionGoToParameterController m_goToParameterController;
GraphController * m_graphController;
constexpr static int k_totalNumberOfCells = 3;
MessageTableCellWithChevron m_calculationCell;

View File

@@ -15,8 +15,8 @@ const char * MinimumGraphController::title() {
return I18n::translate(I18n::Message::Minimum);
}
Expression::Coordinate2D MinimumGraphController::computeNewPointOfInterest(double start, double step, double max, Context * context) {
return m_function->nextMinimumFrom(start, step, max, context);
Expression::Coordinate2D MinimumGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) {
return functionStore()->modelForRecord(m_record)->nextMinimumFrom(start, step, max, context);
}
MaximumGraphController::MaximumGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor) :
@@ -28,8 +28,8 @@ const char * MaximumGraphController::title() {
return I18n::translate(I18n::Message::Maximum);
}
Expression::Coordinate2D MaximumGraphController::computeNewPointOfInterest(double start, double step, double max, Context * context) {
return m_function->nextMaximumFrom(start, step, max, context);
Expression::Coordinate2D MaximumGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) {
return functionStore()->modelForRecord(m_record)->nextMaximumFrom(start, step, max, context);
}
}

View File

@@ -2,24 +2,22 @@
#include "../app.h"
using namespace Shared;
using namespace Poincare;
namespace Graph {
GraphController::GraphController(Responder * parentResponder, CartesianFunctionStore * functionStore, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * rangeVersion, Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header) :
FunctionGraphController(parentResponder, header, curveViewRange, &m_view, cursor, indexFunctionSelectedByCursor, modelVersion, rangeVersion, angleUnitVersion),
GraphController::GraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, StorageCartesianFunctionStore * functionStore, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header) :
StorageFunctionGraphController(parentResponder, inputEventHandlerDelegate, header, curveViewRange, &m_view, cursor, indexFunctionSelectedByCursor, modelVersion, rangeVersion, angleUnitVersion),
m_bannerView(),
m_view(functionStore, curveViewRange, m_cursor, &m_bannerView, &m_cursorView),
m_graphRange(curveViewRange),
m_curveParameterController(curveViewRange, &m_bannerView, m_cursor, &m_view, this, functionStore),
m_functionStore(functionStore),
m_curveParameterController(inputEventHandlerDelegate, curveViewRange, &m_bannerView, m_cursor, &m_view, this),
m_displayDerivativeInBanner(false)
{
m_graphRange->setDelegate(this);
}
I18n::Message GraphController::emptyMessage() {
if (m_functionStore->numberOfDefinedModels() == 0) {
if (functionStore()->numberOfDefinedModels() == 0) {
return I18n::Message::NoFunction;
}
return I18n::Message::NoActivatedFunction;
@@ -27,7 +25,7 @@ I18n::Message GraphController::emptyMessage() {
void GraphController::viewWillAppear() {
m_view.drawTangent(false);
FunctionGraphController::viewWillAppear();
StorageFunctionGraphController::viewWillAppear();
selectFunctionWithCursor(indexFunctionSelectedByCursor()); // update the color of the cursor
}
@@ -43,8 +41,8 @@ float GraphController::interestingXRange() {
float characteristicRange = 0.0f;
TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app();
for (int i = 0; i < functionStore()->numberOfActiveFunctions(); i++) {
Function * f = functionStore()->activeFunctionAtIndex(i);
float fRange = f->expression(myApp->localContext()).characteristicXRange(*(myApp->localContext()), Preferences::sharedPreferences()->angleUnit());
ExpiringPointer<StorageCartesianFunction> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i));
float fRange = f->expressionReduced(myApp->localContext()).characteristicXRange(*(myApp->localContext()), Poincare::Preferences::sharedPreferences()->angleUnit());
if (!std::isnan(fRange)) {
characteristicRange = fRange > characteristicRange ? fRange : characteristicRange;
}
@@ -52,9 +50,13 @@ float GraphController::interestingXRange() {
return (characteristicRange > 0.0f ? 1.6f*characteristicRange : 10.0f);
}
int GraphController::estimatedBannerNumberOfLines() const {
return 1 + m_displayDerivativeInBanner;
}
void GraphController::selectFunctionWithCursor(int functionIndex) {
FunctionGraphController::selectFunctionWithCursor(functionIndex);
CartesianFunction * f = m_functionStore->activeFunctionAtIndex(indexFunctionSelectedByCursor());
StorageFunctionGraphController::selectFunctionWithCursor(functionIndex);
ExpiringPointer<StorageCartesianFunction> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor()));
m_cursorView.setColor(f->color());
}
@@ -63,30 +65,26 @@ BannerView * GraphController::bannerView() {
}
void GraphController::reloadBannerView() {
FunctionGraphController::reloadBannerView();
StorageFunctionGraphController::reloadBannerView();
m_bannerView.setNumberOfSubviews(2+m_displayDerivativeInBanner);
if (m_functionStore->numberOfActiveFunctions() == 0 || !m_displayDerivativeInBanner) {
if (functionStore()->numberOfActiveFunctions() == 0 || !m_displayDerivativeInBanner) {
return;
}
CartesianFunction * f = m_functionStore->activeFunctionAtIndex(indexFunctionSelectedByCursor());
TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app();
reloadDerivativeInBannerViewForCursorOnFunction(m_cursor, f, myApp);
Ion::Storage::Record record = functionStore()->activeRecordAtIndex(indexFunctionSelectedByCursor());
App * myApp = static_cast<App *>(app());
reloadDerivativeInBannerViewForCursorOnFunction(m_cursor, record, myApp);
}
bool GraphController::moveCursorHorizontally(int direction) {
CartesianFunction * f = m_functionStore->activeFunctionAtIndex(indexFunctionSelectedByCursor());
TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app();
return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, f, myApp, k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio);
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);
}
InteractiveCurveViewRange * GraphController::interactiveCurveViewRange() {
return m_graphRange;
}
CartesianFunctionStore * GraphController::functionStore() const {
return m_functionStore;
}
GraphView * GraphController::functionGraphView() {
return &m_view;
}

View File

@@ -5,40 +5,40 @@
#include "graph_controller_helper.h"
#include "banner_view.h"
#include "curve_parameter_controller.h"
#include "../../shared/function_graph_controller.h"
#include "../../shared/storage_function_graph_controller.h"
#include "../../shared/curve_view_cursor.h"
#include "../../shared/round_cursor_view.h"
#include "../../shared/interactive_curve_view_range.h"
#include "../cartesian_function_store.h"
#include "../storage_cartesian_function_store.h"
namespace Graph {
class GraphController : public Shared::FunctionGraphController, public GraphControllerHelper {
class GraphController : public Shared::StorageFunctionGraphController, public GraphControllerHelper {
public:
GraphController(Responder * parentResponder, CartesianFunctionStore * functionStore, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header);
GraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, StorageCartesianFunctionStore * functionStore, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header);
I18n::Message emptyMessage() override;
void viewWillAppear() override;
bool displayDerivativeInBanner() const;
void setDisplayDerivativeInBanner(bool displayDerivative);
float interestingXRange() override;
private:
int estimatedBannerNumberOfLines() const override;
void selectFunctionWithCursor(int functionIndex) override;
BannerView * bannerView() override;
void reloadBannerView() override;
bool moveCursorHorizontally(int direction) override;
Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override;
CartesianFunctionStore * functionStore() const override;
GraphView * functionGraphView() override;
View * cursorView() override {
return &m_cursorView;
}
CurveParameterController * curveParameterController() override;
StorageCartesianFunctionStore * functionStore() const override { return static_cast<StorageCartesianFunctionStore *>(Shared::StorageFunctionGraphController::functionStore()); }
Shared::RoundCursorView m_cursorView;
BannerView m_bannerView;
GraphView m_view;
Shared::InteractiveCurveViewRange * m_graphRange;
CurveParameterController m_curveParameterController;
CartesianFunctionStore * m_functionStore;
bool m_displayDerivativeInBanner;
};

View File

@@ -1,4 +1,5 @@
#include "graph_controller_helper.h"
#include "../app.h"
#include "../../constant.h"
#include "../../shared/poincare_helpers.h"
@@ -7,7 +8,8 @@ using namespace Poincare;
namespace Graph {
bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Shared::Function * function, Shared::TextFieldDelegateApp * 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, float cursorTopMarginRatio, float cursorRightMarginRatio, float cursorBottomMarginRatio, float cursorLeftMarginRatio) {
ExpiringPointer<StorageCartesianFunction> 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());
@@ -16,19 +18,17 @@ bool GraphControllerHelper::privateMoveCursorHorizontally(Shared::CurveViewCurso
return true;
}
void GraphControllerHelper::reloadDerivativeInBannerViewForCursorOnFunction(Shared::CurveViewCursor * cursor, CartesianFunction * function, TextFieldDelegateApp * app) {
void GraphControllerHelper::reloadDerivativeInBannerViewForCursorOnFunction(Shared::CurveViewCursor * cursor, Ion::Storage::Record record, App * app) {
ExpiringPointer<StorageCartesianFunction> function = app->functionStore()->modelForRecord(record);
constexpr size_t bufferSize = FunctionBannerDelegate::k_maxNumberOfCharacters+PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits);
char buffer[bufferSize];
const char * space = " ";
int spaceLength = strlen(space);
const char * legend = "00(x)=";
int numberOfChar = strlcpy(buffer, legend, bufferSize);
buffer[0] = function->name()[0];
buffer[1] = '\'';
const char * space = " ";
int numberOfChar = function->derivativeNameWithArgument(buffer, bufferSize, StorageCartesianFunctionStore::Symbol());
const char * legend = "=";
numberOfChar += strlcpy(buffer+numberOfChar, legend, bufferSize-numberOfChar);
double y = function->approximateDerivative(cursor->x(), app->localContext());
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(y, buffer + numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::ShortNumberOfSignificantDigits), Constant::ShortNumberOfSignificantDigits);
strlcpy(buffer+numberOfChar, space, bufferSize - numberOfChar);
buffer[k_maxDigitLegendLength+6] = 0;
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(y, buffer + numberOfChar, bufferSize-numberOfChar, Constant::ShortNumberOfSignificantDigits);
strlcpy(buffer+numberOfChar, space, bufferSize-numberOfChar);
bannerView()->setLegendAtIndex(buffer, 2);
}

View File

@@ -4,15 +4,16 @@
#include "../../shared/function_banner_delegate.h"
#include "../../shared/text_field_delegate_app.h"
#include "../../shared/interactive_curve_view_range.h"
#include "../cartesian_function_store.h"
#include "../storage_cartesian_function_store.h"
namespace Graph {
class App;
class GraphControllerHelper {
protected:
constexpr static int k_maxDigitLegendLength = 10;
bool privateMoveCursorHorizontally(Shared::CurveViewCursor * cursor, int direction, Shared::InteractiveCurveViewRange * range, int numberOfStepsInGradUnit, Shared::Function * function, Shared::TextFieldDelegateApp * app, float cursorTopMarginRatio, float cursorRightMarginRatio, float cursorBottomMarginRatio, float cursorLeftMarginRatio);
void reloadDerivativeInBannerViewForCursorOnFunction(Shared::CurveViewCursor * cursor, CartesianFunction * function, Shared::TextFieldDelegateApp * app);
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);
void reloadDerivativeInBannerViewForCursorOnFunction(Shared::CurveViewCursor * cursor, Ion::Storage::Record record, App * app);
virtual Shared::BannerView * bannerView() = 0;
};

View File

@@ -5,9 +5,9 @@ using namespace Shared;
namespace Graph {
GraphView::GraphView(CartesianFunctionStore * functionStore, InteractiveCurveViewRange * graphRange,
GraphView::GraphView(StorageCartesianFunctionStore * functionStore, InteractiveCurveViewRange * graphRange,
CurveViewCursor * cursor, BannerView * bannerView, View * cursorView) :
FunctionGraphView(graphRange, cursor, bannerView, cursorView),
StorageFunctionGraphView(graphRange, cursor, bannerView, cursorView),
m_functionStore(functionStore),
m_tangent(false)
{
@@ -18,31 +18,32 @@ void GraphView::reload() {
KDRect dirtyZone(KDRect(0, 0, bounds().width(), bounds().height()-m_bannerView->bounds().height()));
markRectAsDirty(dirtyZone);
}
return FunctionGraphView::reload();
return StorageFunctionGraphView::reload();
}
void GraphView::drawRect(KDContext * ctx, KDRect rect) const {
FunctionGraphView::drawRect(ctx, rect);
StorageFunctionGraphView::drawRect(ctx, rect);
for (int i = 0; i < m_functionStore->numberOfActiveFunctions(); i++) {
CartesianFunction * f = m_functionStore->activeFunctionAtIndex(i);
Ion::Storage::Record record = m_functionStore->activeRecordAtIndex(i);
ExpiringPointer<StorageCartesianFunction> f = m_functionStore->modelForRecord(record);;
/* Draw function (color the area under curve of the selected function) */
if (f == m_selectedFunction) {
if (record == m_selectedRecord) {
drawCurve(ctx, rect, [](float t, void * model, void * context) {
CartesianFunction * f = (CartesianFunction *)model;
StorageCartesianFunction * f = (StorageCartesianFunction *)model;
Poincare::Context * c = (Poincare::Context *)context;
return f->evaluateAtAbscissa(t, c);
}, f, context(), f->color(), true, m_highlightedStart, m_highlightedEnd);
}, f.operator->(), context(), f->color(), true, m_highlightedStart, m_highlightedEnd);
} else {
drawCurve(ctx, rect, [](float t, void * model, void * context) {
CartesianFunction * f = (CartesianFunction *)model;
StorageCartesianFunction * f = (StorageCartesianFunction *)model;
Poincare::Context * c = (Poincare::Context *)context;
return f->evaluateAtAbscissa(t, c);
}, f, context(), f->color());
}, f.operator->(), context(), f->color());
}
/* Draw tangent */
if (m_tangent && f == m_selectedFunction) {
if (m_tangent && record == m_selectedRecord) {
float tangentParameter[2];
tangentParameter[0] = f->approximateDerivative(m_curveViewCursor->x(), context());
tangentParameter[1] = -tangentParameter[0]*m_curveViewCursor->x()+f->evaluateAtAbscissa(m_curveViewCursor->x(), context());

View File

@@ -1,15 +1,15 @@
#ifndef GRAPH_GRAPH_VIEW_H
#define GRAPH_GRAPH_VIEW_H
#include "../../shared/function_graph_view.h"
#include "../cartesian_function_store.h"
#include "../../shared/storage_function_graph_view.h"
#include "../storage_cartesian_function_store.h"
namespace Graph {
class GraphView : public Shared::FunctionGraphView {
class GraphView : public Shared::StorageFunctionGraphView {
public:
GraphView(CartesianFunctionStore * functionStore, Shared::InteractiveCurveViewRange * graphRange,
GraphView(StorageCartesianFunctionStore * functionStore, Shared::InteractiveCurveViewRange * graphRange,
Shared::CurveViewCursor * cursor, Shared::BannerView * bannerView, View * cursorView);
void reload() override;
void drawRect(KDContext * ctx, KDRect rect) const override;
@@ -20,7 +20,7 @@ public:
* of the graph where the area under the curve is colored. */
void setAreaHighlightColor(bool highlightColor) override {};
private:
CartesianFunctionStore * m_functionStore;
StorageCartesianFunctionStore * m_functionStore;
bool m_tangent;
};

View File

@@ -12,8 +12,8 @@ using namespace Poincare;
namespace Graph {
IntegralGraphController::IntegralGraphController(Responder * parentResponder, GraphView * graphView, InteractiveCurveViewRange * graphRange, CurveViewCursor * cursor) :
SumGraphController(parentResponder, graphView, graphRange, cursor, Ion::Charset::Integral)
IntegralGraphController::IntegralGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, GraphView * graphView, InteractiveCurveViewRange * graphRange, CurveViewCursor * cursor) :
StorageSumGraphController(parentResponder, inputEventHandlerDelegate, graphView, graphRange, cursor, Ion::Charset::Integral)
{
}
@@ -36,9 +36,12 @@ double IntegralGraphController::cursorNextStep(double x, int direction) {
return (direction > 0 ? x + m_graphRange->xGridUnit()/k_numberOfCursorStepsInGradUnit : x - m_graphRange->xGridUnit()/k_numberOfCursorStepsInGradUnit);
}
Layout IntegralGraphController::createFunctionLayout(const char * functionName) {
char buffer[7] = "0(x)dx";
buffer[0] = functionName[0];
Layout IntegralGraphController::createFunctionLayout(ExpiringPointer<StorageFunction> function) {
constexpr size_t bufferSize = SymbolAbstract::k_maxNameSize+5; // f(x)dx
char buffer[bufferSize];
const char * dx = "dx";
int numberOfChars = function->nameWithArgument(buffer, bufferSize-strlen(dx), StorageCartesianFunctionStore::Symbol());
strlcpy(buffer+numberOfChars, dx, bufferSize-numberOfChars);
return LayoutHelper::String(buffer, strlen(buffer), KDFont::SmallFont);
}

View File

@@ -3,18 +3,18 @@
#include <escher.h>
#include "graph_view.h"
#include "../../shared/sum_graph_controller.h"
#include "../../shared/storage_sum_graph_controller.h"
namespace Graph {
class IntegralGraphController : public Shared::SumGraphController {
class IntegralGraphController : public Shared::StorageSumGraphController {
public:
IntegralGraphController(Responder * parentResponder, GraphView * graphView, Shared::InteractiveCurveViewRange * graphRange, Shared::CurveViewCursor * cursor);
IntegralGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, GraphView * graphView, Shared::InteractiveCurveViewRange * graphRange, Shared::CurveViewCursor * cursor);
const char * title() override;
private:
I18n::Message legendMessageAtStep(Step step) override;
double cursorNextStep(double position, int direction) override;
Poincare::Layout createFunctionLayout(const char * functionName) override;
Poincare::Layout createFunctionLayout(Shared::ExpiringPointer<Shared::StorageFunction> function) override;
};
}

View File

@@ -3,14 +3,12 @@
#include "../../shared/poincare_helpers.h"
using namespace Shared;
using namespace Poincare;
namespace Graph {
IntersectionGraphController::IntersectionGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, CartesianFunctionStore * store) :
IntersectionGraphController::IntersectionGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor) :
CalculationGraphController(parentResponder, graphView, bannerView, curveViewRange, cursor, I18n::Message::NoIntersectionFound),
m_intersectedFunction(nullptr),
m_functionStore(store)
m_intersectedRecord()
{
}
@@ -20,31 +18,33 @@ const char * IntersectionGraphController::title() {
void IntersectionGraphController::reloadBannerView() {
m_bannerView->setNumberOfSubviews(2);
reloadBannerViewForCursorOnFunction(m_cursor, m_function, 'x');
size_t bufferSize = FunctionBannerDelegate::k_maxNumberOfCharacters+PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits);
reloadBannerViewForCursorOnFunction(m_cursor, m_record, functionStore(), StorageCartesianFunctionStore::Symbol());
constexpr size_t bufferSize = FunctionBannerDelegate::k_maxNumberOfCharacters+Poincare::PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits);
char buffer[bufferSize];
const char * space = " ";
int spaceLength = strlen(space);
const char * legend = "0(x)=0(x)=";
int legendLength = strlen(legend);
int numberOfChar = 0;
numberOfChar += strlcpy(buffer, legend, bufferSize);
buffer[0] = m_function->name()[0];
buffer[5] = m_intersectedFunction->name()[0];
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(m_cursor->y(), buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits);
strlcpy(buffer+numberOfChar, space, bufferSize - numberOfChar);
buffer[FunctionBannerDelegate::k_maxDigitLegendLength+legendLength] = 0;
const char * space = " ";
const char * legend = "=";
// 'f(x)=g(x)=', keep 2 chars for '='
ExpiringPointer<StorageCartesianFunction> f = functionStore()->modelForRecord(m_record);
int numberOfChar = f->nameWithArgument(buffer, bufferSize-2, StorageCartesianFunctionStore::Symbol());
numberOfChar += strlcpy(buffer+numberOfChar, legend, bufferSize-numberOfChar);
// keep 1 char for '=';
ExpiringPointer<StorageCartesianFunction> g = functionStore()->modelForRecord(m_intersectedRecord);
numberOfChar += g->nameWithArgument(buffer+numberOfChar, bufferSize-numberOfChar-1, StorageCartesianFunctionStore::Symbol());
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);
}
Expression::Coordinate2D IntersectionGraphController::computeNewPointOfInterest(double start, double step, double max, Context * context) {
Expression::Coordinate2D result = {.abscissa = NAN, .value = NAN};
for (int i = 0; i < m_functionStore->numberOfActiveFunctions(); i++) {
Function * f = m_functionStore->activeFunctionAtIndex(i);
if (f != m_function) {
Expression::Coordinate2D intersection = m_function->nextIntersectionFrom(start, step, max, context, f);
Poincare::Expression::Coordinate2D IntersectionGraphController::computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) {
Poincare::Expression::Coordinate2D result = {.abscissa = NAN, .value = NAN};
for (int i = 0; i < functionStore()->numberOfActiveFunctions(); i++) {
Ion::Storage::Record record = functionStore()->activeRecordAtIndex(i);
if (record != m_record) {
Poincare::Expression e = functionStore()->modelForRecord(record)->expressionReduced(context);
Poincare::Expression::Coordinate2D intersection = functionStore()->modelForRecord(m_record)->nextIntersectionFrom(start, step, max, context, e);
if ((std::isnan(result.abscissa) || std::fabs(intersection.abscissa-start) < std::fabs(result.abscissa-start)) && !std::isnan(intersection.abscissa)) {
m_intersectedFunction = f;
m_intersectedRecord = record;
result = (std::isnan(result.abscissa) || std::fabs(intersection.abscissa-start) < std::fabs(result.abscissa-start)) ? intersection : result;
}
}

View File

@@ -2,18 +2,18 @@
#define GRAPH_INTERSECTION_GRAPH_CONTROLLER_H
#include "calculation_graph_controller.h"
#include "../storage_cartesian_function_store.h"
namespace Graph {
class IntersectionGraphController : public CalculationGraphController {
public:
IntersectionGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor, CartesianFunctionStore * functionStore);
IntersectionGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor);
const char * title() override;
private:
void reloadBannerView() override;
Poincare::Expression::Coordinate2D computeNewPointOfInterest(double start, double step, double max, Poincare::Context * context) override;
Shared::Function * m_intersectedFunction;
CartesianFunctionStore * m_functionStore;
Ion::Storage::Record m_intersectedRecord;
};
}

View File

@@ -16,7 +16,7 @@ const char * RootGraphController::title() {
}
Expression::Coordinate2D RootGraphController::computeNewPointOfInterest(double start, double step, double max, Context * context) {
return {.abscissa = m_function->nextRootFrom(start, step, max, context), .value = 0.0};
return {.abscissa = functionStore()->modelForRecord(m_record)->nextRootFrom(start, step, max, context), .value = 0.0};
}
}

View File

@@ -12,7 +12,7 @@ TangentGraphController::TangentGraphController(Responder * parentResponder, Grap
m_graphView(graphView),
m_bannerView(bannerView),
m_graphRange(curveViewRange),
m_function(nullptr)
m_record()
{
}
@@ -29,37 +29,38 @@ void TangentGraphController::viewWillAppear() {
m_graphView->reload();
}
void TangentGraphController::setFunction(CartesianFunction * function) {
m_graphView->selectFunction(function);
m_function = function;
void TangentGraphController::setRecord(Ion::Storage::Record record) {
m_graphView->selectRecord(record);
m_record = record;
}
void TangentGraphController::reloadBannerView() {
m_bannerView->setNumberOfSubviews(6);
if (m_function == nullptr) {
if (m_record.isNull()) {
return;
}
FunctionBannerDelegate::reloadBannerViewForCursorOnFunction(m_cursor, m_function, 'x');
TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app();
GraphControllerHelper::reloadDerivativeInBannerViewForCursorOnFunction(m_cursor, m_function, myApp);
App * myApp = static_cast<App *>(app());
StorageFunctionBannerDelegate::reloadBannerViewForCursorOnFunction(m_cursor, m_record, myApp->functionStore(), StorageCartesianFunctionStore::Symbol());
GraphControllerHelper::reloadDerivativeInBannerViewForCursorOnFunction(m_cursor, m_record, myApp);
constexpr size_t bufferSize = FunctionBannerDelegate::k_maxNumberOfCharacters+PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits);
char buffer[bufferSize];
const char * legend = "a=";
int legendLength = strlcpy(buffer, legend, bufferSize);
double y = m_function->approximateDerivative(m_cursor->x(), myApp->localContext());
ExpiringPointer<StorageCartesianFunction> 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);
legend = "b=";
legendLength = strlcpy(buffer, legend, bufferSize);
y = -y*m_cursor->x()+m_function->evaluateAtAbscissa(m_cursor->x(), myApp->localContext());
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);
}
bool TangentGraphController::moveCursorHorizontally(int direction) {
TextFieldDelegateApp * myApp = (TextFieldDelegateApp *)app();
return privateMoveCursorHorizontally(m_cursor, direction, m_graphRange, k_numberOfCursorStepsInGradUnit, m_function, myApp, k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio);
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);
}
bool TangentGraphController::handleEnter() {

View File

@@ -5,17 +5,17 @@
#include "banner_view.h"
#include "graph_controller_helper.h"
#include "../../shared/simple_interactive_curve_view_controller.h"
#include "../../shared/function_banner_delegate.h"
#include "../cartesian_function_store.h"
#include "../../shared/storage_function_banner_delegate.h"
#include "../storage_cartesian_function_store.h"
namespace Graph {
class TangentGraphController : public Shared::SimpleInteractiveCurveViewController, public Shared::FunctionBannerDelegate, public GraphControllerHelper {
class TangentGraphController : public Shared::SimpleInteractiveCurveViewController, public Shared::StorageFunctionBannerDelegate, public GraphControllerHelper {
public:
TangentGraphController(Responder * parentResponder, GraphView * graphView, BannerView * bannerView, Shared::InteractiveCurveViewRange * curveViewRange, Shared::CurveViewCursor * cursor);
const char * title() override;
void viewWillAppear() override;
void setFunction(CartesianFunction * function);
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
@@ -28,7 +28,7 @@ private:
GraphView * m_graphView;
BannerView * m_bannerView;
Shared::InteractiveCurveViewRange * m_graphRange;
CartesianFunction * m_function;
Ion::Storage::Record m_record;
};
}

View File

@@ -1,69 +0,0 @@
#include "list_controller.h"
#include "../app.h"
#include "../../i18n.h"
#include <assert.h>
#include <escher/metric.h>
using namespace Shared;
namespace Graph {
ListController::ListController(Responder * parentResponder, CartesianFunctionStore * functionStore, ButtonRowController * header, ButtonRowController * footer) :
Shared::FunctionListController(parentResponder, functionStore, header, footer, I18n::Message::AddFunction),
m_functionTitleCells{},
m_expressionCells{},
m_parameterController(this, functionStore, I18n::Message::FunctionColor, I18n::Message::DeleteFunction)
{
for (int i = 0; i < k_maxNumberOfRows; i++) {
m_expressionCells[i].setLeftMargin(k_expressionMargin);
}
}
const char * ListController::title() {
return I18n::translate(I18n::Message::FunctionTab);
}
ListParameterController * ListController::parameterController() {
return &m_parameterController;
}
int ListController::maxNumberOfRows() {
return k_maxNumberOfRows;
}
HighlightCell * ListController::titleCells(int index) {
assert(index >= 0 && index < k_maxNumberOfRows);
return &m_functionTitleCells[index];
}
HighlightCell * ListController::expressionCells(int index) {
assert(index >= 0 && index < k_maxNumberOfRows);
return &m_expressionCells[index];
}
void ListController::willDisplayTitleCellAtIndex(HighlightCell * cell, int j) {
Shared::BufferFunctionTitleCell * myFunctionCell = (Shared::BufferFunctionTitleCell *)cell;
CartesianFunction * function = ((CartesianFunctionStore *)m_functionStore)->modelAtIndex(j);
char bufferName[5] = {*function->name(),'(', m_functionStore->symbol(),')', 0};
myFunctionCell->setText(bufferName);
KDColor functionNameColor = function->isActive() ? function->color() : Palette::GreyDark;
myFunctionCell->setColor(functionNameColor);
}
void ListController::willDisplayExpressionCellAtIndex(HighlightCell * cell, int j) {
Shared::FunctionListController::willDisplayExpressionCellAtIndex(cell, j);
FunctionExpressionCell * myCell = (FunctionExpressionCell *)cell;
Function * f = m_functionStore->modelAtIndex(j);
bool active = f->isActive();
KDColor textColor = active ? KDColorBlack : Palette::GreyDark;
myCell->setTextColor(textColor);
}
bool ListController::removeModelRow(ExpressionModel * model) {
if (m_functionStore->numberOfModels() > 1) {
return Shared::FunctionListController::removeModelRow(model);
}
return false;
}
}

View File

@@ -1,33 +0,0 @@
#ifndef GRAPH_LIST_CONTROLLER_H
#define GRAPH_LIST_CONTROLLER_H
#include <escher.h>
#include "../../shared/function_list_controller.h"
#include "../cartesian_function_store.h"
#include "../../shared/buffer_function_title_cell.h"
#include "../../shared/function_expression_cell.h"
#include "../../shared/list_parameter_controller.h"
namespace Graph {
class ListController : public Shared::FunctionListController {
public:
ListController(Responder * parentResponder, CartesianFunctionStore * functionStore, ButtonRowController * header, ButtonRowController * footer);
const char * title() override;
private:
Shared::ListParameterController * parameterController() override;
int maxNumberOfRows() override;
HighlightCell * titleCells(int index) override;
HighlightCell * expressionCells(int index) override;
void willDisplayTitleCellAtIndex(HighlightCell * cell, int j) override;
void willDisplayExpressionCellAtIndex(HighlightCell * cell, int j) override;
bool removeModelRow(Shared::ExpressionModel * function) override;
constexpr static int k_maxNumberOfRows = 5;
Shared::BufferFunctionTitleCell m_functionTitleCells[k_maxNumberOfRows];
Shared::FunctionExpressionCell m_expressionCells[k_maxNumberOfRows];
Shared::ListParameterController m_parameterController;
};
}
#endif

View File

@@ -0,0 +1,31 @@
#include "list_parameter_controller.h"
#include "storage_list_controller.h"
#include <assert.h>
using namespace Shared;
namespace Graph {
HighlightCell * ListParameterController::reusableCell(int index) {
if (index == 0) {
return &m_renameCell;
}
return StorageListParameterController::reusableCell(index -1);
}
bool ListParameterController::handleEnterOnRow(int rowIndex) {
if (rowIndex == 0) {
renameFunction();
return true;
}
return StorageListParameterController::handleEnterOnRow(rowIndex-1);
}
void ListParameterController::renameFunction() {
// Set editing true on function title
StackViewController * stack = (StackViewController *)(parentResponder());
stack->pop();
m_listController->renameSelectedFunction();
}
}

View File

@@ -0,0 +1,31 @@
#ifndef GRAPH_LIST_LIST_PARAM_CONTROLLER_H
#define GRAPH_LIST_LIST_PARAM_CONTROLLER_H
#include <apps/shared/storage_list_parameter_controller.h>
namespace Graph {
class StorageListController;
class ListParameterController : public Shared::StorageListParameterController {
public:
ListParameterController(StorageListController * listController, Responder * parentResponder, I18n::Message functionColorMessage, I18n::Message deleteFunctionMessage, SelectableTableViewDelegate * tableDelegate = nullptr) :
Shared::StorageListParameterController(parentResponder, functionColorMessage, deleteFunctionMessage, tableDelegate),
m_listController(listController),
m_renameCell(I18n::Message::Rename)
{}
HighlightCell * reusableCell(int index) override;
protected:
bool handleEnterOnRow(int rowIndex) override;
private:
int totalNumberOfCells() const override {
return Shared::StorageListParameterController::totalNumberOfCells() + 1;
}
void renameFunction();
StorageListController * m_listController;
MessageTableCell m_renameCell;
};
}
#endif

View File

@@ -0,0 +1,171 @@
#include "storage_list_controller.h"
#include "../app.h"
#include "../../i18n.h"
#include <assert.h>
#include <escher/metric.h>
#include <apps/apps_container.h>
using namespace Shared;
namespace Graph {
StorageListController::StorageListController(Responder * parentResponder, ButtonRowController * header, ButtonRowController * footer) :
Shared::StorageFunctionListController(parentResponder, header, footer, I18n::Message::AddFunction),
m_functionTitleCells{ //TODO find better initialization
TextFieldFunctionTitleCell(this),
TextFieldFunctionTitleCell(this),
TextFieldFunctionTitleCell(this),
TextFieldFunctionTitleCell(this),
TextFieldFunctionTitleCell(this),
},
m_expressionCells{},
m_parameterController(this, this, I18n::Message::FunctionColor, I18n::Message::DeleteFunction)
{
for (int i = 0; i < k_maxNumberOfDisplayableRows; i++) {
m_expressionCells[i].setLeftMargin(k_expressionMargin);
}
}
const char * StorageListController::title() {
return I18n::translate(I18n::Message::FunctionTab);
}
void StorageListController::renameSelectedFunction() {
assert(selectedColumn() == 0);
assert(selectedRow() >= 0 && selectedRow() < numberOfRows()-1); // TODO change if sometimes the addFunction row is not displayed
static_cast<AppsContainer *>(const_cast<Container *>(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::AlphaLock);
TextFieldFunctionTitleCell * selectedTitleCell = (TextFieldFunctionTitleCell *)(selectableTableView()->selectedCell());
app()->setFirstResponder(selectedTitleCell);
selectedTitleCell->setEditing(true);
}
bool StorageListController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) {
// Compute the new name
size_t textLength = strlen(text);
size_t argumentLength = StorageFunction::k_parenthesedArgumentLength;
constexpr int maxBaseNameSize = StorageFunction::k_maxNameWithArgumentSize;
char baseName[maxBaseNameSize];
if (textLength <= argumentLength) {
// The user entered an empty name. Use a default function name.
StorageCartesianFunction::DefaultName(baseName, maxBaseNameSize);
size_t defaultNameLength = strlen(baseName);
strlcpy(baseName + defaultNameLength, StorageFunction::k_parenthesedArgument, maxBaseNameSize - defaultNameLength);
textField->setText(baseName);
baseName[defaultNameLength] = 0;
} else {
strlcpy(baseName, text, textLength - argumentLength + 1);
}
// Delete any variable with the same name
GlobalContext::DestroyRecordsBaseNamedWithoutExtension(baseName, GlobalContext::funcExtension /*TODO store elsewhere?*/);
// Set the name
StorageFunction::NameNotCompliantError nameError = StorageFunction::NameNotCompliantError::None;
Ion::Storage::Record::ErrorStatus error = StorageFunction::BaseNameCompliant(baseName, &nameError) ? modelStore()->recordAtIndex(m_selectableTableView.selectedRow()).setBaseNameWithExtension(baseName, GlobalContext::funcExtension /*TODO store elsewhere?*/) : Ion::Storage::Record::ErrorStatus::NonCompliantName;
// Handle any error
if (error == Ion::Storage::Record::ErrorStatus::None) {
bool selectTab = false;
textField->setEditing(false, false);
computeTitlesColumnWidth();
int currentRow = m_selectableTableView.selectedRow();
if (event == Ion::Events::Down && currentRow < numberOfRows() - 1) {
m_selectableTableView.selectCellAtLocation(m_selectableTableView.selectedColumn(), currentRow + 1);
} else if (event == Ion::Events::Up) {
if (currentRow > 0) {
m_selectableTableView.selectCellAtLocation(m_selectableTableView.selectedColumn(), currentRow - 1);
} else {
selectTab = true;
}
}
m_selectableTableView.selectedCell()->setHighlighted(true);
m_selectableTableView.reloadData();
app()->setFirstResponder(&m_selectableTableView);
if (selectTab) {
m_selectableTableView.parentResponder()->handleEvent(event);
}
static_cast<AppsContainer *>(const_cast<Container *>(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default);
return true;
} else if (error == Ion::Storage::Record::ErrorStatus::NameTaken) {
app()->displayWarning(I18n::Message::NameTaken);
} else if (error == Ion::Storage::Record::ErrorStatus::NonCompliantName) {
assert(nameError != StorageFunction::NameNotCompliantError::None);
if (nameError == StorageFunction::NameNotCompliantError::CharacterNotAllowed) {
app()->displayWarning(I18n::Message::AllowedCharactersAZaz09);
} else if (nameError == StorageFunction::NameNotCompliantError::NameCannotStartWithNumber) {
app()->displayWarning(I18n::Message::NameCannotStartWithNumber);
} else {
assert(nameError == StorageFunction::NameNotCompliantError::ReservedName);
app()->displayWarning(I18n::Message::ReservedName);
}
} else {
assert(error == Ion::Storage::Record::ErrorStatus::NotEnoughSpaceAvailable);
app()->displayWarning(I18n::Message::NameTooLong);
}
textField->setEditing(true, false);
return false;
}
bool StorageListController::textFieldDidAbortEditing(TextField * textField) {
ExpiringPointer<StorageFunction> function = modelStore()->modelForRecord(modelStore()->recordAtIndex(selectedRow()));
setFunctionNameInTextField(function, textField);
m_selectableTableView.selectedCell()->setHighlighted(true);
app()->setFirstResponder(&m_selectableTableView);
static_cast<AppsContainer *>(const_cast<Container *>(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default);
return true;
}
bool StorageListController::textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) {
return event == Ion::Events::Up || event == Ion::Events::Down || Shared::TextFieldDelegate::textFieldShouldFinishEditing(textField, event);
}
bool StorageListController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) {
if (textField->isEditing() && textField->shouldFinishEditing(event)) {
return false;
}
return Shared::TextFieldDelegate::textFieldDidReceiveEvent(textField, event);
}
StorageListParameterController * StorageListController::parameterController() {
return &m_parameterController;
}
int StorageListController::maxNumberOfDisplayableRows() {
return k_maxNumberOfDisplayableRows;
}
FunctionTitleCell * StorageListController::titleCells(int index) {
assert(index >= 0 && index < k_maxNumberOfDisplayableRows);
return &m_functionTitleCells[index];
}
HighlightCell * StorageListController::expressionCells(int index) {
assert(index >= 0 && index < k_maxNumberOfDisplayableRows);
return &m_expressionCells[index];
}
void StorageListController::willDisplayTitleCellAtIndex(HighlightCell * cell, int j) {
TextFieldFunctionTitleCell * titleCell = static_cast<TextFieldFunctionTitleCell *>(cell);
if (!titleCell->isEditing()) {
ExpiringPointer<StorageFunction> function = modelStore()->modelForRecord(modelStore()->recordAtIndex(j));
setFunctionNameInTextField(function, titleCell->textField());
KDColor functionNameColor = function->isActive() ? function->color() : Palette::GreyDark;
titleCell->setColor(functionNameColor);
}
}
void StorageListController::willDisplayExpressionCellAtIndex(HighlightCell * cell, int j) {
Shared::StorageFunctionListController::willDisplayExpressionCellAtIndex(cell, j);
FunctionExpressionCell * myCell = (FunctionExpressionCell *)cell;
ExpiringPointer<StorageFunction> f = modelStore()->modelForRecord(modelStore()->recordAtIndex(j));
KDColor textColor = f->isActive() ? KDColorBlack : Palette::GreyDark;
myCell->setTextColor(textColor);
}
void StorageListController::setFunctionNameInTextField(ExpiringPointer<StorageFunction> function, TextField * textField) {
char bufferName[BufferTextView::k_maxNumberOfChar];
function->nameWithArgument(bufferName, BufferTextView::k_maxNumberOfChar, modelStore()->symbol());
textField->setText(bufferName);
}
}

View File

@@ -0,0 +1,43 @@
#ifndef GRAPH_STORAGE_LIST_CONTROLLER_H
#define GRAPH_STORAGE_LIST_CONTROLLER_H
#include <escher.h>
#include "list_parameter_controller.h"
#include "text_field_function_title_cell.h"
#include "../storage_cartesian_function_store.h"
#include <apps/shared/function_expression_cell.h>
#include <apps/shared/storage_function_list_controller.h>
#include <apps/shared/text_field_delegate.h>
namespace Graph {
class StorageListController : public Shared::StorageFunctionListController, public Shared::TextFieldDelegate {
public:
StorageListController(Responder * parentResponder, ButtonRowController * header, ButtonRowController * footer);
const char * title() override;
void renameSelectedFunction();
// TextFieldDelegate
bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override;
bool textFieldDidAbortEditing(TextField * textField) override;
bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override;
bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override;
private:
constexpr static int k_maxNumberOfDisplayableRows = 5;
Shared::StorageListParameterController * parameterController() override;
int maxNumberOfDisplayableRows() override;
Shared::FunctionTitleCell * titleCells(int index) override;
HighlightCell * expressionCells(int index) override;
void willDisplayTitleCellAtIndex(HighlightCell * cell, int j) override;
void willDisplayExpressionCellAtIndex(HighlightCell * cell, int j) override;
Shared::TextFieldDelegateApp * textFieldDelegateApp() override {
return static_cast<Shared::TextFieldDelegateApp *>(app());
}
void setFunctionNameInTextField(Shared::ExpiringPointer<Shared::StorageFunction> function, TextField * textField);
TextFieldFunctionTitleCell m_functionTitleCells[k_maxNumberOfDisplayableRows];
Shared::FunctionExpressionCell m_expressionCells[k_maxNumberOfDisplayableRows];
ListParameterController m_parameterController;
};
}
#endif

View File

@@ -0,0 +1,65 @@
#include "text_field_function_title_cell.h"
#include "storage_list_controller.h"
#include <assert.h>
namespace Graph {
TextFieldFunctionTitleCell::TextFieldFunctionTitleCell(StorageListController * listController, Orientation orientation, const KDFont * font) :
Shared::FunctionTitleCell(orientation),
Responder(listController),
m_textField(Shared::StorageFunction::k_parenthesedArgumentLength, this, m_textFieldBuffer, m_textFieldBuffer, k_textFieldBufferSize, nullptr, listController, false, font, 0.5f, 0.5f)
{}
void TextFieldFunctionTitleCell::setHighlighted(bool highlight) {
EvenOddCell::setHighlighted(highlight);
m_textField.setBackgroundColor(EvenOddCell::backgroundColor());
}
Responder * TextFieldFunctionTitleCell::responder() {
return isEditing() ? this : nullptr;
}
void TextFieldFunctionTitleCell::setEditing(bool editing) {
app()->setFirstResponder(&m_textField);
const char * previousText = m_textField.text();
m_textField.setEditing(true, false);
m_textField.setText(previousText);
}
bool TextFieldFunctionTitleCell::isEditing() const {
return m_textField.isEditing();
}
void TextFieldFunctionTitleCell::setEven(bool even) {
EvenOddCell::setEven(even);
m_textField.setBackgroundColor(EvenOddCell::backgroundColor());
}
void TextFieldFunctionTitleCell::setColor(KDColor color) {
FunctionTitleCell::setColor(color);
m_textField.setTextColor(color);
}
void TextFieldFunctionTitleCell::setText(const char * title) {
m_textField.setText(title);
}
void TextFieldFunctionTitleCell::layoutSubviews() {
m_textField.setFrame(textFieldFrame());
}
void TextFieldFunctionTitleCell::didBecomeFirstResponder() {
if (isEditing()) {
app()->setFirstResponder(&m_textField);
}
}
KDRect TextFieldFunctionTitleCell::textFieldFrame() const {
KDRect textFrame(0, k_colorIndicatorThickness, bounds().width(), bounds().height() - k_colorIndicatorThickness);
if (m_orientation == Orientation::VerticalIndicator){
textFrame = KDRect(k_colorIndicatorThickness, 0, bounds().width() - k_colorIndicatorThickness-k_separatorThickness, bounds().height()-k_separatorThickness);
}
return textFrame;
}
}

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