diff --git a/apps/code/console_controller.cpp b/apps/code/console_controller.cpp index fea2154cb..acad96b7b 100644 --- a/apps/code/console_controller.cpp +++ b/apps/code/console_controller.cpp @@ -1,4 +1,5 @@ #include "console_controller.h" +#include "script.h" #include #include "app.h" #include @@ -14,22 +15,76 @@ ConsoleController::ConsoleController(Responder * parentResponder, ScriptStore * TextFieldDelegate(), m_rowHeight(KDText::charSize(k_fontSize).height()), m_tableView(this, this, 0, 0), - m_editCell(this, this) + m_editCell(this, this), + m_pythonHeap(nullptr), + m_scriptStore(scriptStore) { - m_outputAccumulationBuffer = (char *)malloc(k_outputAccumulationBufferSize); - emptyOutputAccumulationBuffer(); - m_pythonHeap = (char *)malloc(k_pythonHeapSize); - MicroPython::init(m_pythonHeap, m_pythonHeap + k_pythonHeapSize); - MicroPython::registerScriptProvider(scriptStore); } ConsoleController::~ConsoleController() { - MicroPython::deinit(); - free(m_pythonHeap); - free(m_outputAccumulationBuffer); + unloadPythonEnvironment(); +} + +bool ConsoleController::loadPythonEnvironment() { + if(pythonEnvironmentIsLoaded()) { + return true; + } + emptyOutputAccumulationBuffer(); + m_pythonHeap = (char *)malloc(k_pythonHeapSize); + if (m_pythonHeap == nullptr) { + // In DEBUG mode, the assert at the end of malloc would have already failed + // and the program crashed. + return false; + } + MicroPython::init(m_pythonHeap, m_pythonHeap + k_pythonHeapSize); + MicroPython::registerScriptProvider(m_scriptStore); + autoImport(); + return true; +} + +void ConsoleController::unloadPythonEnvironment() { + if (pythonEnvironmentIsLoaded()) { + m_consoleStore.clear(); + MicroPython::deinit(); + free(m_pythonHeap); + m_pythonHeap = nullptr; + } +} + +bool ConsoleController::pythonEnvironmentIsLoaded() { + return (m_pythonHeap != nullptr); +} + +void ConsoleController::autoImport() { + for (int i = 0; i < m_scriptStore->numberOfScripts(); i++) { + autoImportScriptAtIndex(i); + } +} + +void ConsoleController::runAndPrintForCommand(const char * command) { + m_consoleStore.pushCommand(command, strlen(command)); + assert(m_outputAccumulationBuffer[0] == '\0'); + runCode(command); + flushOutputAccumulationBufferToStore(); + m_consoleStore.deleteLastLineIfEmpty(); + m_tableView.reloadData(); + m_editCell.setEditing(true); +} + +void ConsoleController::removeExtensionIfAny(char * name) { + int nameLength = strlen(name); + if (nameLength<4) { + return; + } + if (strcmp(&name[nameLength-3], ".py") == 0) { + name[nameLength-3] = 0; + } } void ConsoleController::viewWillAppear() { + assert(pythonEnvironmentIsLoaded()); + m_tableView.reloadData(); + m_tableView.scrollToCell(0, m_consoleStore.numberOfLines()); m_editCell.setEditing(true); } @@ -100,15 +155,9 @@ bool ConsoleController::textFieldDidReceiveEvent(TextField * textField, Ion::Eve } bool ConsoleController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { - m_consoleStore.pushCommand(text, strlen(text)); - assert(m_outputAccumulationBuffer[0] == '\0'); - runCode(text); - flushOutputAccumulationBufferToStore(); - m_consoleStore.deleteLastLineIfEmpty(); + runAndPrintForCommand(text); textField->setText(""); - m_tableView.reloadData(); m_tableView.scrollToCell(0, m_consoleStore.numberOfLines()); - m_editCell.setEditing(true); return true; } @@ -146,6 +195,28 @@ void ConsoleController::printText(const char * text, size_t length) { } } +void ConsoleController::autoImportScriptAtIndex(int index) { + const char * importCommand1 = "from "; + const char * importCommand2 = " import *"; + int lenImportCommand1 = strlen(importCommand1); + int lenImportCommand2 = strlen(importCommand2); + Script script = m_scriptStore->scriptAtIndex(index); + if (script.autoImport()) { + // Remove the name extension ".py" if there is one. + int scriptOriginalNameLength = strlen(script.name()); + char scriptNewName[scriptOriginalNameLength]; + memcpy(scriptNewName, script.name(), scriptOriginalNameLength + 1); + removeExtensionIfAny(scriptNewName); + int scriptNewNameLength = strlen(scriptNewName); + // Create the command "from scriptName import *". + char command[lenImportCommand1 + scriptNewNameLength + lenImportCommand2 + 1]; + memcpy(command, importCommand1, lenImportCommand1); + memcpy(&command[lenImportCommand1], scriptNewName, scriptNewNameLength); + memcpy(&command[lenImportCommand1 + scriptNewNameLength], importCommand2, lenImportCommand2 + 1); + runAndPrintForCommand(command); + } +} + void ConsoleController::flushOutputAccumulationBufferToStore() { m_consoleStore.pushResult(m_outputAccumulationBuffer, strlen(m_outputAccumulationBuffer)); emptyOutputAccumulationBuffer(); diff --git a/apps/code/console_controller.h b/apps/code/console_controller.h index 65b5a31f9..048dd622d 100644 --- a/apps/code/console_controller.h +++ b/apps/code/console_controller.h @@ -13,6 +13,8 @@ namespace Code { class ConsoleController : public ViewController, public ListViewDataSource, public ScrollViewDataSource, public TextFieldDelegate, public MicroPython::ExecutionEnvironment { public: + static constexpr KDText::FontSize k_fontSize = KDText::FontSize::Large; + ConsoleController(Responder * parentResponder, ScriptStore * scriptStore); ~ConsoleController(); ConsoleController(const ConsoleController& other) = delete; @@ -20,7 +22,13 @@ public: ConsoleController operator=(const ConsoleController& other) = delete; ConsoleController& operator=(ConsoleController&& other) = delete; - static constexpr KDText::FontSize k_fontSize = KDText::FontSize::Large; + bool loadPythonEnvironment(); + void unloadPythonEnvironment(); + bool pythonEnvironmentIsLoaded(); + + void autoImport(); + void runAndPrintForCommand(const char * command); + void removeExtensionIfAny(char * name); // ViewController View * view() override { return &m_tableView; } @@ -53,6 +61,7 @@ private: static constexpr int k_numberOfLineCells = 15; // May change depending on the screen height static constexpr int k_pythonHeapSize = 16384; static constexpr int k_outputAccumulationBufferSize = 40; + void autoImportScriptAtIndex(int index); void flushOutputAccumulationBufferToStore(); void appendTextToOutputAccumulationBuffer(const char * text, size_t length); void emptyOutputAccumulationBuffer(); @@ -63,12 +72,13 @@ private: ConsoleLineCell m_cells[k_numberOfLineCells]; ConsoleEditCell m_editCell; char * m_pythonHeap; - char * m_outputAccumulationBuffer; + char m_outputAccumulationBuffer[k_outputAccumulationBufferSize]; /* The Python machine might call printText several times to print a single * string. We thus use m_outputAccumulationBuffer to store and concatenate the * different strings until a new line char appears in the text. When this * happens, or when m_outputAccumulationBuffer is full, we create a new * ConsoleLine in the ConsoleStore and empty m_outputAccumulationBuffer. */ + ScriptStore * m_scriptStore; }; } diff --git a/apps/code/console_store.cpp b/apps/code/console_store.cpp index 76ce1a02d..ce85de596 100644 --- a/apps/code/console_store.cpp +++ b/apps/code/console_store.cpp @@ -9,6 +9,11 @@ ConsoleStore::ConsoleStore() : { } +void ConsoleStore::clear() { + assert(k_historySize > 0); + m_history[0] = 0; +} + ConsoleLine ConsoleStore::lineAtIndex(int i) const { assert(i >= 0 && i < numberOfLines()); int currentLineIndex = 0; diff --git a/apps/code/console_store.h b/apps/code/console_store.h index 301cb1beb..756e23c68 100644 --- a/apps/code/console_store.h +++ b/apps/code/console_store.h @@ -9,6 +9,7 @@ namespace Code { class ConsoleStore { public: ConsoleStore(); + void clear(); ConsoleLine lineAtIndex(int i) const; int numberOfLines() const; void pushCommand(const char * text, size_t length); diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index 548894788..7ad4a5627 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -1,10 +1,12 @@ #include "editor_controller.h" +#include "script_parameter_controller.h" namespace Code { -EditorController::EditorController() : +EditorController::EditorController(ScriptParameterController * scriptParameterController) : ViewController(nullptr), - m_view(this) + m_view(this), + m_scriptParameterController(scriptParameterController) { } @@ -24,4 +26,8 @@ void EditorController::didBecomeFirstResponder() { app()->setFirstResponder(&m_view); } +void EditorController::viewDidDisappear() { + m_scriptParameterController->scriptContentEditionDidFinish(); +} + } diff --git a/apps/code/editor_controller.h b/apps/code/editor_controller.h index c619c664b..805cd57c2 100644 --- a/apps/code/editor_controller.h +++ b/apps/code/editor_controller.h @@ -6,17 +6,21 @@ namespace Code { +class ScriptParameterController; + class EditorController : public ViewController { public: - EditorController(); + EditorController(ScriptParameterController * scriptParameterController); void setScript(Script script); /* ViewController */ View * view() override { return &m_view; } bool handleEvent(Ion::Events::Event event) override; void didBecomeFirstResponder() override; + void viewDidDisappear() override; private: TextArea m_view; + ScriptParameterController * m_scriptParameterController; }; } diff --git a/apps/code/menu_controller.cpp b/apps/code/menu_controller.cpp index 31169af7a..54b34087e 100644 --- a/apps/code/menu_controller.cpp +++ b/apps/code/menu_controller.cpp @@ -12,7 +12,11 @@ MenuController::MenuController(Responder * parentResponder, ScriptStore * script m_addNewScriptCell(I18n::Message::AddScript), m_consoleButton(this, I18n::Message::Console, Invocation([](void * context, void * sender) { MenuController * menu = (MenuController *)context; - menu->app()->displayModalViewController(menu->consoleController(), 0.5f, 0.5f); + if (menu->consoleController()->loadPythonEnvironment()) { + menu->app()->displayModalViewController(menu->consoleController(), 0.5f, 0.5f); + return; + } + //TODO: Pop up warning message: not enough space to load Python }, this)), m_selectableTableView(this, this, 0, 1, 0, 0, 0, 0, this, nullptr, false), m_consoleController(parentResponder, m_scriptStore), @@ -67,12 +71,18 @@ bool MenuController::handleEvent(Ion::Events::Event event) { } void MenuController::configureScript() { - m_scriptParameterController.setScript(m_selectableTableView.selectedRow()); + setParameteredScript(); stackViewController()->push(&m_scriptParameterController); } +void MenuController::setParameteredScript() { + m_scriptParameterController.setScript(m_selectableTableView.selectedRow()); +} + void MenuController::addScript() { - m_scriptStore->addNewScript(); + if (m_scriptStore->addNewScript()) { + renameScriptAtIndex(m_scriptStore->numberOfScripts()-1); + } m_selectableTableView.reloadData(); } @@ -91,6 +101,10 @@ void MenuController::deleteScriptAtIndex(int i) { m_selectableTableView.reloadData(); } +void MenuController::reloadConsole() { + m_consoleController.unloadPythonEnvironment(); +} + int MenuController::numberOfRows() { return m_scriptStore->numberOfScripts() + 1; //TODO do not add the addScript row if there can be no more scripts stored. @@ -172,6 +186,7 @@ bool MenuController::textFieldDidFinishEditing(TextField * textField, const char m_selectableTableView.selectCellAtLocation(m_selectableTableView.selectedColumn(), currentRow - 1); } m_selectableTableView.selectedCell()->setHighlighted(true); + reloadConsole(); app()->setFirstResponder(&m_selectableTableView); return true; } else { diff --git a/apps/code/menu_controller.h b/apps/code/menu_controller.h index 03d0e8055..3f2e3118a 100644 --- a/apps/code/menu_controller.h +++ b/apps/code/menu_controller.h @@ -18,9 +18,11 @@ public: ConsoleController * consoleController(); StackViewController * stackViewController(); void configureScript(); + void setParameteredScript(); void addScript(); void renameScriptAtIndex(int i); void deleteScriptAtIndex(int i); + void reloadConsole(); /* ViewController */ View * view() override; diff --git a/apps/code/script.cpp b/apps/code/script.cpp index 5f087c244..096d6f231 100644 --- a/apps/code/script.cpp +++ b/apps/code/script.cpp @@ -22,7 +22,7 @@ bool Script::isNull() const { return false; } -bool Script::autoimport() const { +bool Script::autoImport() const { assert(!isNull()); assert(m_marker != nullptr); if (m_marker[0] == AutoImportationMarker) { diff --git a/apps/code/script.h b/apps/code/script.h index 0fe247661..0e10f4db6 100644 --- a/apps/code/script.h +++ b/apps/code/script.h @@ -9,7 +9,7 @@ class Script { public: Script(const char * marker = nullptr, const char * name = nullptr, size_t nameBufferSize = 0, const char * content = nullptr, size_t contentBufferSize = 0); bool isNull() const; - bool autoimport() const; + bool autoImport() const; const char * name() const { assert(!isNull()); diff --git a/apps/code/script_parameter_controller.cpp b/apps/code/script_parameter_controller.cpp index 54d3da581..80de773de 100644 --- a/apps/code/script_parameter_controller.cpp +++ b/apps/code/script_parameter_controller.cpp @@ -12,14 +12,18 @@ ScriptParameterController::ScriptParameterController(Responder * parentResponder m_deleteScript(I18n::Message::Default), m_selectableTableView(this, this, 0, 1, Metric::CommonTopMargin, Metric::CommonRightMargin, Metric::CommonBottomMargin, Metric::CommonLeftMargin, this), + m_editorController(this), m_scriptStore(scriptStore), m_menuController(menuController), + m_autoImport(true), m_currentScriptIndex(-1) { } void ScriptParameterController::setScript(int i){ - m_editorController.setScript(m_scriptStore->scriptAtIndex(i, ScriptStore::EditableZone::Content)); + Script script = (m_scriptStore->scriptAtIndex(i, ScriptStore::EditableZone::Content)); + m_editorController.setScript(script); + m_autoImport = script.autoImport(); m_currentScriptIndex = i; } @@ -28,6 +32,10 @@ void ScriptParameterController::dismissScriptParameterController() { stackController()->pop(); } +void ScriptParameterController::scriptContentEditionDidFinish() { + m_menuController->reloadConsole(); +} + View * ScriptParameterController::view() { return &m_selectableTableView; } @@ -48,11 +56,15 @@ bool ScriptParameterController::handleEvent(Ion::Events::Event event) { m_menuController->renameScriptAtIndex(i); return true; case 2: - //Auto-import TODO + m_scriptStore->switchAutoImportAtIndex(i); + m_autoImport = !m_autoImport; + m_selectableTableView.reloadData(); + m_menuController->reloadConsole(); return true; case 3: dismissScriptParameterController(); m_menuController->deleteScriptAtIndex(i); + m_menuController->reloadConsole(); return true; default: assert(false); @@ -62,6 +74,11 @@ bool ScriptParameterController::handleEvent(Ion::Events::Event event) { return false; } +void ScriptParameterController::viewWillAppear() { + m_selectableTableView.reloadData(); + m_selectableTableView.selectCellAtLocation(0,0); +} + void ScriptParameterController::didBecomeFirstResponder() { selectCellAtLocation(0, 0); app()->setFirstResponder(&m_selectableTableView); @@ -76,7 +93,6 @@ HighlightCell * ScriptParameterController::reusableCell(int index) { assert(index < k_totalNumberOfCell); HighlightCell * cells[] = {&m_editScript, &m_renameScript, &m_autoImportScript, &m_deleteScript}; return cells[index]; - } int ScriptParameterController::reusableCellCount() { @@ -88,7 +104,11 @@ int ScriptParameterController::numberOfRows() { } void ScriptParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { - MessageTableCell * myCell = (MessageTableCell *)cell; + if (cell == &m_autoImportScript) { + SwitchView * switchView = (SwitchView *)m_autoImportScript.accessoryView(); + switchView->setState(m_autoImport); + } + MessageTableCell * myCell = static_cast(cell); I18n::Message labels[k_totalNumberOfCell] = {I18n::Message::EditScript, I18n::Message::RenameScript, I18n::Message::AutoImportScript, I18n::Message::DeleteScript}; myCell->setMessage(labels[index]); } diff --git a/apps/code/script_parameter_controller.h b/apps/code/script_parameter_controller.h index a493daae6..ed12ab623 100644 --- a/apps/code/script_parameter_controller.h +++ b/apps/code/script_parameter_controller.h @@ -15,11 +15,13 @@ public: ScriptParameterController(Responder * parentResponder, I18n::Message title, ScriptStore * scriptStore, MenuController * menuController); void setScript(int i); void dismissScriptParameterController(); + void scriptContentEditionDidFinish(); /* ViewController */ View * view() override; const char * title() override; bool handleEvent(Ion::Events::Event event) override; + void viewWillAppear() override; void didBecomeFirstResponder() override; /* SimpleListViewDataSource */ @@ -41,6 +43,7 @@ private: EditorController m_editorController; ScriptStore * m_scriptStore; MenuController * m_menuController; + bool m_autoImport; int m_currentScriptIndex; }; diff --git a/apps/code/script_store.cpp b/apps/code/script_store.cpp index bd21dedad..2a2f1f27b 100644 --- a/apps/code/script_store.cpp +++ b/apps/code/script_store.cpp @@ -108,6 +108,20 @@ bool ScriptStore::renameScriptAtIndex(int index, const char * newName) { return m_accordion.replaceBufferAtIndex(accordionIndex, newName); } +void ScriptStore::switchAutoImportAtIndex(int index) { + assert(index >= 0 && index < numberOfScripts()); + Script script = scriptAtIndex(index); + bool autoImportation = script.autoImport(); + int accordionIndex = accordionIndexOfMarkersOfScriptAtIndex(index); + if (autoImportation) { + const char autoImportationString[2] = {Script::NoAutoImportationMarker, 0}; + m_accordion.replaceBufferAtIndex(accordionIndex, autoImportationString); + return; + } + const char autoImportationString[2] = {Script::AutoImportationMarker, 0}; + m_accordion.replaceBufferAtIndex(accordionIndex, autoImportationString); +} + void ScriptStore::deleteScriptAtIndex(int index) { assert (index >= 0 && index < numberOfScripts()); int accordionIndex = accordionIndexOfContentOfScriptAtIndex(index); @@ -179,7 +193,7 @@ bool ScriptStore::copyFactorialScriptOnFreeSpace() { } bool ScriptStore::copyEmptyScriptOnFreeSpace() { - const char script[] = "\0"; + const char script[] = " "; return m_accordion.appendBuffer(script); } diff --git a/apps/code/script_store.h b/apps/code/script_store.h index b08b28dc0..ba87b1d8f 100644 --- a/apps/code/script_store.h +++ b/apps/code/script_store.h @@ -27,6 +27,7 @@ public: int numberOfScripts(); bool addNewScript(DefaultScript defaultScript = DefaultScript::Empty); bool renameScriptAtIndex(int index, const char * newName); + void switchAutoImportAtIndex(int index); void deleteScriptAtIndex(int index); void deleteAllScripts();