mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-19 00:37:25 +01:00
[escher] Fix text inputs so they use UTF8
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(); };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user