Files
Upsilon/apps/code/menu_controller.cpp
Léa Saviot c543145aae [code] Optimized MenuController's script list scrolling speed.
To know if the AddScript row should be displayed, we need to compute the free
space size of the accordion, which is very slow. We thus memoized a bool
indicating if the AddScript row should be displayed.

Change-Id: Ia3891c0fc8df9b0b8bf2a9ad053be6dfc848c970
2017-11-17 14:05:23 +01:00

391 lines
14 KiB
C++

#include "menu_controller.h"
#include "../i18n.h"
#include "../apps_container.h"
#include <assert.h>
#include <escher/metric.h>
#include <ion/events.h>
namespace Code {
MenuController::MenuController(Responder * parentResponder, ScriptStore * scriptStore, ButtonRowController * footer) :
ViewController(parentResponder),
ButtonRowDelegate(nullptr, footer),
m_scriptStore(scriptStore),
m_addNewScriptCell(I18n::Message::AddScript),
m_consoleButton(this, I18n::Message::Console, Invocation([](void * context, void * sender) {
MenuController * menu = (MenuController *)context;
if (menu->consoleController()->loadPythonEnvironment()) {
menu->stackViewController()->push(menu->consoleController());
return;
}
//TODO: Pop up warning message: not enough space to load Python
}, this), KDText::FontSize::Large),
m_selectableTableView(this, this, 0, 1, 0, 0, 0, 0, this, this, false),
m_consoleController(parentResponder, m_scriptStore),
m_scriptParameterController(nullptr, I18n::Message::ScriptOptions, m_scriptStore, this),
m_editorController(this),
m_reloadConsoleWhenBecomingFirstResponder(false),
m_shouldDisplayAddScriptRow(true)
{
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);
}
}
StackViewController * MenuController::stackViewController() {
return static_cast<StackViewController *>(parentResponder()->parentResponder());
}
void MenuController::didBecomeFirstResponder() {
if (m_reloadConsoleWhenBecomingFirstResponder) {
reloadConsole();
}
if (footer()->selectedButton() == 0) {
assert(m_selectableTableView.selectedRow() < 0);
app()->setFirstResponder(&m_consoleButton);
return;
}
if (m_selectableTableView.selectedRow() < 0) {
m_selectableTableView.selectCellAtLocation(0,0);
}
assert(m_selectableTableView.selectedRow() < m_scriptStore->numberOfScripts());
app()->setFirstResponder(&m_selectableTableView);
}
void MenuController::viewWillAppear() {
updateAddScriptRowDisplay();
}
bool MenuController::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::Down) {
m_selectableTableView.deselectTable();
footer()->setSelectedButton(0);
return true;
}
if (event == Ion::Events::Up) {
if (footer()->selectedButton() == 0) {
footer()->setSelectedButton(-1);
m_selectableTableView.selectCellAtLocation(0, numberOfRows()-1);
app()->setFirstResponder(&m_selectableTableView);
return true;
}
}
if (event == Ion::Events::OK || event == Ion::Events::EXE) {
int selectedRow = m_selectableTableView.selectedRow();
int selectedColumn = m_selectableTableView.selectedColumn();
if (selectedRow >= 0 && selectedRow < m_scriptStore->numberOfScripts()) {
if (selectedColumn == 1) {
configureScript();
return true;
}
assert(selectedColumn == 0);
editScript(selectedRow);
return true;
} else if (m_shouldDisplayAddScriptRow
&& selectedColumn == 0
&& selectedRow == m_scriptStore->numberOfScripts())
{
addScript();
return true;
}
}
return false;
}
void MenuController::configureScript() {
setParameteredScript();
stackViewController()->push(&m_scriptParameterController);
}
void MenuController::editScript(int scriptIndex) {
Script script = m_scriptStore->scriptAtIndex(scriptIndex, ScriptStore::EditableZone::Content);
m_editorController.setScript(script);
stackViewController()->push(&m_editorController);
}
void MenuController::setParameteredScript() {
m_scriptParameterController.setScript(m_selectableTableView.selectedRow());
}
void MenuController::addScript() {
assert(m_scriptStore->numberOfScripts() < k_maxNumberOfScripts);
if (m_scriptStore->addNewScript()) {
updateAddScriptRowDisplay();
m_selectableTableView.reloadData();
renameSelectedScript();
return;
}
m_selectableTableView.reloadData();
}
void MenuController::renameSelectedScript() {
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());
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));
}
void MenuController::deleteScriptAtIndex(int i) {
m_scriptStore->deleteScriptAtIndex(i);
updateAddScriptRowDisplay();
m_selectableTableView.reloadData();
}
void MenuController::reloadConsole() {
m_consoleController.unloadPythonEnvironment();
m_reloadConsoleWhenBecomingFirstResponder = false;
}
void MenuController::openConsoleWithScriptAtIndex(int scriptIndex) {
reloadConsole();
if (m_consoleController.loadPythonEnvironment(false)) {
m_consoleController.autoImportScriptAtIndex(scriptIndex);
stackViewController()->push(&m_consoleController);
}
m_reloadConsoleWhenBecomingFirstResponder = true;
}
void MenuController::scriptContentEditionDidFinish(){
reloadConsole();
}
int MenuController::numberOfRows() {
return m_scriptStore->numberOfScripts() + m_shouldDisplayAddScriptRow;
}
void MenuController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) {
if (i == 0 && j < m_scriptStore->numberOfScripts()) {
willDisplayScriptTitleCellForIndex(cell, j);
}
static_cast<EvenOddCell *>(cell)->setEven(j%2 == 0);
cell->setHighlighted(i == selectedColumn() && j == selectedRow());
}
KDCoordinate MenuController::columnWidth(int i) {
switch (i) {
case 0:
return m_selectableTableView.bounds().width()-k_parametersColumnWidth;
case 1:
return k_parametersColumnWidth;
default:
assert(false);
return 0;
}
}
KDCoordinate MenuController::cumulatedWidthFromIndex(int i) {
switch (i) {
case 0:
return 0;
case 1:
return m_selectableTableView.bounds().width()-k_parametersColumnWidth;
case 2:
return m_selectableTableView.bounds().width();
default:
assert(false);
return 0;
}
}
KDCoordinate MenuController::cumulatedHeightFromIndex(int j) {
return Metric::StoreRowHeight * j;
}
int MenuController::indexFromCumulatedWidth(KDCoordinate offsetX) {
if (offsetX <= m_selectableTableView.bounds().width()-k_parametersColumnWidth) {
return 0;
}
if (offsetX <= m_selectableTableView.bounds().width()) {
return 1;
}
else {
return 2;
}
assert(false);
return 0;
}
int MenuController::indexFromCumulatedHeight(KDCoordinate offsetY) {
if (Metric::StoreRowHeight == 0) {
return 0;
}
return (offsetY - 1) / Metric::StoreRowHeight;
}
HighlightCell * MenuController::reusableCell(int index, int type) {
assert(index >= 0);
if (type == ScriptCellType) {
assert(index >=0 && index < k_maxNumberOfDisplayableScriptCells);
return &m_scriptCells[index];
}
if (type == ScriptParameterCellType) {
assert(index >=0 && index < k_maxNumberOfDisplayableScriptCells);
return &m_scriptParameterCells[index];
}
if (type == AddScriptCellType) {
assert(index == 0);
return &m_addNewScriptCell;
}
if(type == EmptyCellType) {
return &m_emptyCell;
}
assert(false);
return nullptr;
}
int MenuController::reusableCellCount(int type) {
if (type == AddScriptCellType) {
return 1;
}
if (type == ScriptCellType || type == ScriptParameterCellType) {
return k_maxNumberOfDisplayableScriptCells;
}
if (type == EmptyCellType) {
return 1;
}
assert(false);
return 0;
}
int MenuController::typeAtLocation(int i, int j) {
assert(i >= 0 && i < numberOfColumns());
assert(j >= 0 && j < numberOfRows());
if (i == 0) {
if (j == numberOfRows()-1 && m_shouldDisplayAddScriptRow) {
return AddScriptCellType;
}
return ScriptCellType;
}
assert(i == 1);
if (j == numberOfRows()-1 && m_shouldDisplayAddScriptRow) {
return EmptyCellType;
}
return ScriptParameterCellType;
}
void MenuController::willDisplayScriptTitleCellForIndex(HighlightCell * cell, int index) {
assert(index < m_scriptStore->numberOfScripts());
EditableTextCell * editableTextCell = static_cast<EvenOddEditableTextCell *>(cell)->editableTextCell();
editableTextCell->textField()->setText(m_scriptStore->scriptAtIndex(index).name());
}
void MenuController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) {
if (selectedRow() == numberOfRows() - 1 && selectedColumn() == 1 && m_shouldDisplayAddScriptRow) {
t->selectCellAtLocation(0, numberOfRows()-1);
}
}
bool MenuController::textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) {
return event == Ion::Events::OK || event == Ion::Events::EXE
|| event == Ion::Events::Down || event == Ion::Events::Up;
}
bool MenuController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) {
if (event == Ion::Events::Right && textField->isEditing()) {
int scriptExtensionLength = strlen(ScriptStore::k_scriptExtension);
if (textField->cursorLocation() > textField->textLength() - scriptExtensionLength - 1) {
return true;
}
}
if (event == Ion::Events::Left && textField->isEditing() && textField->cursorLocation() == 0) {
return true;
}
return false;
}
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 {
newName = text;
}
if (m_scriptStore->renameScriptAtIndex(m_selectableTableView.selectedRow(), newName)) {
updateAddScriptRowDisplay();
textField->setText(newName);
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 && currentRow > 0) {
m_selectableTableView.selectCellAtLocation(m_selectableTableView.selectedColumn(), currentRow - 1);
}
m_selectableTableView.selectedCell()->setHighlighted(true);
reloadConsole();
app()->setFirstResponder(&m_selectableTableView);
static_cast<AppsContainer *>(const_cast<Container *>(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default);
return true;
}
// TODO: add pop up to explain to the user that the name is too long.
return false;
}
bool MenuController::textFieldDidAbortEditing(TextField * textField, const char * text) {
if (strlen(text) <= strlen(ScriptStore::k_scriptExtension)) {
// The previous text was an empty name. Use a numbered default script name.
char numberedDefaultName[k_defaultScriptNameMaxSize];
numberedDefaultScriptName(numberedDefaultName);
m_scriptStore->renameScriptAtIndex(m_selectableTableView.selectedRow(), const_cast<const char *>(numberedDefaultName));
updateAddScriptRowDisplay();
m_selectableTableView.reloadData();
}
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;
}
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->numberOfScripts() < k_maxNumberOfScripts && !m_scriptStore->isFull());
}
}