From 51a5f699c3dbe75e0644b005f2047bc2bd316860 Mon Sep 17 00:00:00 2001 From: Laury Date: Wed, 6 Jul 2022 22:52:49 +0200 Subject: [PATCH] [escher] XNT button is now cyclic --- apps/Makefile | 1 + apps/apps_container.h | 4 +++ apps/code/app.cpp | 7 ++-- apps/code/editor_controller.cpp | 6 +++- apps/code/editor_controller.h | 1 + apps/code/helpers.cpp | 12 ++++--- apps/code/helpers.h | 2 +- apps/code/python_text_area.cpp | 10 +++--- apps/code/python_text_area.h | 2 +- apps/shared/expression_field_delegate_app.h | 1 + apps/shared/layout_field_delegate.h | 2 ++ apps/shared/text_field_delegate.h | 2 ++ apps/shared/text_field_delegate_app.cpp | 4 ++- apps/shared/text_field_delegate_app.h | 2 ++ apps/xnt_loop.cpp | 22 ++++++++++++ apps/xnt_loop.h | 15 ++++++++ escher/include/escher/input_event_handler.h | 2 +- escher/include/escher/input_view_controller.h | 2 ++ escher/include/escher/layout_field.h | 2 +- escher/include/escher/layout_field_delegate.h | 1 + escher/include/escher/text_area.h | 5 +-- escher/include/escher/text_area_delegate.h | 1 + escher/include/escher/text_field.h | 5 +-- escher/include/escher/text_field_delegate.h | 1 + escher/include/escher/text_input.h | 5 +-- escher/src/input_view_controller.cpp | 8 +++++ escher/src/layout_field.cpp | 8 ++++- escher/src/text_area.cpp | 35 ++++++++++++++----- escher/src/text_field.cpp | 31 ++++++++++++---- escher/src/text_input.cpp | 16 ++++----- 30 files changed, 167 insertions(+), 48 deletions(-) create mode 100644 apps/xnt_loop.cpp create mode 100644 apps/xnt_loop.h diff --git a/apps/Makefile b/apps/Makefile index b4d2c7016..3aed30735 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -60,6 +60,7 @@ apps_src += $(addprefix apps/,\ suspend_timer.cpp \ timer_manager.cpp \ title_bar_view.cpp \ + xnt_loop.cpp \ ) tests_src += $(addprefix apps/,\ diff --git a/apps/apps_container.h b/apps/apps_container.h index c6c53b0fe..1366b01e7 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -18,6 +18,7 @@ #include "shared/global_context.h" #include "clock_timer.h" #include "on_boarding/prompt_controller.h" +#include "xnt_loop.h" #include @@ -48,6 +49,8 @@ public: void displayExamModePopUp(GlobalPreferences::ExamMode mode); void shutdownDueToLowBattery(); void setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus newStatus); + CodePoint XNT(CodePoint defaultXNT, bool * shouldRemoveLastCharacter) { m_XNTLoop.XNT(defaultXNT, shouldRemoveLastCharacter); } + void resetXNT() { m_XNTLoop.reset(); } OnBoarding::PromptController * promptController(); void redrawWindow(bool force = false); void activateExamMode(GlobalPreferences::ExamMode examMode); @@ -85,6 +88,7 @@ private: OnBoarding::App::Snapshot m_onBoardingSnapshot; HardwareTest::App::Snapshot m_hardwareTestSnapshot; USB::App::Snapshot m_usbConnectedSnapshot; + XNTLoop m_XNTLoop; }; #endif diff --git a/apps/code/app.cpp b/apps/code/app.cpp index 4db851b22..d865e7a46 100644 --- a/apps/code/app.cpp +++ b/apps/code/app.cpp @@ -129,9 +129,10 @@ VariableBoxController * App::variableBoxForInputEventHandler(InputEventHandler * } bool App::textInputDidReceiveEvent(InputEventHandler * textInput, Ion::Events::Event event) { - const char * pythonText = Helpers::PythonTextForEvent(event); - if (pythonText != nullptr) { - textInput->handleEventWithText(pythonText); + bool shouldRemoveLastCharacter = false; + char buffer[CodePoint::MaxCodePointCharLength + 1]; + if (Helpers::PythonTextForEvent(event, buffer, &shouldRemoveLastCharacter)) { + textInput->handleEventWithText(buffer, false, false, shouldRemoveLastCharacter); return true; } return false; diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index f314f3d72..257398ae5 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -5,6 +5,7 @@ #include #include #include "../global_preferences.h" +#include using namespace Shared; @@ -72,6 +73,10 @@ void EditorController::viewDidDisappear() { m_menuController->scriptContentEditionDidFinish(); } +void EditorController::textAreaDidReceiveNoneXNTEvent() { + AppsContainer::sharedAppsContainer()->resetXNT(); +} + bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) { if (App::app()->textInputDidReceiveEvent(textArea, event)) { return true; @@ -81,7 +86,6 @@ bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events: return true; } - if (event == Ion::Events::Backspace && textArea->selectionIsEmpty()) { /* If the cursor is on the left of the text of a line, backspace one * indentation space at a time. */ diff --git a/apps/code/editor_controller.h b/apps/code/editor_controller.h index 4cd32c1ed..42b35bfa7 100644 --- a/apps/code/editor_controller.h +++ b/apps/code/editor_controller.h @@ -30,6 +30,7 @@ public: TELEMETRY_ID("Editor"); /* TextAreaDelegate */ + void textAreaDidReceiveNoneXNTEvent() override; bool textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) override; /* InputEventHandlerDelegate */ diff --git a/apps/code/helpers.cpp b/apps/code/helpers.cpp index 18250e77f..37ec97405 100644 --- a/apps/code/helpers.cpp +++ b/apps/code/helpers.cpp @@ -1,20 +1,24 @@ #include "helpers.h" #include +#include namespace Code { namespace Helpers { -const char * PythonTextForEvent(Ion::Events::Event event) { +bool PythonTextForEvent(Ion::Events::Event event, char * buffer, bool * shouldRemoveLastCharacter) { for (size_t i=0; iXNT('x', shouldRemoveLastCharacter); + buffer[UTF8Decoder::CodePointToChars(XNT, buffer, CodePoint::MaxCodePointCharLength + 1)] = 0; + return true; } + return false; } - return nullptr; } } } diff --git a/apps/code/helpers.h b/apps/code/helpers.h index 1273ca045..69b6b3842 100644 --- a/apps/code/helpers.h +++ b/apps/code/helpers.h @@ -6,7 +6,7 @@ namespace Code { namespace Helpers { -const char * PythonTextForEvent(Ion::Events::Event event); +bool PythonTextForEvent(Ion::Events::Event event, char * buffer, bool * shouldRemoveLastCharacter); } } diff --git a/apps/code/python_text_area.cpp b/apps/code/python_text_area.cpp index da2274cb7..3edf9eeae 100644 --- a/apps/code/python_text_area.cpp +++ b/apps/code/python_text_area.cpp @@ -426,14 +426,14 @@ bool PythonTextArea::handleEvent(Ion::Events::Event event) { return result; } -bool PythonTextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) { +bool PythonTextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText, bool shouldRemoveLastCharacter) { if (*text == 0) { return false; } if (m_contentView.isAutocompleting()) { removeAutocompletion(); } - bool result = TextArea::handleEventWithText(text, indentation, forceCursorRightOfText); + bool result = TextArea::handleEventWithText(text, indentation, forceCursorRightOfText, shouldRemoveLastCharacter); addAutocompletion(); return result; } @@ -493,9 +493,10 @@ bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIn if (textToInsertLength > 0) { // Try to insert the text (this might fail if the buffer is full) - if (!m_contentView.insertTextAtLocation(textToInsert, const_cast(autocompletionLocation), textToInsertLength)) { + if (!m_contentView.isAbleToInsertTextAt(textToInsertLength, autocompletionLocation, false)) { return false; } + m_contentView.insertTextAtLocation(textToInsert, const_cast(autocompletionLocation), textToInsertLength); autocompletionLocation += textToInsertLength; m_contentView.setAutocompleting(true); m_contentView.setAutocompletionEnd(autocompletionLocation); @@ -507,7 +508,8 @@ bool PythonTextArea::addAutocompletionTextAtIndex(int nextIndex, int * currentIn assert(strlen(parentheses) == parenthesesLength); /* If couldInsertText is false, we should not try to add the parentheses as * there was already not enough space to add the autocompletion. */ - if (addParentheses && m_contentView.insertTextAtLocation(parentheses, const_cast(autocompletionLocation), parenthesesLength)) { + if (addParentheses && m_contentView.isAbleToInsertTextAt(parenthesesLength, autocompletionLocation, false)) { + m_contentView.insertTextAtLocation(parentheses, const_cast(autocompletionLocation), parenthesesLength); m_contentView.setAutocompleting(true); m_contentView.setAutocompletionEnd(autocompletionLocation + parenthesesLength); return true; diff --git a/apps/code/python_text_area.h b/apps/code/python_text_area.h index fb4bba2da..57a46a5f8 100644 --- a/apps/code/python_text_area.h +++ b/apps/code/python_text_area.h @@ -23,7 +23,7 @@ public: void loadSyntaxHighlighter() { m_contentView.loadSyntaxHighlighter(); } void unloadSyntaxHighlighter() { m_contentView.unloadSyntaxHighlighter(); } bool handleEvent(Ion::Events::Event event) override; - bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; + bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false, bool shouldRemoveLastCharacter = false) override; /* autocompletionType returns: * - EndOfIdentifier if there is currently autocompletion, or if the cursor is * at the end of an identifier, diff --git a/apps/shared/expression_field_delegate_app.h b/apps/shared/expression_field_delegate_app.h index 0e1752813..466e5eb21 100644 --- a/apps/shared/expression_field_delegate_app.h +++ b/apps/shared/expression_field_delegate_app.h @@ -9,6 +9,7 @@ namespace Shared { class ExpressionFieldDelegateApp : public TextFieldDelegateApp, public LayoutFieldDelegate { public: virtual ~ExpressionFieldDelegateApp() = default; + void layoutFieldDidReceiveNoneXNTEvent() override { AppsContainer::sharedAppsContainer()->resetXNT(); } bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) override; bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) override; protected: diff --git a/apps/shared/layout_field_delegate.h b/apps/shared/layout_field_delegate.h index 79f25607d..5b2bb1c3c 100644 --- a/apps/shared/layout_field_delegate.h +++ b/apps/shared/layout_field_delegate.h @@ -2,11 +2,13 @@ #define SHARED_LAYOUT_FIELD_DELEGATE_H #include "expression_field_delegate_app.h" +#include "../apps_container.h" namespace Shared { class LayoutFieldDelegate : public ::LayoutFieldDelegate { public: + void layoutFieldDidReceiveNoneXNTEvent() override { AppsContainer::sharedAppsContainer()->resetXNT(); } bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) override; bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) override; bool layoutFieldDidFinishEditing(LayoutField * layoutField, Poincare::Layout layoutR, Ion::Events::Event event) override; diff --git a/apps/shared/text_field_delegate.h b/apps/shared/text_field_delegate.h index bc148e95a..cd04bcffb 100644 --- a/apps/shared/text_field_delegate.h +++ b/apps/shared/text_field_delegate.h @@ -2,11 +2,13 @@ #define SHARED_TEXT_FIELD_DELEGATE_H #include "text_field_delegate_app.h" +#include "../apps_container.h" namespace Shared { class TextFieldDelegate : public ::TextFieldDelegate { public: + void textFieldDidReceiveNoneXNTEvent() override { AppsContainer::sharedAppsContainer()->resetXNT(); } bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; protected: diff --git a/apps/shared/text_field_delegate_app.cpp b/apps/shared/text_field_delegate_app.cpp index ece157f9b..68ae84d0b 100644 --- a/apps/shared/text_field_delegate_app.cpp +++ b/apps/shared/text_field_delegate_app.cpp @@ -72,10 +72,12 @@ bool TextFieldDelegateApp::fieldDidReceiveEvent(EditableField * field, Responder if (XNTCanBeOverriden()) { xnt = field->XNTCodePoint(xnt); } + bool shouldRemoveLastCharacter = false; + xnt = AppsContainer::sharedAppsContainer()->XNT(xnt, &shouldRemoveLastCharacter); size_t length = UTF8Decoder::CodePointToChars(xnt, buffer, bufferSize); assert(length < bufferSize - 1); buffer[length] = 0; - return field->handleEventWithText(buffer); + return field->handleEventWithText(buffer, false, false, shouldRemoveLastCharacter); } return false; } diff --git a/apps/shared/text_field_delegate_app.h b/apps/shared/text_field_delegate_app.h index 0dd472e78..a33adffde 100644 --- a/apps/shared/text_field_delegate_app.h +++ b/apps/shared/text_field_delegate_app.h @@ -5,6 +5,7 @@ #include "input_event_handler_delegate_app.h" #include #include +#include "../apps_container.h" class EditableField; @@ -16,6 +17,7 @@ public: Poincare::Context * localContext() override; virtual bool XNTCanBeOverriden() const { return true; } virtual CodePoint XNT() { return 'x'; } + virtual void textFieldDidReceiveNoneXNTEvent() override { AppsContainer::sharedAppsContainer()->resetXNT(); } bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; bool isAcceptableText(const char * text); diff --git a/apps/xnt_loop.cpp b/apps/xnt_loop.cpp new file mode 100644 index 000000000..e81b6d1aa --- /dev/null +++ b/apps/xnt_loop.cpp @@ -0,0 +1,22 @@ +#include "xnt_loop.h" +#include + +CodePoint XNTLoop::XNT(CodePoint defaultCodePoint, bool * shouldRemoveLastCharacter) { + assert(shouldRemoveLastCharacter != nullptr); + static constexpr CodePoint XNTCodePoints[] = {'x', 'n', 't', UCodePointGreekSmallLetterTheta}; + int XNTCodePointSize = sizeof(XNTCodePoints) / sizeof(CodePoint); + + if (m_loopIndex == -1) { + for (int i = 0; i < XNTCodePointSize; i++) { + if (XNTCodePoints[i] == defaultCodePoint) { + m_loopIndex = i; + break; + } + } + } else { + *shouldRemoveLastCharacter = true; + m_loopIndex = (m_loopIndex + 1) % XNTCodePointSize; + } + + return XNTCodePoints[m_loopIndex]; +} diff --git a/apps/xnt_loop.h b/apps/xnt_loop.h new file mode 100644 index 000000000..e9ed8a0c9 --- /dev/null +++ b/apps/xnt_loop.h @@ -0,0 +1,15 @@ +#ifndef APPS_XNT_LOOP +#define APPS_XNT_LOOP + +#include + +class XNTLoop { +public: + XNTLoop(): m_loopIndex(-1) {} + void reset() { m_loopIndex = -1; } + CodePoint XNT(CodePoint defaultCodePoint, bool * shouldRemoveLastCharacter); +private: + int m_loopIndex; +}; + +#endif diff --git a/escher/include/escher/input_event_handler.h b/escher/include/escher/input_event_handler.h index b5fed665f..72f444cad 100644 --- a/escher/include/escher/input_event_handler.h +++ b/escher/include/escher/input_event_handler.h @@ -10,7 +10,7 @@ class InputEventHandlerDelegate; class InputEventHandler { public: InputEventHandler(InputEventHandlerDelegate * inputEventHandlerdelegate) : m_inputEventHandlerDelegate(inputEventHandlerdelegate) {} - virtual bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) { return false; } + virtual bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false, bool shouldRemoveLastCharacter = false) { return false; } protected: bool handleBoxEvent(Ion::Events::Event event); InputEventHandlerDelegate * m_inputEventHandlerDelegate; diff --git a/escher/include/escher/input_view_controller.h b/escher/include/escher/input_view_controller.h index 5d0bd9197..2b89f6901 100644 --- a/escher/include/escher/input_view_controller.h +++ b/escher/include/escher/input_view_controller.h @@ -27,12 +27,14 @@ public: void abortEditionAndDismiss(); /* TextFieldDelegate */ + void textFieldDidReceiveNoneXNTEvent() override; bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) override; bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; bool textFieldDidAbortEditing(TextField * textField) override; /* LayoutFieldDelegate */ + void layoutFieldDidReceiveNoneXNTEvent() override; bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) override; bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) override; bool layoutFieldDidFinishEditing(LayoutField * layoutField, Poincare::Layout layoutR, Ion::Events::Event event) override; diff --git a/escher/include/escher/layout_field.h b/escher/include/escher/layout_field.h index bd36e8b14..3d6a4c76a 100644 --- a/escher/include/escher/layout_field.h +++ b/escher/include/escher/layout_field.h @@ -45,7 +45,7 @@ public: } /* Responder */ - bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; + bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false, bool shouldRemoveLastCharacter = false) override; bool handleEvent(Ion::Events::Event event) override; // TODO: factorize with TextField (see TODO of EditableField) bool shouldFinishEditing(Ion::Events::Event event) override; diff --git a/escher/include/escher/layout_field_delegate.h b/escher/include/escher/layout_field_delegate.h index ce9dfaf02..080cfc47c 100644 --- a/escher/include/escher/layout_field_delegate.h +++ b/escher/include/escher/layout_field_delegate.h @@ -9,6 +9,7 @@ class LayoutField; class LayoutFieldDelegate : public ContextProvider{ public: + virtual void layoutFieldDidReceiveNoneXNTEvent() {}; virtual bool layoutFieldShouldFinishEditing(LayoutField * layoutField, Ion::Events::Event event) = 0; virtual bool layoutFieldDidReceiveEvent(LayoutField * layoutField, Ion::Events::Event event) = 0; virtual bool layoutFieldDidFinishEditing(LayoutField * layoutField, Poincare::Layout layoutR, Ion::Events::Event event) { return false; } diff --git a/escher/include/escher/text_area.h b/escher/include/escher/text_area.h index efd313633..110268b3f 100644 --- a/escher/include/escher/text_area.h +++ b/escher/include/escher/text_area.h @@ -16,7 +16,7 @@ public: TextArea(Responder * parentResponder, View * contentView, const KDFont * font = KDFont::LargeFont); void setDelegates(InputEventHandlerDelegate * inputEventHandlerDelegate, TextAreaDelegate * delegate) { m_inputEventHandlerDelegate = inputEventHandlerDelegate; m_delegate = delegate; } bool handleEvent(Ion::Events::Event event) override; - bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; + bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false, bool shouldRemoveLastCharacter = false) override; void setText(char * textBuffer, size_t textBufferSize); protected: @@ -120,7 +120,8 @@ protected: const char * editedText() const override { return m_text.text(); } size_t editedTextLength() const override { return m_text.textLength(); } const Text * getText() const { return &m_text; } - bool insertTextAtLocation(const char * text, char * location, int textLength = -1) override; + bool isAbleToInsertTextAt(int textLength, const char * location, bool shouldRemoveLastCharacter) const override; + void insertTextAtLocation(const char * text, char * location, int textLength = -1) override; void moveCursorGeo(int deltaX, int deltaY); bool removePreviousGlyph() override; bool removeEndOfLine() override; diff --git a/escher/include/escher/text_area_delegate.h b/escher/include/escher/text_area_delegate.h index c16c5ffa0..9c907a987 100644 --- a/escher/include/escher/text_area_delegate.h +++ b/escher/include/escher/text_area_delegate.h @@ -5,6 +5,7 @@ class TextArea; class TextAreaDelegate { public: + virtual void textAreaDidReceiveNoneXNTEvent() {}; virtual bool textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) = 0; }; diff --git a/escher/include/escher/text_field.h b/escher/include/escher/text_field.h index f7901b7f8..055c7ed3b 100644 --- a/escher/include/escher/text_field.h +++ b/escher/include/escher/text_field.h @@ -34,7 +34,7 @@ public: void setText(const char * text); void setEditing(bool isEditing) override { m_contentView.setEditing(isEditing); } CodePoint XNTCodePoint(CodePoint defaultXNTCodePoint) override; - bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; + bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false, bool shouldRemoveLastCharacter = false) override; bool handleEvent(Ion::Events::Event event) override; constexpr static int maxBufferSize() { return ContentView::k_maxBufferSize; @@ -71,10 +71,11 @@ protected: void reinitDraftTextBuffer(); void setDraftTextBufferSize(size_t size) { assert(size <= k_maxBufferSize); m_draftTextBufferSize = size; } size_t draftTextBufferSize() const { return m_draftTextBufferSize; } + bool isAbleToInsertTextAt(int textLength, const char * location, bool shouldRemoveLastCharacter) const override; /* If the text to be appended is too long to be added without overflowing the * buffer, nothing is done (not even adding few letters from the text to reach * the maximum buffer capacity) and false is returned. */ - bool insertTextAtLocation(const char * text, char * location, int textLength = -1) override; + void insertTextAtLocation(const char * text, char * location, int textLength = -1) override; KDSize minimalSizeForOptimalDisplay() const override; bool removePreviousGlyph() override; bool removeEndOfLine() override; diff --git a/escher/include/escher/text_field_delegate.h b/escher/include/escher/text_field_delegate.h index 4be1741e6..790cc75e9 100644 --- a/escher/include/escher/text_field_delegate.h +++ b/escher/include/escher/text_field_delegate.h @@ -7,6 +7,7 @@ class TextField; class TextFieldDelegate { public: + virtual void textFieldDidReceiveNoneXNTEvent() {}; virtual bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) = 0; virtual bool textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) = 0; virtual bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { return false; } diff --git a/escher/include/escher/text_input.h b/escher/include/escher/text_input.h index 0de69538c..96648be96 100644 --- a/escher/include/escher/text_input.h +++ b/escher/include/escher/text_input.h @@ -49,7 +49,8 @@ protected: // Virtual text get/add/remove virtual const char * text() const = 0; - virtual bool insertTextAtLocation(const char * text, char * location, int textLength = -1) = 0; + virtual bool isAbleToInsertTextAt(int textLength, const char * location, bool shouldRemoveLastCharacter) const = 0; + virtual void insertTextAtLocation(const char * text, char * location, int textLength) = 0; virtual bool removePreviousGlyph() = 0; virtual bool removeEndOfLine() = 0; @@ -92,7 +93,7 @@ protected: /* If the text to be appended is too long to be added without overflowing the * buffer, nothing is done (not even adding few letters from the text to reach * the maximum buffer capacity) and false is returned. */ - bool insertTextAtLocation(const char * textBuffer, char * location); + void insertTextAtLocation(const char * textBuffer, char * location, int textLength); bool removeEndOfLine(); ContentView * contentView() { return const_cast(nonEditableContentView()); diff --git a/escher/src/input_view_controller.cpp b/escher/src/input_view_controller.cpp index 2947054a7..ada1a0c44 100644 --- a/escher/src/input_view_controller.cpp +++ b/escher/src/input_view_controller.cpp @@ -58,6 +58,14 @@ bool InputViewController::textFieldDidAbortEditing(TextField * textField) { return true; } +void InputViewController::textFieldDidReceiveNoneXNTEvent() { + m_textFieldDelegate->textFieldDidReceiveNoneXNTEvent(); +} + +void InputViewController::layoutFieldDidReceiveNoneXNTEvent() { + m_layoutFieldDelegate->layoutFieldDidReceiveNoneXNTEvent(); +} + bool InputViewController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) { return m_textFieldDelegate->textFieldDidReceiveEvent(textField, event); } diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index cfdbea00c..8fd028358 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -355,7 +355,7 @@ void LayoutField::reload(KDSize previousSize) { markRectAsDirty(bounds()); } -bool LayoutField::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) { +bool LayoutField::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText, bool shouldRemoveLastCharacter) { /* The text here can be: * - the result of a key pressed, such as "," or "cos(•)" * - the text added after a toolbox selection @@ -417,6 +417,9 @@ bool LayoutField::handleEventWithText(const char * text, bool indentation, bool if (currentNumberOfLayouts + resultLayout.numberOfDescendants(true) >= k_maxNumberOfLayouts) { return true; } + if (shouldRemoveLastCharacter) { + cursor->performBackspace(); + } insertLayoutAtCursor(resultLayout, resultExpression, forceCursorRightOfText); } return true; @@ -431,6 +434,9 @@ bool LayoutField::shouldFinishEditing(Ion::Events::Event event) { } bool LayoutField::handleEvent(Ion::Events::Event event) { + if (m_delegate && event != Ion::Events::XNT) { + m_delegate->layoutFieldDidReceiveNoneXNTEvent(); + } bool didHandleEvent = false; KDSize previousSize = minimalSizeForOptimalDisplay(); bool shouldRecomputeLayout = m_contentView.cursor()->showEmptyLayoutIfNeeded(); diff --git a/escher/src/text_area.cpp b/escher/src/text_area.cpp index c3867ef73..5427ed5b6 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -33,7 +33,7 @@ static inline void InsertSpacesAtLocation(int spacesCount, char * buffer, int bu } } -bool TextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) { +bool TextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText, bool shouldRemoveLastCharacter) { if (*text == 0) { return false; } @@ -123,11 +123,19 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for return false; } - // Insert the text - if (!insertTextAtLocation(text, insertionPosition)) { + int textLength = strlen(text); + if (!contentView()->isAbleToInsertTextAt(textLength, insertionPosition, shouldRemoveLastCharacter)) { return true; } + if (shouldRemoveLastCharacter) { + removePreviousGlyph(); + insertionPosition = const_cast(cursorLocation()); + } + + // Insert the text + insertTextAtLocation(text, insertionPosition, textLength); + // Insert the indentation if (indentation) { UTF8Helper::PerformAtCodePoints( @@ -160,6 +168,9 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for } bool TextArea::handleEvent(Ion::Events::Event event) { + if (m_delegate != nullptr && event != Ion::Events::XNT) { + m_delegate->textAreaDidReceiveNoneXNTEvent(); + } if (m_delegate != nullptr && m_delegate->textAreaDidReceiveEvent(this, event)) { return true; } @@ -575,12 +586,21 @@ void TextArea::ContentView::setText(char * textBuffer, size_t textBufferSize) { m_cursorLocation = text(); } -bool TextArea::ContentView::insertTextAtLocation(const char * text, char * location, int textLength) { +bool TextArea::ContentView::isAbleToInsertTextAt(int textLength, const char * location, bool shouldRemoveLastCharacter) const { + int removedCharacters = 0; + if (shouldRemoveLastCharacter) { + UTF8Decoder decoder(m_text.text(), location); + const char * previousGlyphPos = decoder.previousGlyphPosition(); + assert(previousGlyphPos != nullptr); + removedCharacters = location - previousGlyphPos; + } + return m_text.textLength() + textLength - removedCharacters < m_text.bufferSize() && textLength != 0; +} + +void TextArea::ContentView::insertTextAtLocation(const char * text, char * location, int textLength) { int textLen = textLength < 0 ? strlen(text) : textLength; assert(textLen < 0 || textLen <= strlen(text)); - if (m_text.textLength() + textLen >= m_text.bufferSize() || textLen == 0) { - return false; - } + assert(isAbleToInsertTextAt(textLen, location, false)); // Scan for \n bool lineBreak = UTF8Helper::HasCodePoint(text, '\n', text + textLen); @@ -589,7 +609,6 @@ bool TextArea::ContentView::insertTextAtLocation(const char * text, char * locat // Replace System parentheses (used to keep layout tree structure) by normal parentheses Poincare::SerializationHelper::ReplaceSystemParenthesesByUserParentheses(location, textLen); reloadRectFromPosition(location, lineBreak); - return true; } bool TextArea::ContentView::removePreviousGlyph() { diff --git a/escher/src/text_field.cpp b/escher/src/text_field.cpp index 2587bf024..4389658ac 100644 --- a/escher/src/text_field.cpp +++ b/escher/src/text_field.cpp @@ -119,14 +119,23 @@ void TextField::ContentView::reinitDraftTextBuffer() { setCursorLocation(s_draftTextBuffer); } -bool TextField::ContentView::insertTextAtLocation(const char * text, char * location, int textLen) { +bool TextField::ContentView::isAbleToInsertTextAt(int textLength, const char * location, bool shouldRemoveLastCharacter) const { + int removedCharacters = 0; + if (shouldRemoveLastCharacter) { + UTF8Decoder decoder(s_draftTextBuffer, location); + const char * previousGlyphPos = decoder.previousGlyphPosition(); + assert(previousGlyphPos != nullptr); + removedCharacters = location - previousGlyphPos; + } + return m_currentDraftTextLength + textLength - removedCharacters < m_draftTextBufferSize && textLength != 0; +} + +void TextField::ContentView::insertTextAtLocation(const char * text, char * location, int textLen) { assert(m_isEditing); size_t textLength = textLen < 0 ? strlen(text) : (size_t)textLen; // TODO when paste fails because of a too big message, create a pop-up - if (m_currentDraftTextLength + textLength >= m_draftTextBufferSize || textLength == 0) { - return false; - } + assert(isAbleToInsertTextAt(textLength, location, false)); memmove(location + textLength, location, (s_draftTextBuffer + m_currentDraftTextLength + 1) - location); @@ -139,7 +148,6 @@ bool TextField::ContentView::insertTextAtLocation(const char * text, char * loca m_currentDraftTextLength += copySize-1; // Do no count the null-termination reloadRectFromPosition(m_horizontalAlignment == 0.0f ? location : s_draftTextBuffer); - return true; } KDSize TextField::ContentView::minimalSizeForOptimalDisplay() const { @@ -416,6 +424,9 @@ CodePoint TextField::XNTCodePoint(CodePoint defaultXNTCodePoint) { bool TextField::handleEvent(Ion::Events::Event event) { assert(m_delegate != nullptr); + if (event != Ion::Events::XNT) { + m_delegate->textFieldDidReceiveNoneXNTEvent(); + } size_t previousTextLength = strlen(text()); bool didHandleEvent = false; if (privateHandleMoveEvent(event)) { @@ -486,7 +497,7 @@ bool TextField::privateHandleSelectEvent(Ion::Events::Event event) { return false; } -bool TextField::handleEventWithText(const char * eventText, bool indentation, bool forceCursorRightOfText) { +bool TextField::handleEventWithText(const char * eventText, bool indentation, bool forceCursorRightOfText, bool shouldRemoveLastCharacter) { size_t previousTextLength = strlen(text()); if (!isEditing()) { @@ -518,7 +529,13 @@ bool TextField::handleEventWithText(const char * eventText, bool indentation, bo // Replace System parentheses (used to keep layout tree structure) by normal parentheses Poincare::SerializationHelper::ReplaceSystemParenthesesByUserParentheses(buffer); - if (insertTextAtLocation(buffer, const_cast(cursorLocation()))) { + int textLength = strlen(buffer); + if (contentView()->isAbleToInsertTextAt(textLength, cursorLocation(), shouldRemoveLastCharacter)) { + if (shouldRemoveLastCharacter) { + removePreviousGlyph(); + } + + insertTextAtLocation(buffer, const_cast(cursorLocation()), textLength); /* 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. */ diff --git a/escher/src/text_input.cpp b/escher/src/text_input.cpp index da741f62c..608a289b8 100644 --- a/escher/src/text_input.cpp +++ b/escher/src/text_input.cpp @@ -170,15 +170,13 @@ void TextInput::setAlignment(float horizontalAlignment, float verticalAlignment) contentView()->setAlignment(horizontalAlignment, verticalAlignment); } -bool TextInput::insertTextAtLocation(const char * text, char * location) { - if (contentView()->insertTextAtLocation(text, location)) { - /* We layout the scrollable view before scrolling to cursor because the - * content size might have changed. */ - layoutSubviews(); - scrollToCursor(); - return true; - } - return false; +void TextInput::insertTextAtLocation(const char * text, char * location, int textLength) { + assert(contentView()->isAbleToInsertTextAt(textLength, location, false)); + contentView()->insertTextAtLocation(text, location, textLength); + /* We layout the scrollable view before scrolling to cursor because the + * content size might have changed. */ + layoutSubviews(); + scrollToCursor(); } bool TextInput::removeEndOfLine() {