mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-03-18 21:30:38 +01:00
Merge branch 'master' into python_turtle
This commit is contained in:
1
Makefile
1
Makefile
@@ -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)
|
||||
|
||||
@@ -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;)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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\
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, _"
|
||||
|
||||
@@ -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, _"
|
||||
|
||||
@@ -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, _"
|
||||
|
||||
@@ -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, _"
|
||||
|
||||
@@ -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, _"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
40
apps/code/script_name_cell.cpp
Normal file
40
apps/code/script_name_cell.cpp
Normal 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()));
|
||||
}
|
||||
|
||||
}
|
||||
57
apps/code/script_name_cell.h
Normal file
57
apps/code/script_name_cell.h
Normal 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
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/,\
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
31
apps/graph/list/list_parameter_controller.cpp
Normal file
31
apps/graph/list/list_parameter_controller.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
31
apps/graph/list/list_parameter_controller.h
Normal file
31
apps/graph/list/list_parameter_controller.h
Normal 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
|
||||
171
apps/graph/list/storage_list_controller.cpp
Normal file
171
apps/graph/list/storage_list_controller.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
43
apps/graph/list/storage_list_controller.h
Normal file
43
apps/graph/list/storage_list_controller.h
Normal 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
|
||||
65
apps/graph/list/text_field_function_title_cell.cpp
Normal file
65
apps/graph/list/text_field_function_title_cell.cpp
Normal 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
Reference in New Issue
Block a user