#include #include #include #include #include #include #include #include #include #include /* TextArea */ TextArea::TextArea(Responder * parentResponder, View * contentView, const KDFont * font) : TextInput(parentResponder, contentView), InputEventHandler(nullptr), m_delegate(nullptr) { } static inline void InsertSpacesAtLocation(int spacesCount, char * buffer, int bufferSize) { assert(buffer != nullptr); assert((int)(strlen(buffer) + spacesCount) < bufferSize); size_t sizeToMove = strlen(buffer) + 1; size_t spaceCharSize = UTF8Decoder::CharSizeOfCodePoint(' '); size_t spacesLength = spacesCount * spaceCharSize; memmove(buffer + spacesLength, buffer, sizeToMove); for (int i = 0; i < spacesCount; i++) { int spaceOffset = i * spaceCharSize; UTF8Decoder::CodePointToChars(' ', buffer + spaceOffset, bufferSize - spaceOffset); } } bool TextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) { if (*text == 0) { return false; } // Delete the selected text if needed if (!contentView()->selectionIsEmpty()) { deleteSelection(); } /* Compute the indentation. If the text cannot be inserted with the * indentation, stop here. */ int spacesCount = 0; int totalIndentationSize = 0; int addedTextLength = strlen(text); size_t previousTextLength = contentView()->getText()->textLength(); char * insertionPosition = const_cast(cursorLocation()); const char * textAreaBuffer = contentView()->text(); if (indentation) { // Compute the indentation spacesCount = indentationBeforeCursor(); if (insertionPosition > textAreaBuffer && UTF8Helper::PreviousCodePointIs(textAreaBuffer, insertionPosition, ':')) { spacesCount += k_indentationSpaces; } // Check the text will not overflow the buffer totalIndentationSize = UTF8Helper::CountOccurrences(text, '\n') * spacesCount; if (previousTextLength + addedTextLength + totalIndentationSize >= contentView()->getText()->bufferSize()) { return false; } } /* KDCoordinate is a int16. We must limit the number of characters per line, * and lines per scripts, otherwise the line rects or content rect * height/width can overflow int16, which results in weird visual effects.*/ // 1 - Number of Characters per line : if (previousTextLength + addedTextLength > k_maxLineChars) { /* Only check for long lines in long scripts. PreviousTextLength and * addedTextLength being greater than the actual number of glyphs is not an * issue here. After insertion, text buffer will have this structure : * ".../n"+"before"+"inserted1"+("/n.../n")?+"inserted2"+"after"+"\n..." * Lengths : b ib ia a * As maxBufferSize is lower than k_maxLineChars, there is no need to check * for inserted lines between "\n...\n" */ static_assert(TextField::maxBufferSize() < k_maxLineChars, "Pasting text might cause content rect overflow."); // Counting line text lengths before and after insertion. int b = 0; int a = 0; UTF8Helper::countGlyphsInLine(textAreaBuffer, &b, &a, insertionPosition); if (a + b + addedTextLength > k_maxLineChars) { /* Overflow expected, depending on '/n' code point presence and position. * Counting : Glyphs inserted before first '/n' : ib * Glyphs inserted after last '/n' : ia * Number of '/n' : n */ int glyphCount[3] = {0, 0, 0}; UTF8Helper::PerformAtCodePoints(text, '\n', [](int, void * intArray, int, int) { // '\n' found, Increment n int * n = (int *)intArray + 2; *n = *n + 1; // Reset ia int * ia = (int *)intArray + 1; *ia = 0; }, [](int, void * intArray, int, int) { if (((int *)intArray)[2] == 0) { // While no '\n' found, increment ib int * ib = (int *)intArray; *ib = *ib + 1; } else { // Increment ia int * ia = (int *)intArray + 1; *ia = *ia + 1; } }, &glyphCount, 0, 0); // Insertion is not possible if one of the produced line is too long. if ((glyphCount[2] == 0 && a + glyphCount[0] + b > k_maxLineChars) || b + glyphCount[0] > k_maxLineChars || a + glyphCount[1] > k_maxLineChars) { return false; } } } // 2 - Total number of line : if (previousTextLength + addedTextLength > k_maxLines && contentView()->getText()->textLineTotal() + UTF8Helper::CountOccurrences(text, '\n') > k_maxLines) { // Only check for overflowed lines in long scripts to save computation return false; } // Insert the text if (!insertTextAtLocation(text, insertionPosition)) { return true; } // Insert the indentation if (indentation) { UTF8Helper::PerformAtCodePoints( insertionPosition, '\n', [](int codePointOffset, void * text, int indentation, int bufferLength) { int offset = codePointOffset + UTF8Decoder::CharSizeOfCodePoint('\n'); InsertSpacesAtLocation(indentation, (char *)text + offset, bufferLength); }, [](int c1, void * c2, int c3, int c4) {}, (void *)insertionPosition, spacesCount, contentView()->getText()->bufferSize() - (insertionPosition - contentView()->getText()->text()), UCodePointNull, true, nullptr, insertionPosition + addedTextLength); } const char * endOfInsertedText = insertionPosition + addedTextLength + totalIndentationSize; const char * cursorPositionInCommand = TextInputHelpers::CursorPositionInCommand(insertionPosition, endOfInsertedText); // Remove the Empty code points UTF8Helper::RemoveCodePoint(insertionPosition, UCodePointEmpty, &cursorPositionInCommand, endOfInsertedText); // Set the cursor location const char * nextCursorLocation = forceCursorRightOfText ? endOfInsertedText : cursorPositionInCommand; setCursorLocation(nextCursorLocation); return true; } bool TextArea::handleEvent(Ion::Events::Event event) { if (m_delegate != nullptr && m_delegate->textAreaDidReceiveEvent(this, event)) { return true; } if (handleBoxEvent(event)) { return true; } int step = Ion::Events::repetitionFactor(); if (event == Ion::Events::ShiftLeft || event == Ion::Events::ShiftRight) { selectLeftRight(event == Ion::Events::ShiftLeft, false, step); return true; } if (event == Ion::Events::ShiftUp || event == Ion::Events::ShiftDown) { selectUpDown(event == Ion::Events::ShiftUp, 1); return true; } else if (event == Ion::Events::AlphaLeft) { contentView()->moveCursorGeo(-INT_MAX/2, 0); TextInput::scrollToCursor(); } else if (event == Ion::Events::AlphaRight) { contentView()->moveCursorGeo(INT_MAX/2, 0); TextInput::scrollToCursor(); } else if (event == Ion::Events::AlphaUp) { contentView()->moveCursorGeo(0, -INT_MAX/2); TextInput::scrollToCursor(); } else if (event == Ion::Events::AlphaDown) { contentView()->moveCursorGeo(0, INT_MAX/2); TextInput::scrollToCursor(); } else if (event == Ion::Events::Left || event == Ion::Events::Right) { if (contentView()->resetSelection()) { return true; } return (event == Ion::Events::Left) ? TextInput::moveCursorLeft(step) : TextInput::moveCursorRight(step); } if (event == Ion::Events::Left || event == Ion::Events::Right) { if (contentView()->resetSelection()) { return true; } return (event == Ion::Events::Left) ? TextInput::moveCursorLeft(step) : TextInput::moveCursorRight(step); } if (event.hasText()) { return handleEventWithText(event.text()); } if (event == Ion::Events::EXE) { return handleEventWithText("\n"); } if (event == Ion::Events::Copy || event == Ion::Events::Cut) { if (contentView()->selectionIsEmpty()) { return false; } const char * start = contentView()->selectionStart(); Clipboard::sharedClipboard()->store(start, contentView()->selectionEnd() - start); if (event == Ion::Events::Cut) { deleteSelection(); } return true; } if (event == Ion::Events::Paste) { return handleEventWithText(Clipboard::sharedClipboard()->storedText(), false, true); } // The following events need a scrollToCursor and return true if (event == Ion::Events::Backspace) { if (contentView()->selectionIsEmpty()) { if (!removePreviousGlyph()) { return false; } } else { deleteSelection(); return true; } } else if (event == Ion::Events::Up || event == Ion::Events::Down) { contentView()->resetSelection(); contentView()->moveCursorGeo(0, event == Ion::Events::Up ? -step : step); } else if (event == Ion::Events::Clear) { if (!contentView()->selectionIsEmpty()) { deleteSelection(); return true; } else if (!contentView()->removeEndOfLine()) { contentView()->removeStartOfLine(); } } else if (event == Ion::Events::Paste) { return handleEventWithText(Clipboard::sharedClipboard()->storedText()); } else if (event == Ion::Events::Percent) { return removePreviousGlyph(); } else if (event.hasText()) { return handleEventWithText(event.text()); } else { return false; } scrollToCursor(); return true; } void TextArea::setText(char * textBuffer, size_t textBufferSize) { contentView()->setText(textBuffer, textBufferSize); contentView()->moveCursorGeo(0, 0); } int TextArea::indentationBeforeCursor() const { int indentationSize = 0; /* Compute the number of spaces at the beginning of the line. Increase the * indentation size when encountering spaces, reset it to 0 when encountering * another code point, until reaching the beginning of the line. */ UTF8Helper::PerformAtCodePoints(const_cast