[escher] Fix text inputs so they use UTF8

This commit is contained in:
Léa Saviot
2019-01-18 16:41:39 +01:00
committed by Émilie Feral
parent 242bcda631
commit 5142c071df
24 changed files with 510 additions and 360 deletions

View File

@@ -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<char *>(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<char *>(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<char *>(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
}
}

View File

@@ -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;

View File

@@ -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(); };

View File

@@ -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<const char *>(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;
}

View File

@@ -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 (x<y ? x : y); }
static inline int min(int x, int y) { return x < y ? x : y; }
static inline KDColor TokenColor(mp_token_kind_t tokenKind) {
if (tokenKind == MP_TOKEN_STRING) {
@@ -164,13 +164,13 @@ void PythonTextArea::ContentView::drawLine(KDContext * ctx, int line, const char
}
}
KDRect PythonTextArea::ContentView::dirtyRectFromCursorPosition(size_t index, bool lineBreak) const {
KDRect PythonTextArea::ContentView::dirtyRectFromPosition(const char * position, bool lineBreak) const {
/* Mark the whole line as dirty.
* TextArea has a very conservative approach and only dirties the surroundings
* of the current character. That works for plain text, but when doing syntax
* highlighting, you may want to redraw the surroundings as well. For example,
* if editing "def foo" into "df foo", you'll want to redraw "df". */
KDRect baseDirtyRect = TextArea::ContentView::dirtyRectFromCursorPosition(index, lineBreak);
KDRect baseDirtyRect = TextArea::ContentView::dirtyRectFromPosition(position, lineBreak);
return KDRect(
bounds().x(),
baseDirtyRect.y(),

View File

@@ -28,7 +28,7 @@ protected:
void unloadSyntaxHighlighter();
void clearRect(KDContext * ctx, KDRect rect) const override;
void drawLine(KDContext * ctx, int line, const char * text, size_t length, int fromColumn, int toColumn) const override;
KDRect dirtyRectFromCursorPosition(size_t index, bool lineBreak) const override;
KDRect dirtyRectFromPosition(const char * position, bool lineBreak) const override;
private:
App * m_pythonDelegate;
};

View File

@@ -202,8 +202,11 @@ bool CalculationController::textFieldDidHandleEvent(::TextField * textField, boo
bool CalculationController::textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) {
return TextFieldDelegate::textFieldShouldFinishEditing(textField, event)
|| (event == Ion::Events::Right && textField->cursorLocation() == 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) {

View File

@@ -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) {

View File

@@ -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<char *>(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;

View File

@@ -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);

View File

@@ -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<ContentView *>(TextInput::contentView()); }
private:
static constexpr int k_indentationSpaces = 2;
TextAreaDelegate * m_delegate;
};

View File

@@ -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<TextField *>(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;

View File

@@ -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<ContentView *>(nonEditableContentView());
}
virtual const ContentView * nonEditableContentView() const = 0;
private:
virtual void willSetCursorLocation(int * location) {}
virtual void willSetCursorTextLocation(const char * * location) {}
virtual bool privateRemoveEndOfLine();
};

View File

@@ -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 */
}

View File

@@ -1,15 +1,15 @@
#include <escher/text_area.h>
#include <escher/clipboard.h>
#include <escher/text_input_helpers.h>
#include <kandinsky/unicode/utf8_decoder.h>
#include <kandinsky/unicode/utf8_helper.h>
#include <stddef.h>
#include <assert.h>
#include <limits.h>
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<TextArea *>(this)->contentView()->text() && *previousChar == ':') {
indentation += k_indentationSpaces;
}
spaceString[indentation] = 0;
int spaceStringSize = strlen(spaceString);
int textSize = strlen(textBuffer);
int totalIndentationSize = 0;
for (size_t i = 0; i < strlen(textBuffer); i++) {
for (size_t i = 0; i < textSize; i++) {
// No need to use the UTF8Decoder here, because we look for an ASCII char.
if (textBuffer[i] == '\n') {
totalIndentationSize+=spaceStringSize;
totalIndentationSize+= indentation;
}
}
if (contentView()->getText()->textLength() + textSize + totalIndentationSize >= contentView()->getText()->bufferSize() || textSize == 0) {
return false;
}
int currentLocation = location;
for (size_t i = 0; i < strlen(textBuffer); i++) {
const char charString[] = {textBuffer[i], 0};
insertTextAtLocation(charString, currentLocation++);
if (textBuffer[i] == '\n') {
insertTextAtLocation(spaceString, currentLocation);
currentLocation += strlen(spaceString);
insertTextAtLocation(textBuffer, location);
const char * currentLocation = location;
int currentIndex = location - contentView()->text();
for (size_t i = 0; i < textSize; i++) {
// No need to use the UTF8Decoder here, because we look for an ASCII char.
if (textBuffer[currentIndex + i] == '\n') {
const_cast<Text *>(contentView()->getText())->insertSpacesAtLocation(indentation, const_cast<char *>(currentLocation));
currentIndex+= indentation;
}
}
return true;
}
int TextArea::indentationBeforeCursor() const {
int charIndex = cursorLocation()-1;
const char * p = cursorTextLocation()-1;
int indentationSize = 0;
while (charIndex >= 0 && nonEditableContentView()->text()[charIndex] != '\n') {
if (nonEditableContentView()->text()[charIndex] == ' ') {
// No need to use the UTF8Decoder here, be cause we look for an ASCII char.
while (p >= const_cast<TextArea *>(this)->contentView()->text() && *p != '\n') {
if (*p == ' ') {
indentationSize++;
} else {
indentationSize = 0;
}
charIndex--;
p--;
}
return indentationSize;
}
/* TextArea::Text */
size_t TextArea::Text::indexAtPosition(Position p) {
const char * TextArea::Text::pointerAtPosition(Position p) {
assert(m_buffer != nullptr);
if (p.line() < 0) {
return 0;
return m_buffer;
}
int y = 0;
const char * endOfLastLine = nullptr;
for (Line l : *this) {
if (p.line() == y) {
size_t x = p.column() < 0 ? 0 : p.column();
x = min(x, l.length());
return l.text() - m_buffer + x;
return l.text() + minSize(l.charLength(), maxSize(p.column(), 0));
}
endOfLastLine = l.text() + l.length();
y++;
}
assert(endOfLastLine != nullptr && endOfLastLine >= m_buffer);
return endOfLastLine - m_buffer;
return m_buffer + strlen(m_buffer);
}
TextArea::Text::Position TextArea::Text::positionAtIndex(size_t index) const {
TextArea::Text::Position TextArea::Text::positionAtPointer(const char * p) const {
assert(m_buffer != nullptr);
assert(index < m_bufferSize);
const char * target = m_buffer + index;
assert(p <= m_buffer && p < m_buffer + m_bufferSize);
size_t y = 0;
for (Line l : *this) {
if (l.text() <= target && l.text() + l.length() >= target) {
size_t x = target - l.text();
if (l.contains(p)) {
size_t x = p - l.text();
return Position(x, y);
}
y++;
@@ -167,56 +173,91 @@ TextArea::Text::Position TextArea::Text::positionAtIndex(size_t index) const {
return Position(0, 0);
}
void TextArea::Text::insertChar(char c, size_t index) {
void TextArea::Text::insertText(const char * s, int textLength, char * location) {
assert(m_buffer != nullptr);
assert(index < m_bufferSize-1);
char previous = c;
for (size_t i=index; i<m_bufferSize; i++) {
char inserted = previous;
previous = m_buffer[i];
m_buffer[i] = inserted;
if (inserted == 0) {
break;
}
assert(location >= m_buffer && location < m_buffer + m_bufferSize - 1);
assert(strlen(m_buffer) + textLength < m_bufferSize);
size_t sizeToMove = strlen(location) + 1;
assert(location + textLength + sizeToMove <= m_buffer + m_bufferSize);
memmove(location + textLength, location, sizeToMove);
memmove(location, s, textLength);
}
void TextArea::Text::insertSpacesAtLocation(int numberOfSpaces, char * location) {
assert(m_buffer != nullptr);
assert(location >= m_buffer && location < m_buffer + m_bufferSize - 1);
assert(strlen(m_buffer) + numberOfSpaces < m_bufferSize);
size_t sizeToMove = strlen(location) + 1;
assert(location + numberOfSpaces + sizeToMove <= m_buffer + m_bufferSize);
memmove(location + numberOfSpaces, location, sizeToMove);
for (int i = 0; i < numberOfSpaces; i++) {
*(location+i) = ' ';
}
}
char TextArea::Text::removeChar(size_t index) {
CodePoint TextArea::Text::removeCodePoint(const char * * position) {
assert(m_buffer != nullptr);
assert(index < m_bufferSize-1);
char deletedChar = m_buffer[index];
for (size_t i=index; i<m_bufferSize; i++) {
m_buffer[i] = m_buffer[i+1];
assert(m_buffer <= *position && *position < m_buffer + m_bufferSize);
// Find the beginning of the previous code point
UTF8Decoder decoder(m_buffer, *position);
CodePoint deletedCodePoint = decoder.previousCodePoint();
const char * newCursorLocation = decoder.stringPosition();
assert(newCursorLocation < *position);
// Shift the buffer
int codePointSize = *position - newCursorLocation;
assert(newCursorLocation >= m_buffer);
for (size_t i = newCursorLocation - m_buffer; i < m_bufferSize; i++) {
m_buffer[i] = m_buffer[i + codePointSize];
if (m_buffer[i] == 0) {
break;
}
}
return deletedChar;
// Set the new cursor position
*position = newCursorLocation;
return deletedCodePoint;
}
size_t TextArea::Text::removeRemainingLine(size_t index, int direction) {
size_t TextArea::Text::removeRemainingLine(const char * location, int direction) {
assert(m_buffer != nullptr);
assert(index < m_bufferSize);
int jump = index;
while (m_buffer[jump] != '\n' && m_buffer[jump] != 0 && jump >= 0) {
jump += direction;
assert(location >= m_buffer && location < m_buffer + m_bufferSize);
UTF8Decoder decoder(m_buffer, location);
const char * codePointPosition = decoder.stringPosition();
CodePoint nextCodePoint = direction > 0 ? decoder.nextCodePoint() : decoder.previousCodePoint();
if (direction < 0) {
codePointPosition = decoder.stringPosition();
}
size_t delta = direction > 0 ? jump - index : index - jump;
while (nextCodePoint != '\n'
&& ((direction > 0 && nextCodePoint != 0)
|| (direction < 0 && codePointPosition >= m_buffer)))
{
if (direction > 0) {
codePointPosition = decoder.stringPosition();
}
nextCodePoint = direction > 0 ? decoder.nextCodePoint() : decoder.previousCodePoint();
if (direction < 0) {
codePointPosition = decoder.stringPosition();
}
}
size_t delta = direction > 0 ? codePointPosition - location : location - codePointPosition;
if (delta == 0) {
return 0;
}
/* We stop at m_bufferSize-1 because:
* - if direction > 0: jump >= k+1 so we will reach the 0 before m_bufferSize-1
* - if direction < 0: k+1 will reach m_bufferSize. */
for (size_t k = index; k < m_bufferSize-1; k++) {
if (direction > 0) {
m_buffer[k] = m_buffer[jump++];
} else {
m_buffer[++jump] = m_buffer[k+1];
}
if (m_buffer[k] == 0 || m_buffer[k+1] == 0) {
char * dst = const_cast<char* >(direction > 0 ? location : codePointPosition);
char * src = const_cast<char* >(direction > 0 ? codePointPosition : location);
assert(src > dst);
for (size_t index = src - m_buffer; index < m_bufferSize; index++) {
*dst = *src;
if (*src == 0) {
return delta;
}
dst++;
src++;
}
assert(false);
return 0;
@@ -226,41 +267,47 @@ size_t TextArea::Text::removeRemainingLine(size_t index, int direction) {
TextArea::Text::Line::Line(const char * text) :
m_text(text),
m_length(0)
m_charLength(0)
{
if (m_text != nullptr) {
// No need to use the UTF8Decoder here, because we look for an ASCII char.
while (*text != 0 && *text != '\n') {
text++;
}
m_length = text-m_text;
m_charLength = text - m_text;
}
}
KDCoordinate TextArea::Text::Line::glyphWidth(const KDFont * const font) const {
return font->stringSizeUntil(m_text, m_text + m_charLength).width();
}
bool TextArea::Text::Line::contains(const char * c) const {
return (c >= m_text) && (c < m_text + m_length);
return (c >= m_text) && (c < m_text + m_charLength);
}
/* TextArea::Text::LineIterator */
TextArea::Text::LineIterator & TextArea::Text::LineIterator::operator++() {
const char * last = m_line.text() + m_line.length();
const char * last = m_line.text() + m_line.charLength();
m_line = Line(*last == 0 ? nullptr : last+1);
return *this;
}
/* TextArea::Text::Position */
TextArea::Text::Position TextArea::Text::span() const {
KDSize TextArea::Text::span(const KDFont * const font) const {
assert(m_buffer != nullptr);
size_t width = 0;
size_t height = 0;
KDCoordinate width = 0;
int numberOfLines = 0;
for (Line l : *this) {
if (l.length() > width) {
width = l.length();
KDCoordinate lineWidth = l.glyphWidth(font);
if (lineWidth > width) {
width = lineWidth;
}
height++;
numberOfLines++;
}
return Position(width, height);
return KDSize(width, numberOfLines * font->glyphSize().height());
}
/* TextArea::ContentView */
@@ -285,8 +332,9 @@ void TextArea::ContentView::drawRect(KDContext * ctx, KDRect rect) const {
int y = 0;
for (Text::Line line : m_text) {
if (y >= topLeft.line() && y <= bottomRight.line() && topLeft.column() < (int)line.length()) {
drawLine(ctx, y, line.text(), line.length(), topLeft.column(), bottomRight.column());
KDCoordinate width = line.glyphWidth(m_font);
if (y >= topLeft.line() && y <= bottomRight.line() && topLeft.column() < (int)width) {
drawLine(ctx, y, line.text(), width, topLeft.column(), bottomRight.column());
}
y++;
}
@@ -305,77 +353,97 @@ void TextArea::ContentView::drawStringAt(KDContext * ctx, int line, int column,
}
KDSize TextArea::ContentView::minimalSizeForOptimalDisplay() const {
KDSize glyphSize = m_font->glyphSize();
Text::Position span = m_text.span();
KDSize span = m_text.span(m_font);
return KDSize(
/* We take into account the space required to draw a cursor at the end of
* line by adding glyphSize.width() to the width. */
glyphSize.width() * (span.column()+1),
glyphSize.height() * span.line()
span.width() + m_font->glyphSize().width(),
span.height()
);
}
void TextArea::TextArea::ContentView::setText(char * textBuffer, size_t textBufferSize) {
m_text.setText(textBuffer, textBufferSize);
m_cursorIndex = 0;
m_cursorTextLocation = text();
}
bool TextArea::TextArea::ContentView::insertTextAtLocation(const char * text, int location) {
bool TextArea::TextArea::ContentView::insertTextAtLocation(const char * text, const char * location) {
int textSize = strlen(text);
if (m_text.textLength() + textSize >= m_text.bufferSize() || textSize == 0) {
return false;
}
bool lineBreak = false;
int currentLocation = location;
while (*text != 0) {
lineBreak |= *text == '\n';
m_text.insertChar(*text++, currentLocation++);
// Scan for \n and 0
const char * textScanner = text;
while (*textScanner != 0) {
textScanner++;
lineBreak |= *textScanner == '\n';
}
reloadRectFromCursorPosition(currentLocation-1, lineBreak);
assert(*textScanner == 0);
m_text.insertText(text, textScanner - text, const_cast<char *>(location));
reloadRectFromPosition(location/*-1 TODO LEA */, lineBreak);
return true;
}
bool TextArea::TextArea::ContentView::removeChar() {
if (cursorLocation() <= 0) {
bool TextArea::TextArea::ContentView::removeCodePoint() {
if (cursorTextLocation() <= text()) {
assert(cursorTextLocation() == text());
return false;
}
bool lineBreak = false;
assert(m_cursorIndex > 0);
lineBreak = m_text.removeChar(--m_cursorIndex) == '\n';
const char * cursorLoc = cursorTextLocation();
lineBreak = m_text.removeCodePoint(&cursorLoc) == '\n';
setCursorTextLocation(cursorLoc); // Update the cursor
layoutSubviews(); // Reposition the cursor
reloadRectFromCursorPosition(cursorLocation(), lineBreak);
reloadRectFromPosition(cursorTextLocation(), lineBreak);
return true;
}
bool TextArea::ContentView::removeEndOfLine() {
size_t removedLine = m_text.removeRemainingLine(cursorLocation(), 1);
size_t removedLine = m_text.removeRemainingLine(cursorTextLocation(), 1);
if (removedLine > 0) {
layoutSubviews();
reloadRectFromCursorPosition(cursorLocation(), false);
reloadRectFromPosition(cursorTextLocation(), false);
return true;
}
return false;
}
bool TextArea::ContentView::removeStartOfLine() {
if (cursorLocation() <= 0) {
if (cursorTextLocation() <= text()) {
assert(cursorTextLocation() == text());
return false;
}
size_t removedLine = m_text.removeRemainingLine(cursorLocation()-1, -1);
size_t removedLine = m_text.removeRemainingLine(cursorTextLocation(), -1); //TODO LEA Before : cursorTextLocation()-1
if (removedLine > 0) {
assert(m_cursorIndex >= removedLine);
setCursorLocation(cursorLocation()-removedLine);
reloadRectFromCursorPosition(cursorLocation(), false);
assert(cursorTextLocation() >= text() + removedLine);
setCursorTextLocation(cursorTextLocation() - removedLine);
reloadRectFromPosition(cursorTextLocation(), false);
return true;
}
return false;
}
KDRect TextArea::ContentView::characterFrameAtIndex(size_t index) const {
KDRect TextArea::ContentView::glyphFrameAtPosition(const char * position) const {
KDSize glyphSize = m_font->glyphSize();
Text::Position p = m_text.positionAtIndex(index);
Text::Position p = m_text.positionAtPointer(position);
KDCoordinate x = 0;
bool found = false;
int y = 0;
for (Text::Line l : m_text) {
if (p.line() == y) {
x = m_font->stringSizeUntil(l.text(), position).width();
found = true;
break;
}
y++;
}
assert(found);
return KDRect(
p.column() * glyphSize.width(),
x,
p.line() * glyphSize.height(),
glyphSize.width(),
glyphSize.height()
@@ -383,6 +451,6 @@ KDRect TextArea::ContentView::characterFrameAtIndex(size_t index) const {
}
void TextArea::ContentView::moveCursorGeo(int deltaX, int deltaY) {
Text::Position p = m_text.positionAtIndex(m_cursorIndex);
setCursorLocation(m_text.indexAtPosition(Text::Position(p.column() + deltaX, p.line() + deltaY)));
Text::Position p = m_text.positionAtPointer(cursorTextLocation());
setCursorTextLocation(m_text.pointerAtPosition(Text::Position(p.column() + deltaX, p.line() + deltaY)));
}

View File

@@ -1,9 +1,12 @@
#include <escher/text_field.h>
#include <escher/text_input_helpers.h>
#include <escher/clipboard.h>
#include <kandinsky/unicode/utf8_decoder.h>
#include <kandinsky/unicode/utf8_helper.h>
#include <assert.h>
static inline int minInt(int x, int y) { return x < y ? x : y; }
/* TextField::ContentView */
TextField::ContentView::ContentView(char * textBuffer, char * draftTextBuffer, size_t textBufferSize, const KDFont * font, float horizontalAlignment, float verticalAlignment, KDColor textColor, KDColor backgroundColor) :
@@ -19,6 +22,7 @@ TextField::ContentView::ContentView(char * textBuffer, char * draftTextBuffer, s
m_backgroundColor(backgroundColor)
{
assert(m_textBufferSize <= k_maxBufferSize);
m_cursorTextLocation = draftTextBuffer;
}
void TextField::ContentView::setBackgroundColor(KDColor backgroundColor) {
@@ -41,31 +45,24 @@ void TextField::ContentView::drawRect(KDContext * ctx, KDRect rect) const {
backgroundColor = KDColorWhite;
}
ctx->fillRect(bounds(), backgroundColor);
ctx->drawString(text(), characterFrameAtIndex(0).origin(), m_font, m_textColor, backgroundColor);
ctx->drawString(text(), glyphFrameAtPosition(text()).origin(), m_font, m_textColor, backgroundColor);
}
const char * TextField::ContentView::text() const {
if (m_isEditing) {
return const_cast<const char *>(m_draftTextBuffer);
}
return const_cast<const char *>(m_textBuffer);
}
size_t TextField::ContentView::editedTextLength() const {
return m_currentDraftTextLength;
return const_cast<const char *>(m_isEditing ? m_draftTextBuffer : m_textBuffer);
}
void TextField::ContentView::setText(const char * text) {
reloadRectFromCursorPosition(0);
reloadRectFromPosition(m_textBuffer);
size_t textRealLength = strlen(text);
int textLength = textRealLength >= m_textBufferSize ? m_textBufferSize-1 : textRealLength;
int textLength = minInt(textRealLength, m_textBufferSize - 1);
// Copy the text
strlcpy(m_isEditing ? m_draftTextBuffer : m_textBuffer, text, m_textBufferSize);
// Update the draft text length and cursor location
if (m_isEditing || m_textBuffer == m_draftTextBuffer) {
m_currentDraftTextLength = textLength;
}
reloadRectFromCursorPosition(0);
reloadRectFromPosition(m_textBuffer);
}
void TextField::ContentView::setAlignment(float horizontalAlignment, float verticalAlignment) {
@@ -88,82 +85,109 @@ void TextField::ContentView::setEditing(bool isEditing, bool reinitDrafBuffer) {
}
void TextField::ContentView::reinitDraftTextBuffer() {
setCursorLocation(0);
setCursorTextLocation(text());
m_draftTextBuffer[0] = 0;
m_currentDraftTextLength = 0;
}
bool TextField::ContentView::insertTextAtLocation(const char * text, int location) {
bool TextField::ContentView::insertTextAtLocation(const char * text, const char * location) {
assert(m_isEditing);
int textSize = strlen(text);
if (m_currentDraftTextLength + textSize >= m_textBufferSize || textSize == 0) {
return false;
}
for (int k = m_currentDraftTextLength; k >= location && k >= 0; k--) {
m_draftTextBuffer[k+textSize] = m_draftTextBuffer[k];
}
memmove(const_cast<char *>(location + textSize), location, (m_draftTextBuffer + m_currentDraftTextLength + 1) - location);
// Caution! One char will be overridden by the null-terminating char of strlcpy
int overridenCharLocation = location + strlen(text);
char overridenChar = m_draftTextBuffer[overridenCharLocation];
strlcpy(&m_draftTextBuffer[location], text, m_textBufferSize-location);
assert(overridenCharLocation < m_textBufferSize);
m_draftTextBuffer[overridenCharLocation] = overridenChar;
char * overridenCharLocation = const_cast<char *>(location + strlen(text));
char overridenChar = *overridenCharLocation;
strlcpy(const_cast<char *>(location), text, (m_draftTextBuffer + m_textBufferSize) - location);
assert(overridenCharLocation < m_draftTextBuffer + m_textBufferSize);
*overridenCharLocation = overridenChar;
m_currentDraftTextLength += textSize;
for (size_t i = 0; i < m_currentDraftTextLength; i++) {
if (m_draftTextBuffer[i] == '\n') {
m_draftTextBuffer[i] = 0;
m_currentDraftTextLength = i;
UTF8Decoder decoder(m_draftTextBuffer);
const char * codePointPointer = decoder.stringPosition();
CodePoint codePoint = decoder.nextCodePoint();
assert(!codePoint.isCombining());
while (codePoint != KDCodePointNull) {
assert(codePointPointer < m_draftTextBuffer + m_textBufferSize);
if (codePoint == '\n') {
*(const_cast<char *>(codePointPointer)) = 0;
m_currentDraftTextLength = codePointPointer - m_draftTextBuffer;
break;
}
codePointPointer = decoder.stringPosition();
codePoint = decoder.nextCodePoint();
}
reloadRectFromCursorPosition((m_horizontalAlignment == 0.0f ? location : 0));
reloadRectFromPosition(m_horizontalAlignment == 0.0f ? location : m_draftTextBuffer);
return true;
}
KDSize TextField::ContentView::minimalSizeForOptimalDisplay() const {
KDSize glyphSize = m_font->glyphSize();
KDSize stringSize = m_font->stringSize(text());
assert(stringSize.height() == m_font->glyphSize().height());
if (m_isEditing) {
return KDSize(glyphSize.width()*strlen(text())+m_cursorView.minimalSizeForOptimalDisplay().width(), glyphSize.height());
return KDSize(stringSize.width() + m_cursorView.minimalSizeForOptimalDisplay().width(), stringSize.height());
}
return KDSize(glyphSize.width()*strlen(text()), glyphSize.height());
return stringSize;
}
bool TextField::ContentView::removeChar() {
if (cursorLocation() <= 0) {
bool TextField::ContentView::removeCodePoint() {
assert(m_isEditing);
if (cursorTextLocation() <= m_draftTextBuffer) {
assert(cursorTextLocation() == m_draftTextBuffer);
return false;
}
m_currentDraftTextLength--;
UTF8Decoder decoder(m_draftTextBuffer, cursorTextLocation());
decoder.previousCodePoint();
const char * newCursorLocation = decoder.stringPosition();
assert(newCursorLocation < cursorTextLocation());
int removedCodePointLength = cursorTextLocation() - newCursorLocation;
m_currentDraftTextLength-= removedCodePointLength;
if (m_horizontalAlignment > 0.0f) {
reloadRectFromCursorPosition(0);
reloadRectFromPosition(m_draftTextBuffer);
}
setCursorLocation(cursorLocation()-1);
if( m_horizontalAlignment == 0.0f) {
reloadRectFromCursorPosition(cursorLocation());
setCursorTextLocation(newCursorLocation);
if (m_horizontalAlignment == 0.0f) {
reloadRectFromPosition(cursorTextLocation());
}
for (size_t k = cursorLocation(); k < m_currentDraftTextLength; k++) {
m_draftTextBuffer[k] = m_draftTextBuffer[k+1];
for (char * k = const_cast<char *>(cursorTextLocation()); k < m_draftTextBuffer + m_currentDraftTextLength + removedCodePointLength; k++) {
*k = *(k+removedCodePointLength);
if (*k == 0) {
break;
}
}
m_draftTextBuffer[m_currentDraftTextLength] = 0;
assert(m_draftTextBuffer[m_currentDraftTextLength] == 0);
layoutSubviews();
return true;
}
bool TextField::ContentView::removeEndOfLine() {
if (m_currentDraftTextLength == cursorLocation()) {
assert(m_isEditing);
if (m_currentDraftTextLength == cursorTextLocation() - m_draftTextBuffer) {
return false;
}
reloadRectFromCursorPosition((m_horizontalAlignment == 0.0f ? cursorLocation() : 0));
m_currentDraftTextLength = cursorLocation();
m_draftTextBuffer[cursorLocation()] = 0;
reloadRectFromPosition(m_horizontalAlignment == 0.0f ? cursorTextLocation() : m_draftTextBuffer);
m_currentDraftTextLength = cursorTextLocation() - m_draftTextBuffer;
*(const_cast<char *>(cursorTextLocation())) = 0;
layoutSubviews();
return true;
}
void TextField::ContentView::willModifyTextBuffer() {
assert(m_isEditing);
/* This method should be called when the buffer is modified outside the
* content view, for instance from the textfield directly. */
reloadRectFromCursorPosition(0);
reloadRectFromPosition(m_draftTextBuffer);
}
void TextField::ContentView::didModifyTextBuffer() {
@@ -181,11 +205,16 @@ void TextField::ContentView::layoutSubviews() {
TextInput::ContentView::layoutSubviews();
}
KDRect TextField::ContentView::characterFrameAtIndex(size_t index) const {
KDRect TextField::ContentView::glyphFrameAtPosition(const char * position) const {
assert(position != nullptr);
KDSize glyphSize = m_font->glyphSize();
KDSize textSize = m_font->stringSize(text());
KDCoordinate cursorWidth = m_cursorView.minimalSizeForOptimalDisplay().width();
return KDRect(m_horizontalAlignment*(m_frame.width() - textSize.width()-cursorWidth)+ index * glyphSize.width(), m_verticalAlignment*(m_frame.height() - glyphSize.height()), glyphSize);
KDCoordinate horizontalOffset = m_horizontalAlignment == 0.0f ? 0.0f :
m_horizontalAlignment * (m_frame.width() - m_font->stringSize(text()).width() - cursorWidth);
return KDRect(
horizontalOffset + m_font->stringSizeUntil(text(), position).width(),
m_verticalAlignment * (m_frame.height() - glyphSize.height()),
glyphSize);
}
/* TextField */
@@ -228,7 +257,7 @@ void TextField::setText(const char * text) {
m_contentView.setText(text);
/* Set the cursor location here and not in ContentView::setText so that
* TextInput::willSetCursorLocation is called. */
setCursorLocation(strlen(text));
setCursorTextLocation(m_contentView.draftTextBuffer()+strlen(text));
}
void TextField::setAlignment(float horizontalAlignment, float verticalAlignment) {
@@ -252,7 +281,7 @@ bool TextField::privateHandleEvent(Ion::Events::Event event) {
}
if (isEditing() && shouldFinishEditing(event)) {
char bufferText[ContentView::k_maxBufferSize];
int cursorLoc = cursorLocation();
const char * cursorLoc = cursorTextLocation();
if (m_hasTwoBuffers) {
strlcpy(bufferText, m_contentView.textBuffer(), ContentView::k_maxBufferSize);
strlcpy(m_contentView.textBuffer(), m_contentView.draftTextBuffer(), m_contentView.bufferSize());
@@ -272,21 +301,26 @@ bool TextField::privateHandleEvent(Ion::Events::Event event) {
}
setEditing(true, false);
if (m_hasTwoBuffers) {
/* if the text was refused (textInputDidFinishEditing returned false, we
/* If the text was refused (textInputDidFinishEditing returned false, we
* reset the textfield in the same state as before */
setText(m_contentView.textBuffer());
strlcpy(m_contentView.textBuffer(), bufferText, ContentView::k_maxBufferSize);
setCursorLocation(cursorLoc);
setCursorTextLocation(cursorLoc);
}
return true;
}
/* if move event was not caught before nor by textFieldShouldFinishEditing,
* we handle it here to avoid bubbling the event up. */
if ((event == Ion::Events::Up || event == Ion::Events::Down || event == Ion::Events::Left || event == Ion::Events::Right) && isEditing()) {
/* If a move event was not caught before, we handle it here to avoid bubbling
* the event up. */
if (isEditing()
&& (event == Ion::Events::Up
|| event == Ion::Events::Down
|| event == Ion::Events::Left
|| event == Ion::Events::Right))
{
return true;
}
if (event == Ion::Events::Backspace && isEditing()) {
return removeChar();
return removeCodePoint();
}
if (event == Ion::Events::Back && isEditing()) {
setEditing(false, m_hasTwoBuffers);
@@ -317,29 +351,31 @@ CodePoint TextField::XNTCodePoint(CodePoint defaultXNTCodePoint) {
{ "diff", 'x' }, { "int", 'x' },
{ "product", 'n' }, { "sum", 'n' }
};
// Let's assume everything before the cursor is nested correctly, which is reasonable if the expression is being entered left-to-right.
/* Let's assume everything before the cursor is nested correctly, which is
* reasonable if the expression is being entered left-to-right. */
const char * text = this->text();
/* TODO LEA
size_t location = cursorLocation();
const char * location = cursorTextLocation();
UTF8Decoder decoder(text, text + (location - m_contentView.draftTextBuffer()));
unsigned level = 0;
while (location >= 1) {
location--;
switch (text[location]) {
while (location > m_contentView.draftTextBuffer()) {
CodePoint c = decoder.previousCodePoint();
location = decoder.stringPosition();
switch (c) {
case '(':
// Check if we are skipping to the next matching '('.
if (level) {
if (level > 0) {
level--;
break;
}
// Skip over whitespace.
while (location >= 1 && text[location-1] == ' ') {
location--;
while (location > m_contentView.draftTextBuffer() && decoder.previousCodePoint() == ' ') {
location = decoder.stringPosition();
}
// We found the next innermost function we are currently in.
for (size_t i = 0; i < sizeof(sFunctions)/sizeof(sFunctions[0]); i++) {
const char * name = sFunctions[i].name;
size_t length = strlen(name);
if (location >= length && memcmp(&text[location-length], name, length) == 0) {
if (location >= (m_contentView.draftTextBuffer() + length) && memcmp(&text[(location - m_contentView.draftTextBuffer()) - length], name, length) == 0) {
return sFunctions[i].xnt;
}
}
@@ -356,7 +392,7 @@ CodePoint TextField::XNTCodePoint(CodePoint defaultXNTCodePoint) {
break;
}
}
*/
// Fallback to the default
return defaultXNTCodePoint;
}
@@ -390,17 +426,22 @@ void TextField::scrollToCursor() {
}
bool TextField::privateHandleMoveEvent(Ion::Events::Event event) {
if (event == Ion::Events::Left && isEditing() && cursorLocation() > 0) {
return setCursorLocation(cursorLocation()-1);
assert(isEditing());
if (event == Ion::Events::Left && isEditing() && cursorTextLocation() > m_contentView.draftTextBuffer()) {
UTF8Decoder decoder(m_contentView.draftTextBuffer(), cursorTextLocation());
decoder.previousCodePoint();
return setCursorTextLocation(decoder.stringPosition());
}
if (event == Ion::Events::ShiftLeft && isEditing()) {
return setCursorLocation(0);
return setCursorTextLocation(m_contentView.draftTextBuffer());
}
if (event == Ion::Events::Right && isEditing() && cursorLocation() < draftTextLength()) {
return setCursorLocation(cursorLocation()+1);
if (event == Ion::Events::Right && isEditing() && cursorTextLocation() < m_contentView.draftTextBuffer() + draftTextLength()) {
UTF8Decoder decoder(m_contentView.draftTextBuffer(), cursorTextLocation());
decoder.nextCodePoint();
return setCursorTextLocation(decoder.stringPosition());
}
if (event == Ion::Events::ShiftRight && isEditing()) {
return setCursorLocation(draftTextLength());
return setCursorTextLocation(m_contentView.draftTextBuffer() + draftTextLength());
}
return false;
}
@@ -412,8 +453,11 @@ bool TextField::handleEventWithText(const char * eventText, bool indentation, bo
setEditing(true);
}
assert(isEditing());
if (eventText[0] == 0) {
setCursorLocation(0);
assert(false); // TODO LEA Does this ever happen?
setCursorTextLocation(m_contentView.draftTextBuffer());
return m_delegate->textFieldDidHandleEvent(this, true, previousTextLength != 0);
}
@@ -422,19 +466,19 @@ bool TextField::handleEventWithText(const char * eventText, bool indentation, bo
char buffer[bufferSize];
UTF8Helper::CopyAndRemoveCodePoint(buffer, bufferSize, eventText, KDCodePointEmpty);
int nextCursorLocation = draftTextLength();
if (insertTextAtLocation(buffer, cursorLocation())) {
const char * nextCursorLocation = m_contentView.draftTextBuffer() + draftTextLength();
if (insertTextAtLocation(buffer, cursorTextLocation())) {
/* 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();
nextCursorLocation = cursorTextLocation();
if (forceCursorRightOfText) {
nextCursorLocation+= strlen(buffer);
} else {
nextCursorLocation+= TextInputHelpers::CursorIndexInCommand(eventText);
nextCursorLocation+= TextInputHelpers::CursorPositionInCommand(eventText) - eventText;
}
}
setCursorLocation(nextCursorLocation);
setCursorTextLocation(nextCursorLocation);
return m_delegate->textFieldDidHandleEvent(this, true, strlen(text()) != previousTextLength);
}

View File

@@ -3,10 +3,14 @@
/* TextInput::ContentView */
void TextInput::ContentView::setCursorLocation(int location) {
assert(location >= 0);
int adjustedLocation = location > (signed int)editedTextLength() ? (signed int)editedTextLength() : location;
m_cursorIndex = adjustedLocation;
static inline const char * min(const char * x, const char * y) { return x < y ? x : y; } // TODO LEA check this compiles OK
static inline const char * max(const char * x, const char * y) { return x > y ? x : y; } // TODO LEA check this compiles OK
void TextInput::ContentView::setCursorTextLocation(const char * location) {
assert(location != nullptr);
assert(location >= text());
const char * adjustedLocation = min(location, text() + editedTextLength()); // TODO LEA check this complies OK
m_cursorTextLocation = adjustedLocation;
layoutSubviews();
}
@@ -16,30 +20,38 @@ void TextInput::ContentView::setFont(const KDFont * font) {
}
KDRect TextInput::ContentView::cursorRect() {
return characterFrameAtIndex(m_cursorIndex);
return glyphFrameAtPosition(m_cursorTextLocation);
}
void TextInput::ContentView::layoutSubviews() {
m_cursorView.setFrame(cursorRect());
}
KDRect TextInput::ContentView::dirtyRectFromCursorPosition(size_t index, bool lineBreak) const {
KDRect charRect = characterFrameAtIndex(index);
KDRect dirtyRect = KDRect(charRect.x(), charRect.y(), bounds().width() - charRect.x(), charRect.height());
void TextInput::ContentView::reloadRectFromPosition(const char * position, bool lineBreak) {
markRectAsDirty(dirtyRectFromPosition(position, lineBreak));
}
KDRect TextInput::ContentView::dirtyRectFromPosition(const char * position, bool lineBreak) const {
KDRect glyphRect = glyphFrameAtPosition(position);
KDRect dirtyRect = KDRect(
glyphRect.x(),
glyphRect.y(),
bounds().width() - glyphRect.x(),
glyphRect.height());
if (lineBreak) {
dirtyRect = dirtyRect.unionedWith(KDRect(0, charRect.bottom()+1, bounds().width(), bounds().height()-charRect.bottom()-1));
dirtyRect = dirtyRect.unionedWith(
KDRect(0,
glyphRect.bottom() + 1,
bounds().width(),
bounds().height() - glyphRect.bottom() - 1));
}
return dirtyRect;
}
void TextInput::ContentView::reloadRectFromCursorPosition(size_t index, bool lineBreak) {
markRectAsDirty(dirtyRectFromCursorPosition(index, lineBreak));
}
/* TextInput */
bool TextInput::removeChar() {
contentView()->removeChar();
bool TextInput::removeCodePoint() {
contentView()->removeCodePoint();
scrollToCursor();
return true;
}
@@ -56,15 +68,16 @@ void TextInput::scrollToCursor() {
scrollToContentRect(contentView()->cursorRect(), true);
}
bool TextInput::setCursorLocation(int location) {
int adjustedLocation = location < 0 ? 0 : location;
willSetCursorLocation(&adjustedLocation);
contentView()->setCursorLocation(adjustedLocation);
bool TextInput::setCursorTextLocation(const char * location) {
assert(location != nullptr);
const char * adjustedLocation = max(location, text());
willSetCursorTextLocation(&adjustedLocation);
contentView()->setCursorTextLocation(adjustedLocation);
scrollToCursor();
return true;
}
bool TextInput::insertTextAtLocation(const char * text, int location) {
bool TextInput::insertTextAtLocation(const char * text, const char * location) {
if (contentView()->insertTextAtLocation(text, location)) {
/* We layout the scrollable view before scrolling to cursor because the
* content size might have changed. */

View File

@@ -4,34 +4,28 @@
namespace TextInputHelpers {
size_t CursorIndexInCommand(const char * text) {
size_t index = 0;
const char * CursorPositionInCommand(const char * text) {
UTF8Decoder decoder(text);
const char * currentPointer = text;
CodePoint codePoint = decoder.nextCodePoint();
const char * nextPointer = decoder.stringPosition();
while (codePoint != KDCodePointNull) {
if (codePoint == KDCodePointEmpty) {
return index;
return currentPointer;
}
//TODO make sure changing empty / ' order was OK
if (codePoint == '\'') {
index+= nextPointer - currentPointer;
currentPointer = nextPointer;
currentPointer = decoder.stringPosition();
codePoint = decoder.nextCodePoint();
nextPointer = decoder.stringPosition();
if (codePoint == '\'') {
return index;
return currentPointer;
}
// Continue because we already incremented codePoint
continue;
}
index+= nextPointer - currentPointer;
currentPointer = nextPointer;
currentPointer = decoder.stringPosition();
codePoint = decoder.nextCodePoint();
nextPointer = decoder.stringPosition();
}
return index;
return currentPointer;
}
}

View File

@@ -32,7 +32,10 @@ public:
static constexpr const KDFont * LargeFont = &privateLargeFont;
static constexpr const KDFont * SmallFont = &privateSmallFont;
KDSize stringSize(const char * text) const;
KDSize stringSize(const char * text) const {
return stringSizeUntil(text, nullptr);
}
KDSize stringSizeUntil(const char * text, const char * limit) const;
union GlyphBuffer {
public:

View File

@@ -1,8 +1,9 @@
#ifndef KANDINSKY_UNICODE_UTF8_DECODER_H
#define KANDINSKY_UNICODE_UTF8_DECODER_H
#include <stddef.h>
#include "code_point.h"
#include <stddef.h>
#include <assert.h>
/* UTF-8 encodes all valid code points using at most 4 bytes (= 28 bits), the
* lowest codes being equal to ASCII codes. There are less than 2^21 different
@@ -17,13 +18,20 @@
class UTF8Decoder {
public:
UTF8Decoder(const char * string) : m_string(string) {}
UTF8Decoder(const char * string, const char * initialPosition = nullptr) :
m_string(string),
m_stringPosition(initialPosition == nullptr ? string : initialPosition)
{
assert(m_string != nullptr);
}
CodePoint nextCodePoint();
const char * stringPosition() const { return m_string; }
CodePoint previousCodePoint();
const char * stringPosition() const { return m_stringPosition; }
static size_t CharSizeOfCodePoint(CodePoint c);
static size_t CodePointToChars(CodePoint c, char * buffer, int bufferSize);
private:
const char * m_string;
const char * const m_string;
const char * m_stringPosition;
};
#endif

View File

@@ -10,7 +10,7 @@ const char * CodePointSearch(const char * s, CodePoint c);
/* CopyAndRemoveCodePoint copies src into dst while removing all code points c.
* It also updates an index that should be lower if code points where removed
* before it. */
void CopyAndRemoveCodePoint(char * dst, size_t dstSize, const char * src, CodePoint c, size_t * indexToDUpdate = nullptr);
void CopyAndRemoveCodePoint(char * dst, size_t dstSize, const char * src, CodePoint c, const char * * indexToDUpdate = nullptr);
};

View File

@@ -5,25 +5,26 @@
constexpr static int k_tabCharacterWidth = 4;
KDSize KDFont::stringSize(const char * text) const {
KDSize KDFont::stringSizeUntil(const char * text, const char * limit) const {
if (text == nullptr) {
return KDSizeZero;
}
KDSize stringSize = KDSize(0, m_glyphSize.height());
UTF8Decoder decoder(text);
const char * currentStringPosition = decoder.stringPosition();
CodePoint codePoint = decoder.nextCodePoint();
while (codePoint != KDCodePointNull) {
while (codePoint != KDCodePointNull && (limit == nullptr || currentStringPosition < limit)) {
KDSize cSize = KDSize(m_glyphSize.width(), 0);
if (codePoint == KDCodePointLineFeed) {
cSize = KDSize(0, m_glyphSize.height());
codePoint = decoder.nextCodePoint();
} else if (codePoint == KDCodePointTabulation) {
cSize = KDSize(k_tabCharacterWidth*m_glyphSize.width(), 0);
cSize = KDSize(k_tabCharacterWidth * m_glyphSize.width(), 0);
} else if (codePoint.isCombining()) {
cSize = KDSizeZero;
}
stringSize = KDSize(stringSize.width()+cSize.width(), stringSize.height()+cSize.height());
stringSize = KDSize(stringSize.width() + cSize.width(), stringSize.height() + cSize.height());
currentStringPosition = decoder.stringPosition();
codePoint = decoder.nextCodePoint();
}
return stringSize;

View File

@@ -16,15 +16,45 @@ static inline uint8_t last_k_bits(uint8_t value, uint8_t bits) {
}
CodePoint UTF8Decoder::nextCodePoint() {
int leadingOnes = leading_ones(*m_string);
uint32_t result = last_k_bits(*m_string++, 8-leadingOnes-1);
for (int i=0; i<(leadingOnes-1); i++) {
assert(m_stringPosition == m_stringPosition || *(m_stringPosition - 1) != 0);
int leadingOnes = leading_ones(*m_stringPosition);
uint32_t result = last_k_bits(*m_stringPosition++, 8-leadingOnes-1);
for (int i = 0; i < leadingOnes - 1; i++) {
result <<= 6;
result += (*m_string++ & 0x3F);
result += (*m_stringPosition++ & 0x3F);
}
return CodePoint(result);
}
CodePoint UTF8Decoder::previousCodePoint() {
assert(m_stringPosition > m_string);
if (leading_ones(*(m_stringPosition - 1)) == 0) {
// The current code point is one char long
m_stringPosition--;
return *m_stringPosition;
}
// The current code point spans over multiple chars
uint32_t result = 0;
int i = 0;
int leadingOnes = 1;
m_stringPosition--;
assert(leading_ones(*m_stringPosition) == 1);
while (leadingOnes == 1) {
assert(m_stringPosition > m_string);
result += (*m_stringPosition & 0x3F) << (6 * i);
i++;
m_stringPosition--;
leadingOnes = leading_ones(*m_stringPosition);
}
assert(i <= 3);
assert(leadingOnes > 1 && leadingOnes <= 4);
assert(m_stringPosition >= m_string);
result+= last_k_bits(*m_stringPosition, 8-leadingOnes-1);
return CodePoint(result);
}
size_t UTF8Decoder::CharSizeOfCodePoint(CodePoint c) {
constexpr int bufferSize = CodePoint::MaxCodePointCharLength;
char buffer[bufferSize];

View File

@@ -23,7 +23,7 @@ const char * CodePointSearch(const char * s, CodePoint c) {
return nullptr;
}
void CopyAndRemoveCodePoint(char * dst, size_t dstSize, const char * src, CodePoint c, size_t * indexToUpdate) {
void CopyAndRemoveCodePoint(char * dst, size_t dstSize, const char * src, CodePoint c, const char * * pointerToUpdate) {
UTF8Decoder decoder(src);
const char * currentPointer = src;
const char * maxPointer = src + strlen(src) + 1;
@@ -38,9 +38,9 @@ void CopyAndRemoveCodePoint(char * dst, size_t dstSize, const char * src, CodePo
int copySize = min(nextPointer - currentPointer, dstSize - bufferIndex);
memcpy(dst + bufferIndex, currentPointer, copySize);
bufferIndex+= copySize;
} else if (indexToUpdate != nullptr && currentPointer - src < *indexToUpdate) {
assert(*indexToUpdate >= codePointCharSize);
*indexToUpdate-= codePointCharSize;
} else if (pointerToUpdate != nullptr && currentPointer < *pointerToUpdate) {
assert(*pointerToUpdate - src >= codePointCharSize);
*pointerToUpdate = *pointerToUpdate - codePointCharSize;
}
currentPointer = nextPointer;
codePoint = decoder.nextCodePoint();