mirror of
https://github.com/mudita/MuditaOS.git
synced 2026-01-24 22:08:33 -05:00
Add Text scrolling, Add text starting position, Added text scrolling, updated TextLine line endings, TextBlocks newline handling, updated SMS Bubble and Notes text items. Added tests for scrolling.
329 lines
9.3 KiB
C++
329 lines
9.3 KiB
C++
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
|
|
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
|
|
|
|
#include "TextBlockCursor.hpp"
|
|
#include "TextBlock.hpp"
|
|
#include "TextDocument.hpp"
|
|
#include "TextParse.hpp"
|
|
#include "log/log.hpp"
|
|
#include <cassert>
|
|
|
|
static const int last_char_inclusive = 0; // if then -1 / else 0
|
|
|
|
#define debug_cursor(...)
|
|
// #define debug_cursor(...) LOG_DEBUG(__VA_ARGS__)
|
|
|
|
namespace gui
|
|
{
|
|
auto BlockCursor::currentBlock() const -> std::_List_iterator<TextBlock>
|
|
{
|
|
if (currentBlockNumber == text::npos) {
|
|
return document->blocks.end();
|
|
}
|
|
return std::next(document->blocks.begin(), currentBlockNumber);
|
|
}
|
|
|
|
auto BlockCursor::blocksEnd() const -> std::_List_iterator<TextBlock>
|
|
{
|
|
return std::end(document->blocks);
|
|
}
|
|
|
|
auto BlockCursor::blocksBegin() const -> std::_List_iterator<TextBlock>
|
|
{
|
|
return std::begin(document->blocks);
|
|
}
|
|
|
|
BlockCursor::BlockCursor(TextDocument *document,
|
|
unsigned int blockPosition,
|
|
unsigned int blockNumber,
|
|
RawFont *default_font)
|
|
: document(document), default_font(default_font)
|
|
{
|
|
this->pos = text::npos;
|
|
this->currentBlockNumber = text::npos;
|
|
if (checkDocument()) {
|
|
this->currentBlockNumber =
|
|
blockNumber < document->blocks.size() ? blockNumber : document->blocks.size() - 1;
|
|
auto blockLength = std::next(document->blocks.begin(), this->currentBlockNumber)->length();
|
|
this->pos = blockPosition < blockLength ? blockPosition : blockLength + last_char_inclusive;
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] auto BlockCursor::checkNpos() const -> bool
|
|
{
|
|
return pos == text::npos || currentBlockNumber == text::npos;
|
|
}
|
|
|
|
void BlockCursor::resetNpos()
|
|
{
|
|
if (pos == text::npos)
|
|
pos = 0;
|
|
if (currentBlockNumber == text::npos)
|
|
currentBlockNumber = 0;
|
|
}
|
|
|
|
[[nodiscard]] auto BlockCursor::checkDocument() const -> bool
|
|
{
|
|
return document != nullptr && document->blocks.size() > 0;
|
|
}
|
|
|
|
auto BlockCursor::atEnd() const -> bool
|
|
{
|
|
if (!checkDocument() || checkNpos()) {
|
|
return false;
|
|
}
|
|
|
|
auto lastBlock = document->blocks.back();
|
|
|
|
return currentBlockNumber == document->blocks.size() - 1 &&
|
|
pos >= lastBlock.length() + (lastBlock.getEnd() != TextBlock::End::Newline ? last_char_inclusive : -1);
|
|
}
|
|
|
|
auto BlockCursor::operator+=(unsigned int val) -> BlockCursor &
|
|
{
|
|
// just use operator ++
|
|
for (unsigned int i = 0; i < val; i++) {
|
|
++*this;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
BlockCursor &BlockCursor::operator++()
|
|
{
|
|
if (!checkDocument()) {
|
|
return *this;
|
|
}
|
|
|
|
resetNpos();
|
|
|
|
bool endOfCurrentBlockReached = false;
|
|
|
|
debug_cursor("Addition: Position: %d, Current block number %d", pos, currentBlockNumber);
|
|
|
|
size_t blockSize = currentBlock()->length();
|
|
|
|
// Skip newline at end
|
|
if (currentBlock()->getEnd() == TextBlock::End::Newline && (pos + 1) == blockSize) {
|
|
endOfCurrentBlockReached = true;
|
|
}
|
|
else if (pos >= blockSize) {
|
|
endOfCurrentBlockReached = true;
|
|
}
|
|
|
|
bool lastBlockReached = currentBlockNumber + 1 == document->blocks.size();
|
|
|
|
if (endOfCurrentBlockReached && lastBlockReached) {
|
|
return *this;
|
|
}
|
|
|
|
if (endOfCurrentBlockReached) {
|
|
return goToNextBlock();
|
|
}
|
|
|
|
pos += 1;
|
|
|
|
return *this;
|
|
}
|
|
|
|
auto BlockCursor::operator-=(unsigned int val) -> BlockCursor &
|
|
{
|
|
// just use operator --
|
|
for (unsigned int i = 0; i < val; ++i, --*this) {}
|
|
return *this;
|
|
}
|
|
|
|
BlockCursor &BlockCursor::operator--()
|
|
{
|
|
if (!checkDocument()) {
|
|
return *this;
|
|
}
|
|
|
|
resetNpos();
|
|
|
|
debug_cursor("Subtraction: Position: %d, Current block number %d", pos, currentBlockNumber);
|
|
|
|
if (pos == 0 && currentBlockNumber == 0) {
|
|
return *this;
|
|
}
|
|
|
|
if (pos == 0) {
|
|
return goToPreviousBlock();
|
|
}
|
|
|
|
pos -= 1;
|
|
|
|
return *this;
|
|
} // namespace gui
|
|
|
|
auto BlockCursor::goToNextBlock() -> BlockCursor &
|
|
{
|
|
if (currentBlockNumber < document->getBlocks().size()) {
|
|
blockChanged = true;
|
|
currentBlockNumber += 1;
|
|
pos = 0;
|
|
|
|
debug_cursor("Addition block changed: Position: %d, Current block number %d", pos, currentBlockNumber);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
auto BlockCursor::goToPreviousBlock() -> BlockCursor &
|
|
{
|
|
if (currentBlockNumber > 0) {
|
|
blockChanged = true;
|
|
currentBlockNumber -= 1;
|
|
pos = currentBlock()->length() +
|
|
(currentBlock()->getEnd() != TextBlock::End::Newline ? last_char_inclusive : -1);
|
|
|
|
debug_cursor("Subtraction block changed: Position: %d, Current block number %d", pos, currentBlockNumber);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
void BlockCursor::addChar(uint32_t utf_val)
|
|
{
|
|
if (document->blocks.size() == 0) {
|
|
if (utf_val == text::newline) {
|
|
|
|
/*
|
|
* Adding new line to empty document means adding new block with only new line and immediately jumps to
|
|
* another block
|
|
*/
|
|
document->append(TextBlock("", default_font, TextBlock::End::None));
|
|
operator++();
|
|
document->addNewline(*this, TextBlock::End::Newline);
|
|
|
|
return;
|
|
}
|
|
document->append(TextBlock("", default_font, TextBlock::End::None));
|
|
resetNpos();
|
|
}
|
|
auto block = currentBlock();
|
|
if (block == blocksEnd()) {
|
|
LOG_ERROR("add char to document with no text blocks shouldn't ever happen");
|
|
return;
|
|
}
|
|
if (utf_val == text::newline) {
|
|
document->addNewline(*this, TextBlock::End::Newline);
|
|
return;
|
|
}
|
|
block->addChar(utf_val, pos);
|
|
}
|
|
|
|
void BlockCursor::addTextBlock(TextBlock &&textblock)
|
|
{
|
|
|
|
if (textblock.length() == 0 && document->blocks.empty() && textblock.getEnd() != TextBlock::End::Newline) {
|
|
return;
|
|
}
|
|
else if (textblock.length() == 0 && ((--blocksEnd())->getEnd() != TextBlock::End::Newline)) {
|
|
return;
|
|
}
|
|
|
|
if (document->isEmpty()) {
|
|
resetNpos();
|
|
}
|
|
|
|
// If previously added block is empty without newline remove it.
|
|
if (!document->blocks.empty()) {
|
|
if (document->blocks.back().isEmpty()) {
|
|
document->blocks.pop_back();
|
|
}
|
|
}
|
|
|
|
auto blockEnd = textblock.getEnd();
|
|
auto blockFormat = textblock.getFormat()->getFont();
|
|
|
|
document->append(std::move(textblock));
|
|
|
|
// If new added block ends with newline add additional empty block at end
|
|
if (blockEnd == TextBlock::End::Newline) {
|
|
addTextBlock(TextBlock("", blockFormat, TextBlock::End::None));
|
|
}
|
|
}
|
|
|
|
bool BlockCursor::removeChar()
|
|
{
|
|
if (checkNpos()) {
|
|
LOG_ERROR("cant remove from not initialized/empty cursor");
|
|
return false;
|
|
}
|
|
|
|
auto block = currentBlock();
|
|
auto prevBlock = block;
|
|
auto nextBlock = block;
|
|
|
|
if (block != blocksBegin()) {
|
|
prevBlock--;
|
|
}
|
|
if (block != blocksEnd()) {
|
|
nextBlock++;
|
|
}
|
|
|
|
if (block == blocksEnd()) {
|
|
LOG_ERROR("removing char from document with no TextBlocks");
|
|
return false;
|
|
}
|
|
|
|
if (nextBlock != blocksEnd() && nextBlock->isEmpty() && nextBlock != (--blocksEnd())) {
|
|
debug_cursor("Next empty block removed");
|
|
document->removeBlock(nextBlock);
|
|
}
|
|
|
|
debug_cursor("From block: [%d], remove pos: [%d] block length [%d], Block count [%lu]",
|
|
getBlockNumber(),
|
|
pos,
|
|
block->length(),
|
|
document->blocks.size());
|
|
|
|
block->removeChar(pos);
|
|
|
|
if (block->isEmpty() && block != blocksEnd() &&
|
|
!(prevBlock->getEnd() == TextBlock::End::Newline && nextBlock->isEmpty())) {
|
|
debug_cursor("Current empty block removed");
|
|
document->removeBlock(block);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
const TextBlock &BlockCursor::operator*()
|
|
{
|
|
return *currentBlock();
|
|
}
|
|
|
|
auto BlockCursor::getText() -> std::string
|
|
{
|
|
if (currentBlock() == blocksEnd()) {
|
|
return "";
|
|
}
|
|
return currentBlock()->getText(getPosition());
|
|
}
|
|
|
|
auto BlockCursor::getUTF8Text() -> UTF8
|
|
{
|
|
if (currentBlock() == blocksEnd()) {
|
|
return "";
|
|
}
|
|
return currentBlock()->getText(getPosition());
|
|
}
|
|
|
|
const TextBlock *BlockCursor::operator->()
|
|
{
|
|
return &*currentBlock();
|
|
}
|
|
|
|
auto BlockCursor::begin() -> std::list<TextBlock>::iterator
|
|
{
|
|
return document == nullptr ? document->blocks.end() : document->blocks.begin();
|
|
}
|
|
|
|
auto BlockCursor::end() -> std::list<TextBlock>::iterator
|
|
{
|
|
return document->blocks.end();
|
|
}
|
|
|
|
} // namespace gui
|