From 5142c071df8a04c328806fc5f8508d74bfe4a8c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 18 Jan 2019 16:41:39 +0100 Subject: [PATCH] [escher] Fix text inputs so they use UTF8 --- apps/code/editor_controller.cpp | 61 ++-- apps/code/editor_controller.h | 2 +- apps/code/editor_view.h | 4 +- apps/code/menu_controller.cpp | 15 +- apps/code/python_text_area.cpp | 6 +- apps/code/python_text_area.h | 2 +- apps/probability/calculation_controller.cpp | 7 +- .../editable_cell_table_view_controller.cpp | 4 +- apps/shared/text_field_with_extension.cpp | 23 +- apps/shared/text_field_with_extension.h | 2 +- escher/include/escher/text_area.h | 28 +- escher/include/escher/text_field.h | 19 +- escher/include/escher/text_input.h | 30 +- escher/include/escher/text_input_helpers.h | 10 +- escher/src/text_area.cpp | 304 +++++++++++------- escher/src/text_field.cpp | 202 +++++++----- escher/src/text_input.cpp | 53 +-- escher/src/text_input_helpers.cpp | 18 +- kandinsky/include/kandinsky/font.h | 5 +- .../include/kandinsky/unicode/utf8_decoder.h | 16 +- .../include/kandinsky/unicode/utf8_helper.h | 2 +- kandinsky/src/font.cpp | 11 +- kandinsky/src/unicode/utf8_decoder.cpp | 38 ++- kandinsky/src/unicode/utf8_helper.cpp | 8 +- 24 files changed, 510 insertions(+), 360 deletions(-) diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index 51f1f2397..11a22cb07 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -44,7 +44,7 @@ void EditorController::didBecomeFirstResponder() { void EditorController::viewWillAppear() { m_editorView.loadSyntaxHighlighter(); - m_editorView.setCursorLocation(strlen(m_editorView.text())); + m_editorView.setCursorTextLocation(m_editorView.text() + strlen(m_editorView.text())); } void EditorController::viewDidDisappear() { @@ -53,7 +53,8 @@ void EditorController::viewDidDisappear() { bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) { if (event == Ion::Events::Var) { - // We save script before displaying the Variable box to add new functions or variables + /* We save the script before displaying the Variable box to add new + * functions or variables. */ saveScript(); return false; } @@ -61,60 +62,38 @@ bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events: return true; } if (event == Ion::Events::EXE) { - // Auto-Indent - char * text = const_cast(textArea->text()); - int charBeforeCursorIndex = textArea->cursorLocation()-1; - int indentationSize = 0; - // Indent more if the previous line ends with ':'. - if (charBeforeCursorIndex >= 0 && text[charBeforeCursorIndex] == ':') { - indentationSize += k_indentationSpacesNumber; - } - // Compute the indentation of the current line. - int indentationIndex = charBeforeCursorIndex; - while (indentationIndex >= 0 && text[indentationIndex] != '\n') { - indentationIndex--; - } - if (indentationIndex >= 0) { - indentationIndex++; - while (text[indentationIndex] == ' ') { - indentationSize++; - indentationIndex++; - } - } - textArea->handleEventWithText("\n"); - for (int i = 0; i < indentationSize; i++) { - textArea->handleEventWithText(" "); - } + textArea->handleEventWithText("\n", true, false); return true; } if (event == Ion::Events::Backspace) { - // If the cursor is on the left of the text of a line, - // backspace one intentation space at a time. + /* If the cursor is on the left of the text of a line, backspace one + * indentation space at a time. */ char * text = const_cast(textArea->text()); - int charBeforeCursorIndex = textArea->cursorLocation()-1; + const char * charBeforeCursorPointer = textArea->cursorTextLocation()-1; int indentationSize = 0; - while (charBeforeCursorIndex >= 0 && text[charBeforeCursorIndex] == ' ') { - charBeforeCursorIndex--; + while (charBeforeCursorPointer >= text && *charBeforeCursorPointer == ' ') { + charBeforeCursorPointer--; indentationSize++; } - if (charBeforeCursorIndex >= 0 && text[charBeforeCursorIndex] == '\n' + if (charBeforeCursorPointer >= text + && *charBeforeCursorPointer == '\n' && indentationSize >= k_indentationSpacesNumber) { for (int i = 0; i < k_indentationSpacesNumber; i++) { - textArea->removeChar(); + textArea->removeCodePoint(); } return true; } } else if (event == Ion::Events::Space) { - // If the cursor is on the left of the text of a line, - // a space triggers an indentation. + /* If the cursor is on the left of the text of a line, a space triggers an + * indentation. */ char * text = const_cast(textArea->text()); - int charBeforeCursorIndex = textArea->cursorLocation()-1; - while (charBeforeCursorIndex >= 0 && text[charBeforeCursorIndex] == ' ') { - charBeforeCursorIndex--; + const char * charBeforeCursorPointer = textArea->cursorTextLocation()-1; + while (charBeforeCursorPointer >= text && *charBeforeCursorPointer == ' ') { + charBeforeCursorPointer--; } - if (charBeforeCursorIndex >= 0 && text[charBeforeCursorIndex] == '\n') { + if (charBeforeCursorPointer >= text && *charBeforeCursorPointer == '\n') { char indentationBuffer[k_indentationSpacesNumber+1]; for (int i = 0; i < k_indentationSpacesNumber; i++) { indentationBuffer[i] = ' '; @@ -144,9 +123,7 @@ StackViewController * EditorController::stackController() { void EditorController::saveScript() { size_t sizeOfValue = strlen(m_areaBuffer+1)+1+1; // size of scriptContent + size of importation status Script::ErrorStatus err = m_script.setValue({.buffer=m_areaBuffer, .size=sizeOfValue}); - if (err == Script::ErrorStatus::NotEnoughSpaceAvailable || err == Script::ErrorStatus::RecordDoesNotExist) { - assert(false); // This should not happen as we set the text area according to the available space in the Kallax - } + assert(err != Script::ErrorStatus::NotEnoughSpaceAvailable && err != Script::ErrorStatus::RecordDoesNotExist); // This should not happen as we set the text area according to the available space in the Kallax } } diff --git a/apps/code/editor_controller.h b/apps/code/editor_controller.h index 34e3eadab..93e3adb60 100644 --- a/apps/code/editor_controller.h +++ b/apps/code/editor_controller.h @@ -34,7 +34,7 @@ public: private: Shared::InputEventHandlerDelegateApp * inputEventHandlerDelegateApp() override; - static constexpr int k_indentationSpacesNumber = 2; + static constexpr int k_indentationSpacesNumber = 2; //TODO LEA merge with text area k_indentationSpaces StackViewController * stackController(); void saveScript(); EditorView m_editorView; diff --git a/apps/code/editor_view.h b/apps/code/editor_view.h index 16cb0749c..170fc9b54 100644 --- a/apps/code/editor_view.h +++ b/apps/code/editor_view.h @@ -16,8 +16,8 @@ public: void setText(char * textBuffer, size_t textBufferSize) { m_textArea.setText(textBuffer, textBufferSize); } - bool setCursorLocation(int location) { - return m_textArea.setCursorLocation(location); + bool setCursorTextLocation(const char * location) { + return m_textArea.setCursorTextLocation(location); } void loadSyntaxHighlighter() { m_textArea.loadSyntaxHighlighter(); }; void unloadSyntaxHighlighter() { m_textArea.unloadSyntaxHighlighter(); }; diff --git a/apps/code/menu_controller.cpp b/apps/code/menu_controller.cpp index bf4533456..363cdce52 100644 --- a/apps/code/menu_controller.cpp +++ b/apps/code/menu_controller.cpp @@ -126,7 +126,7 @@ void MenuController::renameSelectedScript() { app()->setFirstResponder(myCell); myCell->setHighlighted(false); myCell->textField()->setEditing(true, false); - myCell->textField()->setCursorLocation(strlen(myCell->textField()->text())); + myCell->textField()->setCursorTextLocation(myCell->textField()->text() + strlen(myCell->textField()->text())); } void MenuController::deleteScript(Script script) { @@ -283,7 +283,9 @@ bool MenuController::textFieldShouldFinishEditing(TextField * textField, Ion::Ev } bool MenuController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) { - if (event == Ion::Events::Right && textField->isEditing() && textField->cursorLocation() == textField->draftTextLength()) { + if (event == Ion::Events::Right + && textField->isEditing() + && textField->cursorTextLocation() == textField->text() + textField->draftTextLength()) { return true; } if (event == Ion::Events::Clear && textField->isEditing()) { @@ -292,7 +294,7 @@ bool MenuController::textFieldDidReceiveEvent(TextField * textField, Ion::Events assert(k_bufferSize >= 1 + strlen(ScriptStore::k_scriptExtension) + 1); strlcpy(&buffer[1], ScriptStore::k_scriptExtension, strlen(ScriptStore::k_scriptExtension) + 1); textField->setText(buffer); - textField->setCursorLocation(0); + textField->setCursorTextLocation(textField->text()); return true; } return false; @@ -316,7 +318,7 @@ bool MenuController::textFieldDidFinishEditing(TextField * textField, const char * default name and let the user modify it. */ if (!foundDefaultName) { textField->setText(numberedDefaultName); - textField->setCursorLocation(defaultNameLength); + textField->setCursorTextLocation(textField->draftTextBuffer() + defaultNameLength); } newName = const_cast(numberedDefaultName); } @@ -348,8 +350,9 @@ bool MenuController::textFieldDidFinishEditing(TextField * textField, const char bool MenuController::textFieldDidHandleEvent(TextField * textField, bool returnValue, bool textSizeDidChange) { int scriptExtensionLength = 1 + strlen(ScriptStore::k_scriptExtension); - if (textField->isEditing() && textField->cursorLocation() > textField->draftTextLength() - scriptExtensionLength) { - textField->setCursorLocation(textField->draftTextLength() - scriptExtensionLength); + const char * maxPointerLocation = textField->text() + textField->draftTextLength() - scriptExtensionLength; + if (textField->isEditing() && textField->cursorTextLocation() > maxPointerLocation) { + textField->setCursorTextLocation(maxPointerLocation); } return returnValue; } diff --git a/apps/code/python_text_area.cpp b/apps/code/python_text_area.cpp index e8764be2d..d99d26e60 100644 --- a/apps/code/python_text_area.cpp +++ b/apps/code/python_text_area.cpp @@ -18,7 +18,7 @@ constexpr KDColor OperatorColor = KDColor::RGB24(0xd73a49); constexpr KDColor StringColor = KDColor::RGB24(0x032f62); constexpr KDColor BackgroundColor = KDColorWhite; -static inline int min(int x, int y) { return (xcursorLocation() == textField->draftTextLength() && selectedColumn() < m_calculation->numberOfParameters()) - || (event == Ion::Events::Left && textField->cursorLocation() == 0); + || (event == Ion::Events::Right + && textField->cursorTextLocation() == textField->text() + textField->draftTextLength() + && selectedColumn() < m_calculation->numberOfParameters()) + || (event == Ion::Events::Left + && textField->cursorTextLocation() == textField->text()); } bool CalculationController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { diff --git a/apps/shared/editable_cell_table_view_controller.cpp b/apps/shared/editable_cell_table_view_controller.cpp index b9a5af534..a6c411bd8 100644 --- a/apps/shared/editable_cell_table_view_controller.cpp +++ b/apps/shared/editable_cell_table_view_controller.cpp @@ -19,8 +19,8 @@ bool EditableCellTableViewController::textFieldShouldFinishEditing(TextField * t return TextFieldDelegate::textFieldShouldFinishEditing(textField, event) || (event == Ion::Events::Down && selectedRow() < numberOfRows()-1) || (event == Ion::Events::Up && selectedRow() > 0) - || (event == Ion::Events::Right && textField->cursorLocation() == textField->draftTextLength() && selectedColumn() < numberOfColumns()-1) - || (event == Ion::Events::Left && textField->cursorLocation() == 0 && selectedColumn() > 0); + || (event == Ion::Events::Right && (textField->cursorTextLocation() == textField->draftTextBuffer() + textField->draftTextLength()) && selectedColumn() < numberOfColumns()-1) + || (event == Ion::Events::Left && textField->cursorTextLocation() == textField->draftTextBuffer() && selectedColumn() > 0); } bool EditableCellTableViewController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { diff --git a/apps/shared/text_field_with_extension.cpp b/apps/shared/text_field_with_extension.cpp index b2a3ca9fe..5d74cb531 100644 --- a/apps/shared/text_field_with_extension.cpp +++ b/apps/shared/text_field_with_extension.cpp @@ -2,11 +2,11 @@ namespace Shared { -void TextFieldWithExtension::willSetCursorLocation(int * location) { +void TextFieldWithExtension::willSetCursorTextLocation(const char * * location) { size_t textLength = strlen(text()); assert(textLength >= m_extensionLength); - size_t maxLocation = textLength - m_extensionLength; - if (*location > (int)maxLocation) { + const char * maxLocation = m_contentView.draftTextBuffer() + (textLength - m_extensionLength); + if (*location > maxLocation) { *location = maxLocation; } } @@ -21,17 +21,18 @@ void TextFieldWithExtension::removeWholeText() { } bool TextFieldWithExtension::removeTextBeforeExtension(bool whole) { - int extensionIndex = strlen(text()) - m_extensionLength; - assert(extensionIndex >= 0 && extensionIndex < ContentView::k_maxBufferSize - m_extensionLength); - int destinationIndex = whole ? 0 : cursorLocation(); - if (destinationIndex == extensionIndex) { + assert(isEditing()); + const char * extension = m_contentView.draftTextBuffer() + (strlen(text()) - m_extensionLength); + assert(extension >= m_contentView.draftTextBuffer() && extension < m_contentView.draftTextBuffer() + (ContentView::k_maxBufferSize - m_extensionLength)); + char * destination = whole ? m_contentView.draftTextBuffer() : const_cast(cursorTextLocation()); + if (destination == extension) { return false; } - assert(destinationIndex >= 0); - assert(destinationIndex < extensionIndex); + assert(destination >= m_contentView.draftTextBuffer()); + assert(destination < extension); m_contentView.willModifyTextBuffer(); - strlcpy(&(m_contentView.textBuffer()[destinationIndex]), &(m_contentView.textBuffer()[extensionIndex]), ContentView::k_maxBufferSize); - m_contentView.setCursorLocation(destinationIndex); + strlcpy(destination, extension, ContentView::k_maxBufferSize - (destination - m_contentView.draftTextBuffer())); + m_contentView.setCursorTextLocation(destination); m_contentView.didModifyTextBuffer(); layoutSubviews(); return true; diff --git a/apps/shared/text_field_with_extension.h b/apps/shared/text_field_with_extension.h index 12e087ef5..fa2264be4 100644 --- a/apps/shared/text_field_with_extension.h +++ b/apps/shared/text_field_with_extension.h @@ -24,7 +24,7 @@ public: m_extensionLength(extensionLength) {} private: - void willSetCursorLocation(int * location) override; + void willSetCursorTextLocation(const char * * location) override; bool privateRemoveEndOfLine() override; void removeWholeText() override; bool removeTextBeforeExtension(bool whole); diff --git a/escher/include/escher/text_area.h b/escher/include/escher/text_area.h index 94a663e9f..2e60c9d52 100644 --- a/escher/include/escher/text_area.h +++ b/escher/include/escher/text_area.h @@ -17,7 +17,7 @@ public: protected: int indentationBeforeCursor() const; - bool insertTextWithIndentation(const char * textBuffer, int location); + bool insertTextWithIndentation(const char * textBuffer, const char * location); class Text { public: @@ -36,11 +36,12 @@ protected: public: Line(const char * text); const char * text() const { return m_text; } - size_t length() const { return m_length; } + size_t charLength() const { return m_charLength; } + KDCoordinate glyphWidth(const KDFont * const font) const; bool contains(const char * c) const; private: const char * m_text; - size_t m_length; + size_t m_charLength; }; class LineIterator { @@ -66,14 +67,16 @@ protected: LineIterator begin() const { return LineIterator(m_buffer); }; LineIterator end() const { return LineIterator(nullptr); }; - Position span() const; + KDSize span(const KDFont * const font) const; - Position positionAtIndex(size_t index) const; - size_t indexAtPosition(Position p); + Position positionAtPointer(const char * pointer) const; + const char * pointerAtPosition(Position p); - void insertChar(char c, size_t index); - char removeChar(size_t index); - size_t removeRemainingLine(size_t index, int direction); + void insertText(const char * s, int textLength, char * location); + void insertSpacesAtLocation(int numberOfSpaces, char * location); + + CodePoint removeCodePoint(const char * * position); + size_t removeRemainingLine(const char * position, int direction); char operator[](size_t index) { assert(index < m_bufferSize); return m_buffer[index]; @@ -105,18 +108,19 @@ protected: const char * text() 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, int location) override; + bool insertTextAtLocation(const char * text, const char * location) override; void moveCursorGeo(int deltaX, int deltaY); - bool removeChar() override; + bool removeCodePoint() override; bool removeEndOfLine() override; bool removeStartOfLine(); protected: - KDRect characterFrameAtIndex(size_t index) const override; + KDRect glyphFrameAtPosition(const char * position) const override; Text m_text; }; ContentView * contentView() { return static_cast(TextInput::contentView()); } private: + static constexpr int k_indentationSpaces = 2; TextAreaDelegate * m_delegate; }; diff --git a/escher/include/escher/text_field.h b/escher/include/escher/text_field.h index 46a9c73f2..9077e2332 100644 --- a/escher/include/escher/text_field.h +++ b/escher/include/escher/text_field.h @@ -16,7 +16,8 @@ public: void setDelegates(InputEventHandlerDelegate * inputEventHandlerDelegate, TextFieldDelegate * delegate) { m_inputEventHandlerDelegate = inputEventHandlerDelegate; m_delegate = delegate; } void setDraftTextBuffer(char * draftTextBuffer); bool isEditing() const override; - size_t draftTextLength() const; + const char * draftTextBuffer() const { return const_cast(this)->m_contentView.draftTextBuffer(); } + size_t draftTextLength() const; //TODO keep ? void setText(const char * text); void setAlignment(float horizontalAlignment, float verticalAlignment); virtual void setEditing(bool isEditing, bool reinitDraftBuffer = true) override; @@ -40,7 +41,7 @@ protected: void drawRect(KDContext * ctx, KDRect rect) const override; bool isEditing() const { return m_isEditing; } const char * text() const override; - size_t editedTextLength() const override; + size_t editedTextLength() const override { return m_currentDraftTextLength; } //TODO keep ? char * textBuffer() { return m_textBuffer; } char * draftTextBuffer() { return m_draftTextBuffer; } int bufferSize() { return k_maxBufferSize; } @@ -48,12 +49,12 @@ protected: void setAlignment(float horizontalAlignment, float verticalAlignment); void setEditing(bool isEditing, bool reinitDraftBuffer); void reinitDraftTextBuffer(); - /* 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, int location) 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, const char * location) override; // TODO KDSize minimalSizeForOptimalDisplay() const override; - bool removeChar() override; + bool removeCodePoint() override; bool removeEndOfLine() override; void willModifyTextBuffer(); void didModifyTextBuffer(); @@ -65,11 +66,11 @@ protected: constexpr static int k_maxBufferSize = 152; private: void layoutSubviews() override; - KDRect characterFrameAtIndex(size_t index) const override; + KDRect glyphFrameAtPosition(const char * position) const override; bool m_isEditing; char * m_textBuffer; char * m_draftTextBuffer; - size_t m_currentDraftTextLength; + size_t m_currentDraftTextLength; //TODO keep ? size_t m_textBufferSize; float m_horizontalAlignment; float m_verticalAlignment; diff --git a/escher/include/escher/text_input.h b/escher/include/escher/text_input.h index f4a705c77..283d3db90 100644 --- a/escher/include/escher/text_input.h +++ b/escher/include/escher/text_input.h @@ -11,9 +11,9 @@ public: TextInput(Responder * parentResponder, View * contentView) : ScrollableView(parentResponder, contentView, this) {} void setFont(const KDFont * font) { contentView()->setFont(font); } const char * text() const { return nonEditableContentView()->text(); } - bool removeChar(); - size_t cursorLocation() const { return nonEditableContentView()->cursorLocation(); } - bool setCursorLocation(int location); + bool removeCodePoint(); + const char * cursorTextLocation() const { return nonEditableContentView()->cursorTextLocation(); } + bool setCursorTextLocation(const char * location); virtual void scrollToCursor(); protected: class ContentView : public View { @@ -22,29 +22,29 @@ protected: View(), m_cursorView(), m_font(font), - m_cursorIndex(0) + m_cursorTextLocation(nullptr) {} void setFont(const KDFont * font); const KDFont * font() const { return m_font; } - size_t cursorLocation() const { return m_cursorIndex; } - void setCursorLocation(int cursorLocation); + const char * cursorTextLocation() const { assert(m_cursorTextLocation != nullptr); return m_cursorTextLocation; } + void setCursorTextLocation(const char * cursorTextLocation); virtual const char * text() const = 0; - virtual bool insertTextAtLocation(const char * text, int location) = 0; - virtual bool removeChar() = 0; + virtual bool insertTextAtLocation(const char * text, const char * location) = 0; + virtual bool removeCodePoint() = 0; virtual bool removeEndOfLine() = 0; KDRect cursorRect(); protected: virtual void layoutSubviews() override; - virtual KDRect dirtyRectFromCursorPosition(size_t index, bool lineBreak) const; - void reloadRectFromCursorPosition(size_t index, bool lineBreak = false); - virtual KDRect characterFrameAtIndex(size_t index) const = 0; + void reloadRectFromPosition(const char * position, bool lineBreak = false); + virtual KDRect glyphFrameAtPosition(const char * position) const = 0; TextCursorView m_cursorView; const KDFont * m_font; - size_t m_cursorIndex; + const char * m_cursorTextLocation; + virtual KDRect dirtyRectFromPosition(const char * position, bool lineBreak) const; private: int numberOfSubviews() const override { return 1; } View * subviewAtIndex(int index) override { - assert(index == 1); + assert(index == 0); return &m_cursorView; } virtual size_t editedTextLength() const = 0; @@ -53,14 +53,14 @@ 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, int location); + bool insertTextAtLocation(const char * textBuffer, const char * location); bool removeEndOfLine(); ContentView * contentView() { return const_cast(nonEditableContentView()); } virtual const ContentView * nonEditableContentView() const = 0; private: - virtual void willSetCursorLocation(int * location) {} + virtual void willSetCursorTextLocation(const char * * location) {} virtual bool privateRemoveEndOfLine(); }; diff --git a/escher/include/escher/text_input_helpers.h b/escher/include/escher/text_input_helpers.h index 8eab24830..04718b4b5 100644 --- a/escher/include/escher/text_input_helpers.h +++ b/escher/include/escher/text_input_helpers.h @@ -6,11 +6,11 @@ namespace TextInputHelpers { -size_t CursorIndexInCommand(const char * text); -/* Returns the index of the cursor position in a Command, which is the smallest - * index between : - * - The first EmptyChar index (which is the position of the first argument) - * - The first empty quote +const char * CursorPositionInCommand(const char * text); +/* Returns the pointer to the char that should be right of the cursor, which is + * the first char between : + * - The first EmptyChar (which is the position of the first argument) + * - The char after the first empty quote * - The end of the text */ } diff --git a/escher/src/text_area.cpp b/escher/src/text_area.cpp index 408dd2dbf..997227404 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -1,15 +1,15 @@ #include #include #include +#include #include #include #include #include -static inline size_t min(size_t a, size_t b) { - return (a>b ? b : a); -} +static inline size_t minSize(size_t a, size_t b) { return a < b ? a : b; } +static inline size_t maxSize(size_t a, size_t b) { return a > b ? a : b; } /* TextArea */ @@ -21,26 +21,26 @@ TextArea::TextArea(Responder * parentResponder, View * contentView, const KDFont } bool TextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) { - int nextCursorLocation = cursorLocation(); + const char * nextCursorLocation = cursorTextLocation(); - size_t cursorIndexInCommand = TextInputHelpers::CursorIndexInCommand(text); + const char * cursorPositionInCommand = TextInputHelpers::CursorPositionInCommand(text); constexpr int bufferSize = TextField::maxBufferSize(); char buffer[bufferSize]; // Remove the Empty code points - UTF8Helper::CopyAndRemoveCodePoint(buffer, bufferSize, text, KDCodePointEmpty, &cursorIndexInCommand); + UTF8Helper::CopyAndRemoveCodePoint(buffer, bufferSize, text, KDCodePointEmpty, &cursorPositionInCommand); // Insert the text - if ((indentation && insertTextWithIndentation(buffer, cursorLocation())) || insertTextAtLocation(buffer, cursorLocation())) { + if ((indentation && insertTextWithIndentation(buffer, cursorTextLocation())) || insertTextAtLocation(buffer, cursorTextLocation())) { // Set the cursor location if (forceCursorRightOfText) { nextCursorLocation += strlen(buffer); } else { - nextCursorLocation += cursorIndexInCommand; + nextCursorLocation += cursorPositionInCommand - text; } } - setCursorLocation(nextCursorLocation); + setCursorTextLocation(nextCursorLocation); return true; } @@ -50,9 +50,20 @@ bool TextArea::handleEvent(Ion::Events::Event event) { } else if (handleBoxEvent(app(), event)) { return true; } else if (event == Ion::Events::Left) { - return setCursorLocation(cursorLocation()-1); + if (cursorTextLocation() <= text()) { + assert(cursorTextLocation() == text()); + return false; + } + UTF8Decoder decoder(text(), cursorTextLocation()); + decoder.previousCodePoint(); + return setCursorTextLocation(decoder.stringPosition()); } else if (event == Ion::Events::Right) { - return setCursorLocation(cursorLocation()+1); + if (*cursorTextLocation() == 0) { + return false; + } + UTF8Decoder decoder(text(), cursorTextLocation()); + decoder.nextCodePoint(); + return setCursorTextLocation(decoder.stringPosition()); } else if (event == Ion::Events::Up) { contentView()->moveCursorGeo(0, -1); } else if (event == Ion::Events::Down) { @@ -62,7 +73,7 @@ bool TextArea::handleEvent(Ion::Events::Event event) { } else if (event == Ion::Events::ShiftRight) { contentView()->moveCursorGeo(INT_MAX/2, 0); } else if (event == Ion::Events::Backspace) { - return removeChar(); + return removeCodePoint(); } else if (event.hasText()) { return handleEventWithText(event.text()); } else if (event == Ion::Events::EXE) { @@ -85,80 +96,75 @@ void TextArea::setText(char * textBuffer, size_t textBufferSize) { contentView()->moveCursorGeo(0, 0); } -bool TextArea::insertTextWithIndentation(const char * textBuffer, int location) { +bool TextArea::insertTextWithIndentation(const char * textBuffer, const char * location) { int indentation = indentationBeforeCursor(); - char spaceString[indentation+1]; - for (int i = 0; i < indentation; i++) { - spaceString[i] = ' '; + const char * previousChar = cursorTextLocation()-1; + if (previousChar >= const_cast