Numworks Epsilon  1.4.1
Graphing Calculator Operating System
console_controller.cpp
Go to the documentation of this file.
1 #include "console_controller.h"
2 #include "app.h"
3 #include "script.h"
5 #include <apps/i18n.h>
6 #include <assert.h>
7 #include <escher/metric.h>
8 #include "../apps_container.h"
9 
10 extern "C" {
11 #include <stdlib.h>
12 }
13 
14 namespace Code {
15 
16 static const char * sStandardPromptText = ">>> ";
17 
19 #if EPSILON_GETOPT
20  , bool lockOnConsole
21 #endif
22  ) :
23  ViewController(parentResponder),
26  MicroPython::ExecutionEnvironment(),
27  m_rowHeight(KDText::charSize(k_fontSize).height()),
28  m_importScriptsWhenViewAppears(false),
29  m_selectableTableView(this, this, this, this),
30  m_editCell(this, this),
31  m_pythonHeap(nullptr),
32  m_scriptStore(scriptStore),
33  m_sandboxController(this),
34  m_inputRunLoopActive(false)
35 #if EPSILON_GETOPT
36  , m_locked(lockOnConsole)
37 #endif
38 {
40  m_selectableTableView.setBackgroundColor(KDColorWhite);
41  m_editCell.setPrompt(sStandardPromptText);
42  for (int i = 0; i < k_numberOfLineCells; i++) {
43  m_cells[i].setParentResponder(&m_selectableTableView);
44  }
45 }
46 
49 }
50 
51 bool ConsoleController::loadPythonEnvironment(bool autoImportScripts) {
53  return true;
54  }
55  emptyOutputAccumulationBuffer();
56  m_pythonHeap = (char *)malloc(k_pythonHeapSize);
57  if (m_pythonHeap == nullptr) {
58  // In DEBUG mode, the assert at the end of malloc would have already failed
59  // and the program crashed.
60  return false;
61  }
62  MicroPython::init(m_pythonHeap, m_pythonHeap + k_pythonHeapSize);
64  m_importScriptsWhenViewAppears = autoImportScripts;
65  return true;
66 }
67 
70  m_consoleStore.startNewSession();
72  free(m_pythonHeap);
73  m_pythonHeap = nullptr;
74  }
75 }
76 
78  return (m_pythonHeap != nullptr);
79 }
80 
82  for (int i = 0; i < m_scriptStore->numberOfScripts(); i++) {
83  autoImportScript(m_scriptStore->scriptAtIndex(i));
84  }
85 }
86 
87 void ConsoleController::runAndPrintForCommand(const char * command) {
88  m_consoleStore.pushCommand(command, strlen(command));
89  assert(m_outputAccumulationBuffer[0] == '\0');
90 
91  runCode(command);
92  flushOutputAccumulationBufferToStore();
93  m_consoleStore.deleteLastLineIfEmpty();
94 }
95 
96 const char * ConsoleController::inputText(const char * prompt) {
97  AppsContainer * a = (AppsContainer *)(app()->container());
98  m_inputRunLoopActive = true;
99 
100  m_selectableTableView.reloadData();
101  m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines());
102  m_editCell.setPrompt(prompt);
103  m_editCell.setText("");
104 
105  a->redrawWindow();
106  a->runWhile([](void * a){
107  ConsoleController * c = static_cast<ConsoleController *>(a);
108  return c->inputRunLoopActive();
109  }, this);
110 
111  flushOutputAccumulationBufferToStore();
112  m_consoleStore.deleteLastLineIfEmpty();
113  m_editCell.setPrompt(sStandardPromptText);
114 
115  return m_editCell.text();
116 }
117 
120  m_sandboxIsDisplayed = false;
121  if (m_importScriptsWhenViewAppears) {
122  m_importScriptsWhenViewAppears = false;
123  autoImport();
124  }
125  m_selectableTableView.reloadData();
126  m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines());
127  m_editCell.setEditing(true);
128  m_editCell.setText("");
129 }
130 
132  app()->setFirstResponder(&m_editCell);
133 }
134 
136  if ((event == Ion::Events::Home || event == Ion::Events::Up) && inputRunLoopActive()) {
137  askInputRunLoopTermination();
138  // We need to return true here because we want to actually exit from the
139  // input run loop, which requires ending a dispatchEvent cycle.
140  return true;
141  }
142  if (event == Ion::Events::Up) {
143  if (m_consoleStore.numberOfLines() > 0 && m_selectableTableView.selectedRow() == m_consoleStore.numberOfLines()) {
144  m_editCell.setEditing(false);
145  m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines()-1);
146  return true;
147  }
148  } else if (event == Ion::Events::OK || event == Ion::Events::EXE) {
149  if (m_consoleStore.numberOfLines() > 0 && m_selectableTableView.selectedRow() < m_consoleStore.numberOfLines()) {
150  const char * text = m_consoleStore.lineAtIndex(m_selectableTableView.selectedRow()).text();
151  m_editCell.setEditing(true);
152  m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines());
153  app()->setFirstResponder(&m_editCell);
154  return m_editCell.insertText(text);
155  }
156  } else if (event == Ion::Events::Copy) {
157  return copyCurrentLineToClipboard();
158  } else if (event == Ion::Events::Clear) {
159  m_selectableTableView.deselectTable();
160  m_consoleStore.clear();
161  m_selectableTableView.reloadData();
162  m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines());
163  return true;
164  } else if (event == Ion::Events::Backspace) {
165  int selectedRow = m_selectableTableView.selectedRow();
166  assert(selectedRow >= 0 && selectedRow < m_consoleStore.numberOfLines());
167  m_selectableTableView.deselectTable();
168  int firstDeletedLineIndex = m_consoleStore.deleteCommandAndResultsAtIndex(selectedRow);
169  m_selectableTableView.reloadData();
170  m_selectableTableView.selectCellAtLocation(0, firstDeletedLineIndex);
171  return true;
172  }
173 #if EPSILON_GETOPT
174  if (m_locked && (event == Ion::Events::Home || event == Ion::Events::Back)) {
175  return true;
176  }
177 #endif
178  return false;
179 }
180 
182  return m_consoleStore.numberOfLines()+1;
183 }
184 
186  return m_rowHeight;
187 }
188 
190  return j*rowHeight(0);
191 }
192 
194  return offsetY/rowHeight(0);
195 }
196 
198  assert(index >= 0);
199  if (type == LineCellType) {
200  assert(index < k_numberOfLineCells);
201  return m_cells+index;
202  } else {
203  assert(type == EditCellType);
204  assert(index == 0);
205  return &m_editCell;
206  }
207 }
208 
210  if (type == LineCellType) {
211  return k_numberOfLineCells;
212  } else {
213  return 1;
214  }
215 }
216 
218  assert(i == 0);
219  assert(j >= 0);
220  if (j < m_consoleStore.numberOfLines()) {
221  return LineCellType;
222  } else {
223  assert(j == m_consoleStore.numberOfLines());
224  return EditCellType;
225  }
226 }
227 
229  assert(i == 0);
230  if (j < m_consoleStore.numberOfLines()) {
231  static_cast<ConsoleLineCell *>(cell)->setLine(m_consoleStore.lineAtIndex(j));
232  }
233 }
234 
235 void ConsoleController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) {
236  if (t->selectedRow() == m_consoleStore.numberOfLines()) {
237  m_editCell.setEditing(true);
238  return;
239  }
240  if (t->selectedRow()>-1) {
241  if (previousSelectedCellY > -1 && previousSelectedCellY < m_consoleStore.numberOfLines()) {
242  // Reset the scroll of the previous cell
243  ConsoleLineCell * previousCell = (ConsoleLineCell *)(t->cellAtLocation(previousSelectedCellX, previousSelectedCellY));
244  previousCell->reloadCell();
245  }
246  ConsoleLineCell * selectedCell = (ConsoleLineCell *)(t->selectedCell());
247  selectedCell->reloadCell();
248  }
249 }
250 
252  assert(textField->isEditing());
253  return (textField->draftTextLength() > 0
254  && (event == Ion::Events::OK || event == Ion::Events::EXE));
255 }
256 
258  if (event == Ion::Events::Var) {
259  if (!textField->isEditing()) {
260  textField->setEditing(true);
261  }
262  }
263  return static_cast<App *>(textField->app())->textInputDidReceiveEvent(textField, event);
264 }
265 
266 bool ConsoleController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) {
267  if (inputRunLoopActive()) {
268  askInputRunLoopTermination();
269  return false;
270  }
271  runAndPrintForCommand(text);
272  if (m_sandboxIsDisplayed) {
273  return true;
274  }
275  m_selectableTableView.reloadData();
276  m_editCell.setEditing(true);
277  textField->setText("");
278  m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines());
279  return true;
280 }
281 
282 bool ConsoleController::textFieldDidAbortEditing(TextField * textField, const char * text) {
283  if (inputRunLoopActive()) {
284  askInputRunLoopTermination();
285  } else {
286 #if EPSILON_GETOPT
287  if (!m_locked) {
288 #endif
289  stackViewController()->pop();
290 #if EPSILON_GETOPT
291  } else {
292  textField->setEditing(true);
293  }
294 #endif
295  }
296  return true;
297 }
298 
300  Code::App * codeApp = static_cast<Code::App *>(app());
301  return codeApp->pythonToolbox();
302 }
303 
305  if (m_sandboxIsDisplayed) {
306  return;
307  }
308  m_sandboxIsDisplayed = true;
309  stackViewController()->push(&m_sandboxController);
310 }
311 
312 /* printText is called by the Python machine.
313  * The text argument is not always null-terminated. */
314 void ConsoleController::printText(const char * text, size_t length) {
315  size_t textCutIndex = firstNewLineCharIndex(text, length);
316  // If there is no new line in text, just append it to the output accumulation
317  // buffer.
318  if (textCutIndex >= length) {
319  appendTextToOutputAccumulationBuffer(text, length);
320  return;
321  }
322  // If there is a new line in the middle of the text, we have to store at least
323  // two new console lines in the console store.
324  if (textCutIndex < length - 1) {
325  printText(text, textCutIndex + 1);
326  printText(&text[textCutIndex+1], length - (textCutIndex + 1));
327  return;
328  }
329  // If there is a new line at the end of the text, we have to store the line in
330  // the console store.
331  if (textCutIndex == length - 1) {
332  appendTextToOutputAccumulationBuffer(text, length-1);
333  flushOutputAccumulationBufferToStore();
334  }
335 }
336 
337 void ConsoleController::autoImportScript(Script script, bool force) {
338  if (script.importationStatus() || force) {
339  // Create the command "from scriptName import *".
340  char command[k_maxImportCommandSize];
341  size_t currentChar = strlcpy(command, k_importCommand1, strlen(k_importCommand1)+1);
342  const char * scriptName = script.name();
343  currentChar += strlcpy(command+currentChar, scriptName, strlen(scriptName)+1);
344  // Remove the name extension ".py"
345  currentChar -= strlen(ScriptStore::k_scriptExtension);
346  currentChar += strlcpy(command+currentChar, k_importCommand2, strlen(k_importCommand2)+1);
347  runAndPrintForCommand(command);
348  }
349  if (force) {
350  m_selectableTableView.reloadData();
351  m_selectableTableView.selectCellAtLocation(0, m_consoleStore.numberOfLines());
352  m_editCell.setEditing(true);
353  m_editCell.setText("");
354  }
355 }
356 
357 void ConsoleController::flushOutputAccumulationBufferToStore() {
358  m_consoleStore.pushResult(m_outputAccumulationBuffer, strlen(m_outputAccumulationBuffer));
359  emptyOutputAccumulationBuffer();
360 }
361 
362 void ConsoleController::appendTextToOutputAccumulationBuffer(const char * text, size_t length) {
363  int endOfAccumulatedText = strlen(m_outputAccumulationBuffer);
364  int spaceLeft = k_outputAccumulationBufferSize - endOfAccumulatedText;
365  if (spaceLeft > (int)length) {
366  memcpy(&m_outputAccumulationBuffer[endOfAccumulatedText], text, length);
367  return;
368  }
369  memcpy(&m_outputAccumulationBuffer[endOfAccumulatedText], text, spaceLeft-1);
370  flushOutputAccumulationBufferToStore();
371  appendTextToOutputAccumulationBuffer(&text[spaceLeft-1], length - (spaceLeft - 1));
372 }
373 
374 void ConsoleController::emptyOutputAccumulationBuffer() {
375  for (int i = 0; i < k_outputAccumulationBufferSize; i++) {
376  m_outputAccumulationBuffer[i] = 0;
377  }
378 }
379 
380 size_t ConsoleController::firstNewLineCharIndex(const char * text, size_t length) {
381  size_t index = 0;
382  while (index < length) {
383  if (text[index] == '\n') {
384  return index;
385  }
386  index++;
387  }
388  return index;
389 }
390 
391 StackViewController * ConsoleController::stackViewController() {
392  return static_cast<StackViewController *>(parentResponder());
393 }
394 
395 bool ConsoleController::copyCurrentLineToClipboard() {
396  int row = m_selectableTableView.selectedRow();
397  if (row < m_consoleStore.numberOfLines()) {
398  Clipboard::sharedClipboard()->store(m_consoleStore.lineAtIndex(row).text());
399  return true;
400  }
401  return false;
402 }
403 
404 }
void tableViewDidChangeSelection(SelectableTableView *t, int previousSelectedCellX, int previousSelectedCellY) override
void setText(const char *text)
int deleteCommandAndResultsAtIndex(int index)
bool isEditing() const
Definition: text_field.cpp:175
void willDisplayCellAtLocation(HighlightCell *cell, int i, int j) override
constexpr Event Var
Definition: events.h:74
bool textFieldDidReceiveEvent(TextField *textField, Ion::Events::Event event) override
Definition: app.h:13
void runCode(const char *)
Definition: port.cpp:34
int numberOfLines() const
HighlightCell * reusableCell(int index, int type) override
bool insertText(const char *text)
#define assert(e)
Definition: assert.h:9
void setBackgroundColor(KDColor c)
Definition: scroll_view.h:32
int typeAtLocation(int i, int j) override
void init(void *heapStart, void *heapEnd)
Definition: port.cpp:92
HighlightCell * selectedCell()
KDCoordinate cumulatedHeightFromIndex(int j) override
void setText(const char *text)
Definition: text_field.cpp:184
static constexpr KDCoordinate CommonRightMargin
Definition: metric.h:9
void setPrompt(const char *prompt)
constexpr Event EXE
Definition: events.h:114
int16_t KDCoordinate
Definition: coordinate.h:6
void push(ViewController *vc, KDColor textColor=Palette::SubTab, KDColor backgroundColor=KDColorWhite, KDColor separatorColor=Palette::GreyBright)
Responder * parentResponder() const
Definition: responder.cpp:12
constexpr Event Home
Definition: events.h:68
void autoImportScript(Script script, bool force=false)
constexpr Event Up
Definition: events.h:62
size_t strlcpy(char *dst, const char *src, size_t len)
Definition: strlcpy.c:3
bool textFieldDidAbortEditing(TextField *textField, const char *text) override
bool selectCellAtLocation(int i, int j, bool setFirstResponder=true)
LIBA_BEGIN_DECLS void free(void *ptr)
Definition: malloc.c:33
const char * inputText(const char *prompt) override
HighlightCell * cellAtLocation(int i, int j)
Definition: table_view.cpp:30
Definition: text.h:8
Toolbox * toolboxForTextInput(TextInput *textInput) override
virtual void setEditing(bool isEditing, bool reinitDraftBuffer=true)
Definition: text_field.cpp:196
constexpr Event Back
Definition: events.h:66
bool textFieldDidFinishEditing(TextField *textField, const char *text, Ion::Events::Event event) override
void printText(const char *text, size_t length) override
void store(const char *storedText)
Definition: clipboard.cpp:9
void reloadData(bool setFirstResponder=true)
c(generic_all_nodes)
Definition: app.cpp:7
constexpr KDColor KDColorWhite
Definition: color.h:42
size_t strlen(const char *s)
Definition: strlen.c:3
const char * text() const
Definition: console_line.h:19
PythonToolbox * pythonToolbox()
Definition: app.h:39
void setParentResponder(Responder *responder)
Definition: responder.cpp:16
Script scriptAtIndex(int index)
Definition: script_store.h:21
void registerScriptProvider(ScriptProvider *s)
Definition: port.cpp:109
#define false
Definition: stdbool.h:9
bool handleEvent(Ion::Events::Event event) override
ConsoleController(Responder *parentResponder, ScriptStore *scriptStore)
const char * name() const
Definition: storage.h:41
void * malloc(size_t size)
Definition: malloc.c:44
void runWhile(bool(*callback)(void *ctx), void *ctx)
Definition: run_loop.cpp:24
bool textFieldShouldFinishEditing(TextField *textField, Ion::Events::Event event) override
void setMargins(KDCoordinate top, KDCoordinate right, KDCoordinate bottom, KDCoordinate left)
Definition: scroll_view.h:22
constexpr Event Copy
Definition: events.h:123
bool importationStatus() const
Definition: script.cpp:10
const Container * container() const
Definition: app.cpp:102
void deinit()
Definition: port.cpp:105
size_t draftTextLength() const
Definition: text_field.cpp:179
KDCoordinate rowHeight(int j) override
void setFirstResponder(Responder *responder)
Definition: app.cpp:62
void runAndPrintForCommand(const char *command)
void pushCommand(const char *text, size_t length)
void setEditing(bool isEditing, bool reinitDraftBuffer=false)
void pushResult(const char *text, size_t length)
static Clipboard * sharedClipboard()
Definition: clipboard.cpp:5
App * app()
Definition: responder.cpp:77
constexpr Event OK
Definition: events.h:65
bool loadPythonEnvironment(bool autoImportScripts=true)
void reloadCell() override
constexpr Event Clear
Definition: events.h:125
static constexpr KDCoordinate TitleBarExternHorizontalMargin
Definition: metric.h:13
ConsoleLine lineAtIndex(int i) const
const char * text() const
void * memcpy(void *dst, const void *src, size_t n)
Definition: memcpy.c:3
static constexpr char k_scriptExtension[]
Definition: script_store.h:16
int reusableCellCount(int type) override
constexpr Event Backspace
Definition: events.h:76
void didBecomeFirstResponder() override
int indexFromCumulatedHeight(KDCoordinate offsetY) override