[apps/escher/toolbox] handleEventWithText for all responders.

This makes the code in the various toolbox more generic. The arguments
of the text to insert are each replaced by a Ion::Charset::Empty.
These Empty chars are used to create layouts in ExpressionLayoutFields,
and to compute the position of the cursor in other fields, before being
removed.

Change-Id: Ie81c1e394b06fef2ab801ccff919d6550f70ec30
This commit is contained in:
Léa Saviot
2018-04-23 14:57:46 +02:00
parent 643aa89f74
commit 13ae1d2545
23 changed files with 153 additions and 152 deletions

View File

@@ -294,8 +294,7 @@ bool PythonToolbox::selectLeaf(ToolboxMessageTree * selectedMessageTree) {
int strippedEditedTextMaxLength = strlen(editedText)+1;
char strippedEditedText[strippedEditedTextMaxLength];
Shared::ToolboxHelpers::TextToInsertForCommandMessage(node->insertedText(), strippedEditedText, strippedEditedTextMaxLength);
TextInput * textInput = static_cast<TextInput *>(sender());
textInput->handleEventWithText(strippedEditedText, true);
sender()->handleEventWithText(strippedEditedText, true);
app()->dismissModalViewController();
return true;
}

View File

@@ -10,10 +10,7 @@ namespace Code {
class PythonToolbox : public Toolbox {
public:
typedef void (*Action)(void * sender, const char * text);
PythonToolbox();
// StackViewController
bool handleEvent(Ion::Events::Event event) override;
protected:
KDCoordinate rowHeight(int j) override;

View File

@@ -108,67 +108,21 @@ const ToolboxMessageTree toolboxModel = ToolboxMessageTree(I18n::Message::Toolbo
#endif
MathToolbox::MathToolbox() :
Toolbox(nullptr, I18n::translate(rootModel()->label())),
m_action(actionForTextInput)
Toolbox(nullptr, I18n::translate(rootModel()->label()))
{
}
void MathToolbox::setSenderAndAction(Responder * sender, Action action) {
setSender(sender);
m_action = action;
}
void MathToolbox::actionForExpressionLayoutField(void * sender, const char * text, bool removeArguments) {
ExpressionLayoutField * expressionLayoutEditorSender = static_cast<ExpressionLayoutField *>(sender);
Expression * resultExpression = nullptr;
if (removeArguments) {
// Replace the arguments with Empty chars.
int textToInsertMaxLength = strlen(text);
char textToInsert[textToInsertMaxLength];
Shared::ToolboxHelpers::TextToParseIntoLayoutForCommandText(text, textToInsert, textToInsertMaxLength);
// Create the layout
resultExpression = Expression::parse(textToInsert);
} else {
resultExpression = Expression::parse(text);
}
if (resultExpression == nullptr) {
return;
}
ExpressionLayout * resultLayout = resultExpression->createLayout();
// Find the pointed layout.
ExpressionLayout * pointedLayout = nullptr;
if (resultLayout->isHorizontal()) {
// If the layout is horizontal, pick the first open parenthesis.
// For now, all horizontal layouts in MathToolbox have parentheses.
for (int i = 0; i < resultLayout->numberOfChildren(); i++) {
if (resultLayout->editableChild(i)->isLeftParenthesis()) {
pointedLayout = resultLayout->editableChild(i);
break;
}
}
}
// Insert the layout. If pointedLayout is nullptr, the cursor will be on the
// right of the inserted layout.
expressionLayoutEditorSender->insertLayoutAtCursor(resultLayout, pointedLayout);
}
void MathToolbox::actionForTextInput(void * sender, const char * text, bool removeArguments) {
TextInput * textInputSender = static_cast<TextInput *>(sender);
if (removeArguments) {
int maxTextToInsertLength = strlen(text);
char textToInsert[maxTextToInsertLength];
// Translate the message and remove the arguments.
Shared::ToolboxHelpers::TextToInsertForCommandText(text, textToInsert, maxTextToInsertLength);
textInputSender->handleEventWithText(textToInsert);
} else {
textInputSender->handleEventWithText(text);
}
}
bool MathToolbox::selectLeaf(ToolboxMessageTree * selectedMessageTree) {
ToolboxMessageTree * messageTree = selectedMessageTree;
m_selectableTableView.deselectTable();
m_action(sender(), I18n::translate(messageTree->insertedText()), true);
// Translate the message and remove the arguments
const char * text = I18n::translate(messageTree->insertedText());
int maxTextToInsertLength = strlen(text) + 1;
char textToInsert[maxTextToInsertLength];
Shared::ToolboxHelpers::TextToInsertForCommandText(text, textToInsert, maxTextToInsertLength, true);
sender()->handleEventWithText(textToInsert);
app()->dismissModalViewController();
return true;
}

View File

@@ -7,11 +7,7 @@
class MathToolbox : public Toolbox {
public:
typedef void (*Action)(void * sender, const char * text, bool removeArguments);
MathToolbox();
void setSenderAndAction(Responder * sender, Action action);
static void actionForExpressionLayoutField(void * sender, const char * text, bool removeArguments = true);
static void actionForTextInput(void * sender, const char * text, bool removeArguments = true);
protected:
bool selectLeaf(ToolboxMessageTree * selectedMessageTree) override;
const ToolboxMessageTree * rootModel() override;
@@ -19,7 +15,6 @@ protected:
MessageTableCellWithChevron* nodeCellAtIndex(int index) override;
int maxNumberOfDisplayedRows() override;
constexpr static int k_maxNumberOfDisplayedRows = 6; // = 240/40
Action m_action;
private:
MessageTableCellWithMessage m_leafCells[k_maxNumberOfDisplayedRows];
MessageTableCellWithChevron m_nodeCells[k_maxNumberOfDisplayedRows];

View File

@@ -24,15 +24,11 @@ const char * ListController::title() {
}
Toolbox * ListController::toolboxForTextInput(TextInput * textInput) {
setToolboxExtraCells();
m_sequenceToolbox.setSenderAndAction(textInput, MathToolbox::actionForTextInput);
return &m_sequenceToolbox;
return toolbox(textInput);
}
Toolbox * ListController::toolboxForExpressionLayoutField(ExpressionLayoutField * expressionLayoutField) {
setToolboxExtraCells();
m_sequenceToolbox.setSenderAndAction(expressionLayoutField, MathToolbox::actionForExpressionLayoutField);
return &m_sequenceToolbox;
return toolbox(expressionLayoutField);
}
TextFieldDelegateApp * ListController::textFieldDelegateApp() {
@@ -87,6 +83,20 @@ void ListController::selectPreviousNewSequenceCell() {
}
}
Toolbox * ListController::toolbox(Responder * sender) {
// Set extra cells
int recurrenceDepth = -1;
int sequenceDefinition = sequenceDefinitionForRow(selectedRow());
Sequence * sequence = m_sequenceStore->functionAtIndex(functionIndexForRow(selectedRow()));
if (sequenceDefinition == 0) {
recurrenceDepth = sequence->numberOfElements()-1;
}
m_sequenceToolbox.setExtraCells(sequence->name(), recurrenceDepth);
// Set sender
m_sequenceToolbox.setSender(sender);
return &m_sequenceToolbox;
}
void ListController::editExpression(Sequence * sequence, int sequenceDefinition, Ion::Events::Event event) {
char * initialText = nullptr;
char initialTextContent[TextField::maxBufferSize()];
@@ -309,14 +319,4 @@ void ListController::unloadView(View * view) {
Shared::ListController::unloadView(view);
}
void ListController::setToolboxExtraCells() {
int recurrenceDepth = -1;
int sequenceDefinition = sequenceDefinitionForRow(selectedRow());
Sequence * sequence = m_sequenceStore->functionAtIndex(functionIndexForRow(selectedRow()));
if (sequenceDefinition == 0) {
recurrenceDepth = sequence->numberOfElements()-1;
}
m_sequenceToolbox.setExtraCells(sequence->name(), recurrenceDepth);
}
}

View File

@@ -26,6 +26,7 @@ public:
Toolbox * toolboxForExpressionLayoutField(ExpressionLayoutField * expressionLayoutField) override;
void selectPreviousNewSequenceCell();
private:
Toolbox * toolbox(Responder * sender);
Shared::TextFieldDelegateApp * textFieldDelegateApp() override;
Shared::ExpressionFieldDelegateApp * expressionFieldDelegateApp() override;
void editExpression(Sequence * sequence, int sequenceDefinitionIndex, Ion::Events::Event event);
@@ -44,7 +45,6 @@ private:
void reinitExpression(Shared::Function * function) override;
View * loadView() override;
void unloadView(View * view) override;
void setToolboxExtraCells();
static constexpr KDCoordinate k_emptySubRowHeight = 30;
constexpr static int k_maxNumberOfRows = 3*MaxNumberOfSequences;
SequenceStore * m_sequenceStore;

View File

@@ -114,26 +114,7 @@ bool SequenceToolbox::selectAddedCell(int selectedRow){
int bufferSize = 10;
char buffer[bufferSize];
m_addedCellLayout[selectedRow]->writeTextInBuffer(buffer, bufferSize);
if (m_action == MathToolbox::actionForTextInput) {
/* DIRTY. The symbols are layouted using a Subscript VerticalOffsetLayout,
* which serializes into "_{}", but we want parentheses for text fields. We
* thus need to remove any underscores, and changes brackets into
* parentheses. */
for (int i = 0; i < bufferSize; i++) {
if (buffer[i] == '{') {
buffer[i] = '(';
}
if (buffer[i] == '}') {
buffer[i] = ')';
}
if (buffer[i] == '_') {
memmove(&buffer[i], &buffer[i+1], bufferSize - (i+1) + 1);
bufferSize--;
i--;
}
}
}
m_action(sender(), buffer, false);
sender()->handleEventWithText(buffer);
app()->dismissModalViewController();
return true;
}

View File

@@ -49,7 +49,7 @@ bool ExpressionFieldDelegateApp::expressionLayoutFieldDidReceiveEvent(Expression
Toolbox * ExpressionFieldDelegateApp::toolboxForExpressionLayoutField(ExpressionLayoutField * expressionLayoutField) {
Toolbox * toolbox = container()->mathToolbox();
static_cast<MathToolbox *>(toolbox)->setSenderAndAction(expressionLayoutField, MathToolbox::actionForExpressionLayoutField);
toolbox->setSender(expressionLayoutField);
return toolbox;
}

View File

@@ -109,7 +109,7 @@ bool TextFieldDelegateApp::textFieldDidReceiveEvent(TextField * textField, Ion::
Toolbox * TextFieldDelegateApp::toolboxForTextInput(TextInput * textInput) {
Toolbox * toolbox = container()->mathToolbox();
static_cast<MathToolbox *>(toolbox)->setSenderAndAction(textInput, MathToolbox::actionForTextInput);
toolbox->setSender(textInput);
return toolbox;
}

View File

@@ -8,7 +8,8 @@ namespace Shared {
namespace ToolboxHelpers {
int CursorIndexInCommandText(const char * text) {
for (size_t i = 0; i < strlen(text); i++) {
size_t textLength = strlen(text);
for (size_t i = 0; i < textLength; i++) {
if (text[i] == '(' || text[i] == '\'') {
return i + 1;
}
@@ -16,7 +17,7 @@ int CursorIndexInCommandText(const char * text) {
return i;
}
}
return strlen(text);
return textLength;
}
void TextToInsertForCommandMessage(I18n::Message message, char * buffer, int bufferSize, bool replaceArgsWithEmptyChar) {
@@ -65,13 +66,5 @@ void TextToInsertForCommandText(const char * command, char * buffer, int bufferS
buffer[currentNewTextIndex] = 0;
}
void TextToParseIntoLayoutForCommandMessage(I18n::Message message, char * buffer, int bufferSize) {
TextToParseIntoLayoutForCommandText(I18n::translate(message), buffer, bufferSize);
}
void TextToParseIntoLayoutForCommandText(const char * command, char * buffer, int bufferSize) {
TextToInsertForCommandText(command, buffer, bufferSize, true);
}
}
}

View File

@@ -17,9 +17,6 @@ void TextToInsertForCommandMessage(I18n::Message message, char * buffer, int buf
void TextToInsertForCommandText(const char * command, char * buffer, int bufferSize, bool replaceArgsWithEmptyChar = false);
/* Removes the arguments from a command:
* - Removes text between parentheses or brackets, except commas */
void TextToParseIntoLayoutForCommandMessage(I18n::Message message, char * buffer, int bufferSize);
void TextToParseIntoLayoutForCommandText(const char * command, char * buffer, int bufferSize);
/* Removes the arguments from a command and replaces them with empty chars. */
}
}

View File

@@ -7,7 +7,7 @@
#include <escher/text_field_delegate.h>
#include <poincare/expression_layout.h>
class ExpressionField : public Responder, public View {
class ExpressionField : public Responder, public View {
public:
ExpressionField(Responder * parentResponder, char * textBuffer, int textBufferLength, Poincare::ExpressionLayout * layout, TextFieldDelegate * textFieldDelegate, ExpressionLayoutFieldDelegate * expressionLayoutFieldDelegate);
@@ -20,6 +20,7 @@ public:
bool editionIsInTextField() const;
bool isEmpty() const;
bool heightIsMaximal() const;
bool handleEventWithText(const char * text, bool indentation = false) override;
/* View */
int numberOfSubviews() const override { return 1; }

View File

@@ -20,6 +20,7 @@ public:
void reload();
bool hasText() const;
void writeTextInBuffer(char * buffer, int bufferLength);
bool handleEventWithText(const char * text, bool indentation = false);
Poincare::ExpressionLayout * expressionLayout();
/* Responder */

View File

@@ -10,6 +10,7 @@ class Responder {
public:
Responder(Responder * parentResponder);
virtual bool handleEvent(Ion::Events::Event event); // Default implementation does nothing
virtual bool handleEventWithText(const char * text, bool indentation = false) { return false; }
virtual void didBecomeFirstResponder();
virtual void willResignFirstResponder();
virtual void didEnterResponderChain(Responder * previousFirstResponder);

View File

@@ -19,7 +19,6 @@ public:
size_t cursorLocation() const { return nonEditableContentView()->cursorLocation(); }
bool setCursorLocation(int location);
virtual void scrollToCursor();
virtual bool handleEventWithText(const char * text, bool indenting = false) = 0;
protected:
class ContentView : public View {
public:

View File

@@ -8,12 +8,9 @@ namespace TextInputHelpers {
int CursorIndexInCommand(const char * text);
/* Returns the index of the cursor position in a Command, which is the smallest
* index between :
* - After the first open parenthesis/quote if the following element is
* either a quote, a coma or a parenthesi
* - The end of the text
* - Special case: when the text preceding the parenthesis is 'random', the
* cursor position is the end of the text. */
constexpr static const char * k_random = "random";
* - The first EmptyChar index (which is the position of the first argument)
* - The first empty quote
* - The end of the text */
}
#endif

View File

@@ -12,7 +12,7 @@
class Toolbox : public StackViewController, public ListViewDataSource, public SelectableTableViewDataSource {
public:
Toolbox(Responder * parentResponder, const char * title = 0);
void setSender(Responder * sender);
void setSender(Responder * sender) { m_sender = sender; }
// StackViewController
bool handleEvent(Ion::Events::Event event) override;
@@ -72,7 +72,7 @@ protected:
bool handleEventForRow(Ion::Events::Event event, int selectedRow);
bool selectSubMenu(ToolboxMessageTree * selectedMessageTree);
bool returnToPreviousMenu();
virtual Responder * sender();
Responder * sender() { return m_sender; }
virtual bool selectLeaf(ToolboxMessageTree * selectedMessageTree) = 0;
virtual const ToolboxMessageTree * rootModel() = 0;
virtual MessageTableCellWithMessage * leafCellAtIndex(int index) = 0;

View File

@@ -109,6 +109,14 @@ bool ExpressionField::heightIsMaximal() const {
return inputViewHeight() == k_separatorThickness + k_verticalExpressionViewMargin + maximalHeight();
}
bool ExpressionField::handleEventWithText(const char * text, bool indentation) {
if (editionIsInTextField()) {
return m_textField.handleEventWithText(text, indentation);
} else {
return m_expressionLayoutField.handleEventWithText(text, indentation);
}
}
KDCoordinate ExpressionField::inputViewHeight() const {
if (editionIsInTextField()) {
return k_separatorThickness + k_textFieldHeight;

View File

@@ -1,9 +1,11 @@
#include <escher/expression_layout_field.h>
#include <escher/clipboard.h>
#include <escher/text_field.h>
#include <poincare/src/layout/matrix_layout.h>
#include <poincare/expression.h>
#include <poincare/expression_layout_cursor.h>
#include <poincare/src/layout/matrix_layout.h>
#include <assert.h>
#include <string.h>
ExpressionLayoutField::ExpressionLayoutField(Responder * parentResponder, Poincare::ExpressionLayout * expressionLayout, ExpressionLayoutFieldDelegate * delegate) :
ScrollableView(parentResponder, &m_contentView, this),
@@ -254,6 +256,47 @@ void ExpressionLayoutField::writeTextInBuffer(char * buffer, int bufferLength) {
m_contentView.expressionView()->expressionLayout()->writeTextInBuffer(buffer, bufferLength);
}
bool ExpressionLayoutField::handleEventWithText(const char * text, bool indentation) {
size_t textLength = strlen(text) + 1;
size_t textToInsertMaxLength = 2*textLength;
char textToInsert[textToInsertMaxLength];
size_t textToInsertIndex = 0;
// Add empty chars where arguments are needed
for (size_t i = textToInsertIndex; i < textLength; i++) {
textToInsert[textToInsertIndex++] = text[i];
if (((text[i] == '(' || text[i] == '[')
&& text[i+1] == ',')
|| (text[i] == ','
&& (text[i+1] == ')' || text[i+1] == ']')))
{
textToInsert[textToInsertIndex++] = Ion::Charset::Empty;
}
}
Poincare::Expression * resultExpression = Poincare::Expression::parse(textToInsert);
if (resultExpression == nullptr) {
return false;
}
Poincare::ExpressionLayout * resultLayout = resultExpression->createLayout();
// Find the pointed layout.
Poincare::ExpressionLayout * pointedLayout = nullptr;
if (resultLayout->isHorizontal()) {
/* If the layout is horizontal, pick the first open parenthesis. For now,
* all horizontal layouts in MathToolbox have parentheses. */
for (int i = 0; i < resultLayout->numberOfChildren(); i++) {
if (resultLayout->editableChild(i)->isLeftParenthesis()) {
pointedLayout = resultLayout->editableChild(i);
break;
}
}
}
/* Insert the layout. If pointedLayout is nullptr, the cursor will be on the
* right of the inserted layout. */
insertLayoutAtCursor(resultLayout, pointedLayout);
return true;
}
Poincare::ExpressionLayout * ExpressionLayoutField::expressionLayout() {
return m_contentView.expressionView()->expressionLayout();
}

View File

@@ -293,8 +293,22 @@ TextArea::TextArea(Responder * parentResponder, char * textBuffer,
bool TextArea::handleEventWithText(const char * text, bool indentation) {
int nextCursorLocation = cursorLocation();
if ((indentation && insertTextWithIndentation(text, cursorLocation())) || insertTextAtLocation(text, cursorLocation())) {
nextCursorLocation += TextInputHelpers::CursorIndexInCommand(text);
int cursorIndexInCommand = TextInputHelpers::CursorIndexInCommand(text);
size_t eventTextSize = strlen(text) + 1;
char buffer[eventTextSize];
size_t bufferIndex = 0;
// Remove EmptyChars
for (size_t i = bufferIndex; i < eventTextSize; i++) {
if (text[i] != Ion::Charset::Empty) {
buffer[bufferIndex++] = text[i];
}
}
if ((indentation && insertTextWithIndentation(buffer, cursorLocation())) || insertTextAtLocation(buffer, cursorLocation())) {
nextCursorLocation += cursorIndexInCommand;
}
setCursorLocation(nextCursorLocation);
return true;

View File

@@ -1,6 +1,7 @@
#include <escher/text_field.h>
#include <escher/text_input_helpers.h>
#include <escher/clipboard.h>
#include <ion/charset.h>
#include <assert.h>
/* TextField::ContentView */
@@ -305,12 +306,41 @@ bool TextField::handleEventWithText(const char * eventText, bool indentation) {
if (!isEditing()) {
setEditing(true);
}
size_t eventTextSize = strlen(eventText) + 1;
char buffer[eventTextSize];
size_t bufferIndex = 0;
/* DIRTY
* We use the notation "_{}" to indicate a subscript layout. In a text field,
* such a subscript should be written using parentheses. For instance: "u_{n}"
* should be inserted as "u(n)".
* We thus remove underscores and changes brackets into parentheses. */
for (size_t i = bufferIndex; i < eventTextSize; i++) {
if (eventText[i] == '{') {
buffer[bufferIndex++] = '(';
} else if (eventText[i] == '}') {
buffer[bufferIndex++] = ')';
} else if (eventText[i] != '_') {
buffer[bufferIndex++] = eventText[i];
}
}
int cursorIndexInCommand = TextInputHelpers::CursorIndexInCommand(eventText);
bufferIndex = 0;
// Remove EmptyChars
for (size_t i = bufferIndex; i < eventTextSize; i++) {
if (buffer[i] != Ion::Charset::Empty) {
buffer[bufferIndex++] = buffer[i];
}
}
int nextCursorLocation = draftTextLength();
if (insertTextAtLocation(eventText, cursorLocation())) {
/* The cursor position depends on the text as we sometimes want to
* position the cursor at the end of the text and sometimes after the
* first parenthesis. */
nextCursorLocation = cursorLocation() + TextInputHelpers::CursorIndexInCommand(eventText);
if (insertTextAtLocation(buffer, cursorLocation())) {
/* The cursor position depends on the text as we sometimes want to position
* the cursor at the end of the text and sometimes after the first
* parenthesis. */
nextCursorLocation = cursorLocation() + cursorIndexInCommand;
}
setCursorLocation(nextCursorLocation);
return m_delegate->textFieldDidHandleEvent(this, true, strlen(text()) != previousTextLength);

View File

@@ -1,20 +1,19 @@
#include <escher/text_input_helpers.h>
#include <ion/charset.h>
#include <string.h>
namespace TextInputHelpers {
int CursorIndexInCommand(const char * text) {
for (size_t i = 0; i < strlen(text)-1; i++) {
if (text[i] == '(' || text[i] == '\'') {
if (text[i+1] == ')' || text[i+1] == '\'' || text[i+1] == ',') {
if (i >= strlen(k_random) && memcmp(&text[i-strlen(k_random)], k_random, strlen(k_random)) == 0) {
break;
}
return i + 1;
}
size_t textLength = strlen(text);
for (size_t i = 0; i < textLength - 1; i++) {
if (text[i] == '\'' && text[i+1] == '\'') {
return i + 1;
} else if (text[i] == Ion::Charset::Empty) {
return i;
}
}
return strlen(text);
return textLength;
}
}

View File

@@ -94,10 +94,6 @@ Toolbox::Toolbox(Responder * parentResponder, const char * title) :
{
}
void Toolbox::setSender(Responder * sender) {
m_sender = sender;
}
bool Toolbox::handleEvent(Ion::Events::Event event) {
return handleEventForRow(event, selectedRow());
}
@@ -232,7 +228,3 @@ bool Toolbox::returnToPreviousMenu() {
app()->setFirstResponder(&m_listController);
return true;
}
Responder * Toolbox::sender() {
return m_sender;
}