mirror of
https://github.com/mudita/MuditaOS.git
synced 2026-01-02 10:58:45 -05:00
Fix of the issue that VoLTE checking state label was cut off due to improper setMinimumWidthToFitText() method behavior when text is placed in box with rounded corners.
1068 lines
32 KiB
C++
1068 lines
32 KiB
C++
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
|
|
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
|
|
|
|
#include "Text.hpp"
|
|
|
|
#include <parsers/TextParse.hpp>
|
|
#include <FontManager.hpp>
|
|
|
|
#include <Common.hpp>
|
|
#include <log/log.hpp>
|
|
|
|
#if DEBUG_GUI_TEXT == 1
|
|
#define debug_text(...) LOG_DEBUG(__VA_ARGS__)
|
|
#else
|
|
#define debug_text(...)
|
|
#endif
|
|
#if DEBUG_GUI_TEXT_LINES == 1
|
|
#define debug_text_lines(...) LOG_DEBUG(__VA_ARGS__)
|
|
#else
|
|
#define debug_text_lines(...)
|
|
#endif
|
|
#if DEBUG_GUI_TEXT_CURSOR == 1
|
|
#define debug_text_cursor(...) LOG_DEBUG(__VA_ARGS__)
|
|
#else
|
|
#define debug_text_cursor(...)
|
|
#endif
|
|
|
|
namespace gui
|
|
{
|
|
/// changes integer value to ascii int value (with no additional checks)
|
|
char intToAscii(int val)
|
|
{
|
|
return val + '0';
|
|
}
|
|
|
|
Text::Text(Item *parent,
|
|
const std::uint32_t &x,
|
|
const std::uint32_t &y,
|
|
const std::uint32_t &w,
|
|
const std::uint32_t &h,
|
|
ExpandMode expandMode,
|
|
TextType textType)
|
|
: Rect(parent, x, y, w, h), expandMode{expandMode}, textType{textType},
|
|
format(FontManager::getInstance().getFont(style::window::font::small))
|
|
{
|
|
lines = std::make_unique<Lines>(this);
|
|
|
|
alignment = style::text::defaultTextAlignment;
|
|
|
|
setPenWidth(style::window::default_border_no_focus_w);
|
|
setPenFocusWidth(style::window::default_border_focus_w);
|
|
buildCursor();
|
|
|
|
setBorderColor(gui::ColorFullBlack);
|
|
setEdges(RectangleEdge::All);
|
|
|
|
preBuildDrawListHook = [this](std::list<Command> &commands) { preBuildDrawListHookImplementation(commands); };
|
|
}
|
|
|
|
Text::Text() : Text(nullptr, 0, 0, 0, 0)
|
|
{}
|
|
|
|
Text::~Text()
|
|
{
|
|
delete inputMode;
|
|
}
|
|
|
|
void Text::setEditMode(EditMode new_mode)
|
|
{
|
|
if (new_mode != editMode) {
|
|
editMode = new_mode;
|
|
}
|
|
}
|
|
|
|
void Text::setTextType(TextType type)
|
|
{
|
|
if (textType != type) {
|
|
textType = type;
|
|
drawLines();
|
|
}
|
|
}
|
|
|
|
void Text::setTextEllipsisType(TextEllipsis type)
|
|
{
|
|
if (ellipsisType != type) {
|
|
ellipsisType = type;
|
|
drawLines();
|
|
}
|
|
}
|
|
|
|
TextEllipsis Text::getTextEllipsisType()
|
|
{
|
|
return ellipsisType;
|
|
}
|
|
|
|
void Text::setTextLimitType(TextLimitType limitType, unsigned int val)
|
|
{
|
|
auto it = std::find_if(limitsList.begin(), limitsList.end(), [&limitType](const TextLimit &limitOnList) {
|
|
return limitOnList.limitType == limitType;
|
|
});
|
|
|
|
if (it != limitsList.end()) {
|
|
(*it).limitValue = val;
|
|
}
|
|
else {
|
|
limitsList.emplace_back(TextLimit{limitType, val});
|
|
}
|
|
}
|
|
|
|
void Text::clearTextLimits()
|
|
{
|
|
limitsList.clear();
|
|
}
|
|
|
|
void Text::drawUnderline(bool val)
|
|
{
|
|
if (lines->getUnderLineProperties().draw != val) {
|
|
lines->getUnderLineProperties().draw = val;
|
|
drawLines();
|
|
}
|
|
}
|
|
|
|
void Text::setText(const UTF8 &text)
|
|
{
|
|
debug_text("setText: %s", text.c_str());
|
|
setText(std::make_unique<TextDocument>(textToTextBlocks(text, format)));
|
|
}
|
|
|
|
void Text::setText(std::unique_ptr<TextDocument> &&doc)
|
|
{
|
|
buildDocument(std::move(doc));
|
|
onTextChanged();
|
|
}
|
|
|
|
void Text::setTextChangedCallback(TextChangedCallback &&callback)
|
|
{
|
|
textChangedCallback = std::move(callback);
|
|
}
|
|
|
|
void Text::addText(const UTF8 &text, AdditionType additionType)
|
|
{
|
|
if (text.length() == 0) {
|
|
return;
|
|
}
|
|
|
|
if (additionType == AdditionType::perChar) {
|
|
*cursor << text;
|
|
}
|
|
else if (additionType == AdditionType::perBlock) {
|
|
for (const auto &block : textToTextBlocks(text, format)) {
|
|
*cursor << block;
|
|
}
|
|
}
|
|
|
|
onTextChanged();
|
|
drawLines();
|
|
}
|
|
|
|
void Text::addText(const TextBlock &text)
|
|
{
|
|
*cursor << text;
|
|
onTextChanged();
|
|
drawLines();
|
|
}
|
|
|
|
void Text::setRichText(const UTF8 &text, text::RichTextParser::TokenMap &&tokenMap)
|
|
{
|
|
setText("");
|
|
addRichText(text, std::move(tokenMap));
|
|
}
|
|
|
|
void Text::addRichText(const UTF8 &text, text::RichTextParser::TokenMap &&tokenMap)
|
|
{
|
|
const auto tmp_document = text::RichTextParser().parse(text, &format, std::move(tokenMap));
|
|
|
|
if (!tmp_document || tmp_document->isEmpty()) {
|
|
debug_text("Nothing to parse/parser error in rich text: %s", text.c_str());
|
|
return addText(text); // fallback
|
|
}
|
|
for (const auto &block : tmp_document->getBlockCursor(0)) {
|
|
*cursor << block;
|
|
}
|
|
drawLines();
|
|
}
|
|
|
|
void Text::removeFromCursor()
|
|
{
|
|
const auto currentText = getText();
|
|
const auto cursorPosition = cursor->getAbsolutePosition();
|
|
const auto substrLength = currentText.length() - cursorPosition;
|
|
|
|
const auto newText = currentText.substr(cursorPosition, substrLength);
|
|
buildDocument(newText);
|
|
|
|
/* Rewind cursor to the beginning of the line */
|
|
cursor->moveCursor(NavigationDirection::LEFT, substrLength);
|
|
|
|
onTextChanged();
|
|
}
|
|
|
|
void Text::clear()
|
|
{
|
|
buildDocument("");
|
|
onTextChanged();
|
|
}
|
|
|
|
bool Text::isEmpty()
|
|
{
|
|
return document->isEmpty();
|
|
}
|
|
|
|
UTF8 Text::getText() const
|
|
{
|
|
return document->getText();
|
|
}
|
|
|
|
void Text::setFont(const UTF8 &fontName)
|
|
{
|
|
RawFont *newFont = FontManager::getInstance().getFont(fontName);
|
|
|
|
if (format.getFont() != newFont) {
|
|
format.setFont(newFont);
|
|
buildDocument(getText());
|
|
}
|
|
}
|
|
|
|
void Text::setFont(RawFont *font)
|
|
{
|
|
format.setFont(font);
|
|
buildCursor();
|
|
}
|
|
|
|
void Text::setMinimumWidthToFitText()
|
|
{
|
|
setMinimumWidthToFitText(getText());
|
|
}
|
|
|
|
void Text::setMinimumWidthToFitText(const UTF8 &text)
|
|
{
|
|
if (text.empty()) {
|
|
return;
|
|
}
|
|
|
|
const auto &parsedText = text::RichTextParser().parse(text, &format)->getText();
|
|
const auto &textToFit = !parsedText.empty() ? parsedText : text;
|
|
setMinimumWidth(format.getFont()->getPixelWidth(textToFit) + getCursorDrawSpace());
|
|
}
|
|
|
|
void Text::setMinimumHeightToFitText(unsigned int linesCount)
|
|
{
|
|
setMinimumHeight(format.getFont()->info.line_height * linesCount);
|
|
}
|
|
|
|
auto Text::setCursorStartPosition(CursorStartPosition val) -> void
|
|
{
|
|
// As we destroy cursors starting position information need to be stored in both places
|
|
if (cursorStartPosition != val) {
|
|
cursorStartPosition = val;
|
|
cursor->setCursorStartPosition(cursorStartPosition);
|
|
buildDocument(std::make_unique<TextDocument>(document->getBlocks()));
|
|
}
|
|
}
|
|
|
|
bool Text::onInput(const InputEvent &evt)
|
|
{
|
|
if (Rect::onInput(evt)) {
|
|
debug_text("Rect::onInput");
|
|
return true;
|
|
}
|
|
if (handleRotateInputMode(evt)) {
|
|
debug_text("handleRotateInputMode");
|
|
return true;
|
|
}
|
|
if (handleRestoreInputModeUI(evt)) {
|
|
debug_text("handleRestoreInputModeUI");
|
|
return true;
|
|
}
|
|
if (handleSelectSpecialChar(evt)) {
|
|
debug_text("handleSelectSpecialChar");
|
|
return true;
|
|
}
|
|
if (handleActivation(evt)) {
|
|
debug_text("handleActivation");
|
|
return true;
|
|
}
|
|
if (handleNavigation(evt)) {
|
|
debug_text("handleNavigation");
|
|
return true;
|
|
}
|
|
if (handleRemovalChar(evt)) {
|
|
debug_text("handleBackspace");
|
|
return true;
|
|
}
|
|
if (handleWholeTextRemoval(evt)) {
|
|
debug_text("handleLongBackspace");
|
|
return true;
|
|
}
|
|
if (handleAddChar(evt)) {
|
|
debug_text("handleAddChar");
|
|
return true;
|
|
}
|
|
if (handleLongPressAddChar(evt)) {
|
|
debug_text("handleDigitLongPress");
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Text::onFocus(bool state)
|
|
{
|
|
if (state != focus && state) {
|
|
drawLines();
|
|
}
|
|
showCursor(state);
|
|
return true;
|
|
}
|
|
|
|
void Text::setRadius(int value)
|
|
{
|
|
Rect::setRadius(value);
|
|
// if padding are smaller than radius update the padding
|
|
if (padding.left < value) {
|
|
padding.left = value;
|
|
}
|
|
if (padding.right < value) {
|
|
padding.right = value;
|
|
}
|
|
}
|
|
|
|
void Text::setColor(Color color)
|
|
{
|
|
if (format.getColor() != color) {
|
|
format.setColor(color);
|
|
}
|
|
}
|
|
|
|
void Text::setPadding(const Padding &value)
|
|
{
|
|
if (padding != value) {
|
|
padding = value;
|
|
buildCursor();
|
|
drawLines();
|
|
}
|
|
}
|
|
|
|
void Text::setAlignment(const Alignment &value)
|
|
{
|
|
if (alignment != value) {
|
|
alignment = value;
|
|
drawLines();
|
|
}
|
|
}
|
|
|
|
void Text::setInputMode(InputMode *&&mode)
|
|
{
|
|
delete this->inputMode;
|
|
|
|
this->inputMode = mode;
|
|
}
|
|
|
|
std::string Text::getInputModeKeyMap()
|
|
{
|
|
if (inputMode == nullptr) {
|
|
return "";
|
|
}
|
|
|
|
if (inputMode->is(InputMode::Abc)) {
|
|
return inputMode->get(detectInputMode());
|
|
}
|
|
|
|
return inputMode->get();
|
|
}
|
|
|
|
InputMode::Mode Text::detectInputMode()
|
|
{
|
|
return cursor->checkSentenceBeginning() ? InputMode::ABC : InputMode::abc;
|
|
}
|
|
|
|
bool Text::onDimensionChanged(const BoundingBox &oldDim, const BoundingBox &newDim)
|
|
{
|
|
Rect::onDimensionChanged(oldDim, newDim);
|
|
drawLines();
|
|
return true;
|
|
}
|
|
|
|
auto Text::getSizeMinusPadding(Axis axis, Area val) -> Length
|
|
{
|
|
int size = area(val).size(axis);
|
|
|
|
if (size <= padding.getSumInAxis(axis)) {
|
|
size = 0;
|
|
}
|
|
else {
|
|
size -= padding.getSumInAxis(axis);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
auto Text::applyParentSizeRestrictions() -> void
|
|
{
|
|
// Check if Parent does not have max size restrictions.
|
|
if (this->parent != nullptr) {
|
|
area(Area::Max).w = parent->area(Area::Max).w != 0 ? std::min(parent->area(Area::Max).w, area(Area::Max).w)
|
|
: area(Area::Max).w;
|
|
area(Area::Max).h = parent->area(Area::Max).h != 0 ? std::min(parent->area(Area::Max).h, area(Area::Max).h)
|
|
: area(Area::Max).h;
|
|
}
|
|
}
|
|
|
|
auto Text::drawCursor() -> void
|
|
{
|
|
cursor->updateView();
|
|
}
|
|
|
|
auto Text::drawLines() -> void
|
|
{
|
|
lines->erase();
|
|
|
|
applyParentSizeRestrictions();
|
|
|
|
const auto [startDrawBlockNumber, startDrawBlockPosition] = lines->drawStartConditions;
|
|
BlockCursor drawCursor(
|
|
cursor->getDocument(), startDrawBlockPosition, startDrawBlockNumber, getTextFormat().getFont());
|
|
|
|
debug_text("--> START drawLines: {%" PRIu32 ", %" PRIu32 "}", w, h);
|
|
|
|
lines->draw(drawCursor,
|
|
getSizeMinusPadding(Axis::X, Area::Max) - getCursorDrawSpace(),
|
|
getSizeMinusPadding(Axis::Y, Area::Max),
|
|
padding.top,
|
|
padding.left,
|
|
textType);
|
|
|
|
calculateAndRequestSize();
|
|
|
|
lines->linesHAlign(getSizeMinusPadding(Axis::X, Area::Normal));
|
|
lines->linesVAlign(getSizeMinusPadding(Axis::Y, Area::Normal));
|
|
|
|
debug_text("<- END\n");
|
|
}
|
|
|
|
auto Text::calculateAndRequestSize() -> void
|
|
{
|
|
// silly case resize - there request space and all is nice
|
|
// need to at least erase last line if it wont fit
|
|
// should be done on each loop
|
|
{
|
|
Length hUsed = padding.top + lines->linesHeight() + padding.bottom;
|
|
Length wUsed = lines->maxWidth() + padding.getSumInAxis(Axis::X);
|
|
|
|
if (lines->empty()) {
|
|
debug_text("No lines to show, try to at least fit in cursor");
|
|
if (format.getFont() != nullptr &&
|
|
padding.top + lines->linesHeight() < format.getFont()->info.line_height) {
|
|
hUsed = format.getFont()->info.line_height;
|
|
wUsed = getCursorDrawSpace();
|
|
debug_text("empty line height: %d", hUsed);
|
|
}
|
|
}
|
|
|
|
if (hUsed != area(Area::Normal).size(Axis::Y) || wUsed != area(Area::Normal).size(Axis::X)) {
|
|
debug_text("size request: %d %d", wUsed, hUsed);
|
|
auto [w, h] = requestSize(wUsed, hUsed);
|
|
|
|
if (h < hUsed) {
|
|
debug_text("No free height for text!");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Text::preBuildDrawListHookImplementation(std::list<Command> &commands)
|
|
{
|
|
// we can't build elements to show just before showing.
|
|
// why? because we need to know if these elements fit in
|
|
// if not -> these need to call resize, resize needs to be called prior to buildDrawList
|
|
drawCursor();
|
|
debug_text("parent area: %s, elements area: %s",
|
|
area().str().c_str(),
|
|
[&]() -> std::string {
|
|
std::string str;
|
|
for (auto el : children) {
|
|
if (auto label = dynamic_cast<gui::Label *>(el)) {
|
|
str += "area: " + label->area().str() + std::string(label->getText()) + " ";
|
|
}
|
|
}
|
|
return str.c_str();
|
|
}()
|
|
.c_str());
|
|
}
|
|
|
|
void Text::buildDocument(const UTF8 &text)
|
|
{
|
|
buildDocument(std::make_unique<TextDocument>(textToTextBlocks(text, format)));
|
|
}
|
|
|
|
void Text::buildDocument(std::unique_ptr<TextDocument> &&document_moved)
|
|
{
|
|
if (document_moved == nullptr) {
|
|
debug_text("Passed invalid document, returning without setting it");
|
|
return;
|
|
}
|
|
|
|
lines->reset();
|
|
document->destroy();
|
|
buildCursor();
|
|
|
|
for (const auto &block : document_moved->getBlocks()) {
|
|
*cursor << block;
|
|
}
|
|
debug_text("document text: %s", document->getText().c_str());
|
|
drawLines();
|
|
}
|
|
|
|
TextCursor *Text::createCursor()
|
|
{
|
|
if (textType == TextType::MultiLine) {
|
|
return new TextLinesCursor(this);
|
|
}
|
|
else {
|
|
return new TextInLineCursor(this);
|
|
}
|
|
}
|
|
|
|
void Text::buildCursor()
|
|
{
|
|
erase(cursor);
|
|
cursor = createCursor();
|
|
cursor->setCursorStartPosition(cursorStartPosition);
|
|
cursor->setAlignment(this->getAlignment());
|
|
cursor->setMargins(this->getPadding());
|
|
showCursor(focus);
|
|
}
|
|
|
|
void Text::showCursor(bool focus)
|
|
{
|
|
cursor->setVisible(focus && isMode(EditMode::Edit));
|
|
}
|
|
|
|
unsigned int Text::getCursorDrawSpace()
|
|
{
|
|
return editMode == EditMode::Edit ? TextCursor::defaultWidth : 0;
|
|
}
|
|
|
|
auto Text::handleRotateInputMode(const InputEvent &inputEvent) -> bool
|
|
{
|
|
if (inputMode != nullptr && inputEvent.isShortRelease(gui::KeyCode::KEY_AST)) {
|
|
inputMode->next();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
auto Text::handleRestoreInputModeUI(const InputEvent &inputEvent) -> bool
|
|
{
|
|
if (inputMode != nullptr && inputEvent.isKeyRelease()) {
|
|
inputMode->show_restore();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
auto Text::handleSelectSpecialChar(const InputEvent &inputEvent) -> bool
|
|
{
|
|
if (inputMode != nullptr && inputEvent.isLongRelease() && inputEvent.is(gui::KeyCode::KEY_AST)) {
|
|
inputMode->select_special_char();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
auto Text::handleActivation(const InputEvent &inputEvent) -> bool
|
|
{
|
|
return inputEvent.isShortRelease(KeyCode::KEY_AST) && Rect::onActivated(nullptr);
|
|
}
|
|
|
|
auto Text::handleNavigation(const InputEvent &inputEvent) -> bool
|
|
{
|
|
if (!inputEvent.isShortRelease()) {
|
|
return false;
|
|
}
|
|
|
|
if (isMode(EditMode::Print)) {
|
|
return false;
|
|
}
|
|
|
|
if (isMode(EditMode::Scroll) && textType == TextType::MultiLine) {
|
|
debug_text("Text in scroll mode ignores left/right navigation");
|
|
if (inputEvent.is(KeyCode::KEY_LEFT) || inputEvent.is(KeyCode::KEY_RIGHT)) {
|
|
return false;
|
|
}
|
|
|
|
const auto applyMultilineScrolling = [this](const auto scrollFn) {
|
|
std::size_t scrollStepsPerformed;
|
|
for (scrollStepsPerformed = 0; scrollStepsPerformed < scrollStep; ++scrollStepsPerformed) {
|
|
const auto atTextEnd = !scrollFn();
|
|
if (atTextEnd) {
|
|
break;
|
|
}
|
|
}
|
|
return (scrollStepsPerformed != 0);
|
|
};
|
|
|
|
const auto textLinesCursor = static_cast<TextLinesCursor *>(cursor);
|
|
|
|
if (inputEvent.is(KeyCode::KEY_UP)) {
|
|
return applyMultilineScrolling(std::bind(&TextLinesCursor::displayPreviousLine, textLinesCursor));
|
|
}
|
|
|
|
if (inputEvent.is(KeyCode::KEY_DOWN)) {
|
|
return applyMultilineScrolling(std::bind(&TextLinesCursor::displayNextLine, textLinesCursor));
|
|
}
|
|
}
|
|
|
|
if (inputToNavigation(inputEvent) != NavigationDirection::NONE) {
|
|
|
|
setCursorStartPosition(CursorStartPosition::Offset);
|
|
|
|
auto ret = cursor->moveCursor(inputToNavigation(inputEvent));
|
|
debug_text("moveCursor: %s", c_str(ret));
|
|
|
|
if (ret == TextCursor::Move::Start || ret == TextCursor::Move::End) {
|
|
debug_text("scrolling needs implementing");
|
|
return false;
|
|
}
|
|
if (ret != TextCursor::Move::Error) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Text::handleRemovalChar(const InputEvent &inputEvent)
|
|
{
|
|
if (!isMode(EditMode::Edit)) {
|
|
return false;
|
|
}
|
|
if (inputEvent.isShortRelease(removeKey)) {
|
|
|
|
setCursorStartPosition(CursorStartPosition::Offset);
|
|
|
|
if (!document->isEmpty() && removeChar()) {
|
|
translator.resetPreviousKeyPress();
|
|
onTextChanged();
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Text::handleWholeTextRemoval(const InputEvent &inputEvent)
|
|
{
|
|
if (!isMode(EditMode::Edit)) {
|
|
return false;
|
|
}
|
|
if (inputEvent.isLongRelease(removeKey)) {
|
|
if (!document->isEmpty()) {
|
|
removeFromCursor();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Text::handleAddChar(const InputEvent &inputEvent)
|
|
{
|
|
if (!inputEvent.isShortRelease() || !isMode(EditMode::Edit)) {
|
|
return false;
|
|
}
|
|
|
|
auto code = translator.handle(inputEvent.getRawKey(), getInputModeKeyMap());
|
|
|
|
if (code != Profile::none_key && checkAdditionBounds(code) == AdditionBound::CanAddAll) {
|
|
|
|
setCursorStartPosition(CursorStartPosition::Offset);
|
|
|
|
debug_text("handleAddChar %d -> Begin", code);
|
|
debug_text("%s times: %" PRIu32, inputEvent.str().c_str(), translator.getTimes());
|
|
/// if we have multi press in non digit mode - we need to replace char and put next char from translator
|
|
if (!(inputMode->is(InputMode::digit) || (inputMode->is(InputMode::phone))) && translator.getTimes() > 0) {
|
|
removeChar();
|
|
code = translator.handle(inputEvent.getRawKey(), getInputModeKeyMap(), false);
|
|
}
|
|
addChar(code);
|
|
onTextChanged();
|
|
|
|
debug_text("handleAddChar -> End(true)");
|
|
|
|
return true;
|
|
}
|
|
|
|
debug_text("handleAdChar -> End(false)");
|
|
return false;
|
|
}
|
|
|
|
auto Text::handleLongPressAddChar(const InputEvent &inputEvent) -> bool
|
|
{
|
|
if (!inputEvent.isLongRelease()) {
|
|
return false;
|
|
}
|
|
if (!isMode(EditMode::Edit)) {
|
|
return false;
|
|
}
|
|
|
|
// check input event handling accordingly to input mode
|
|
auto code = text::npos;
|
|
|
|
// phone mode
|
|
if (inputMode->is(InputMode::phone) && inputEvent.is(KeyCode::KEY_0)) {
|
|
code = '+';
|
|
}
|
|
// all other modes only handle digits
|
|
else if (inputEvent.isDigit()) {
|
|
code = intToAscii(inputEvent.numericValue());
|
|
}
|
|
|
|
if (code != text::npos && checkAdditionBounds(code) == AdditionBound::CanAddAll) {
|
|
setCursorStartPosition(CursorStartPosition::Offset);
|
|
addChar(code);
|
|
onTextChanged();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
auto Text::makePreDrawLines(const std::uint32_t utfVal) -> std::unique_ptr<Lines>
|
|
{
|
|
auto preDrawLines = std::make_unique<Lines>(this);
|
|
auto documentCopy = *cursor->getDocument();
|
|
auto formatCopy = getTextFormat().getFont();
|
|
|
|
applyParentSizeRestrictions();
|
|
|
|
BlockCursor preDrawCursor(&documentCopy, 0, 0, formatCopy);
|
|
preDrawCursor.addChar(utfVal);
|
|
|
|
preDrawLines->draw(
|
|
preDrawCursor, getSizeMinusPadding(Axis::X, Area::Max), text::npos, padding.top, padding.left, textType);
|
|
|
|
return preDrawLines;
|
|
}
|
|
|
|
auto Text::makePreDrawLines(const TextBlock &textBlock) -> std::unique_ptr<Lines>
|
|
{
|
|
auto preDrawLines = std::make_unique<Lines>(this);
|
|
auto documentCopy = *cursor->getDocument();
|
|
auto formatCopy = getTextFormat().getFont();
|
|
auto preDrawTextBlock = TextBlock(textBlock);
|
|
|
|
applyParentSizeRestrictions();
|
|
|
|
BlockCursor preDrawCursor(&documentCopy, 0, 0, formatCopy);
|
|
preDrawCursor.addTextBlock(std::move(preDrawTextBlock));
|
|
|
|
preDrawLines->draw(
|
|
preDrawCursor, getSizeMinusPadding(Axis::X, Area::Max), text::npos, padding.top, padding.left, textType);
|
|
|
|
return preDrawLines;
|
|
}
|
|
|
|
auto Text::checkMaxSignsLimit(unsigned int limitVal) -> AdditionBound
|
|
{
|
|
if (getText().length() >= limitVal) {
|
|
debug_text("Text at max signs count can't add more");
|
|
return AdditionBound::CantAdd;
|
|
}
|
|
else {
|
|
return AdditionBound::CanAddAll;
|
|
}
|
|
}
|
|
|
|
auto Text::checkMaxSignsLimit(const TextBlock &textBlock, unsigned int limitVal)
|
|
-> std::tuple<AdditionBound, TextBlock>
|
|
{
|
|
if (getText().length() >= limitVal) {
|
|
debug_text("Text at max signs count can't add more.");
|
|
return {AdditionBound::CantAdd, textBlock};
|
|
}
|
|
else if (getText().length() + textBlock.length() >= limitVal) {
|
|
|
|
// Split existing block into smaller one that can still fit and return it
|
|
auto availableSpace = limitVal - getText().length();
|
|
auto partBlockText = textBlock.getText().substr(0, availableSpace);
|
|
auto blockFormat = textBlock.getFormat();
|
|
|
|
debug_text("Text at max sign count adding part of block. Original: %s, Fit part %s",
|
|
textBlock.getText().c_str(),
|
|
partBlockText.c_str());
|
|
|
|
return {AdditionBound::CanAddPart, TextBlock(partBlockText, std::make_unique<TextFormat>(*blockFormat))};
|
|
}
|
|
else {
|
|
return {AdditionBound::CanAddAll, textBlock};
|
|
}
|
|
}
|
|
|
|
auto Text::checkMaxSizeLimit(std::uint32_t utfVal) -> AdditionBound
|
|
{
|
|
auto returnValue = AdditionBound::CanAddAll;
|
|
|
|
auto preDrawLines = makePreDrawLines(utfVal);
|
|
|
|
debug_text("Text lines height: %d, preDraw height: %d, Widget max h: %d",
|
|
lines->linesHeight(),
|
|
preDrawLines->linesHeight(),
|
|
area(Area::Max).h);
|
|
|
|
if (preDrawLines->maxWidth() == 0 ||
|
|
preDrawLines->linesHeight() + getPadding().getSumInAxis(Axis::Y) > area(Area::Max).h) {
|
|
|
|
debug_text("Text at max size can't add more");
|
|
preDrawLines->erase();
|
|
returnValue = AdditionBound::CantAdd;
|
|
}
|
|
|
|
preDrawLines->erase();
|
|
return returnValue;
|
|
}
|
|
|
|
auto Text::checkMaxSizeLimit(const TextBlock &textBlock) -> std::tuple<AdditionBound, TextBlock>
|
|
{
|
|
auto preDrawLines = makePreDrawLines(textBlock);
|
|
|
|
debug_text("Text lines height: %d, preDraw height: %d, Widget max h: %d",
|
|
lines->linesHeight(),
|
|
preDrawLines->linesHeight(),
|
|
area(Area::Max).h);
|
|
|
|
if (preDrawLines->maxWidth() == 0) {
|
|
preDrawLines->erase();
|
|
return {AdditionBound::CantAdd, textBlock};
|
|
}
|
|
|
|
if (preDrawLines->linesHeight() + getPadding().getSumInAxis(Axis::Y) > area(Area::Max).h) {
|
|
|
|
debug_text("Text at max size can't add whole bock, try to split");
|
|
|
|
for (unsigned int signCounter = textBlock.length(); signCounter != 0; signCounter--) {
|
|
|
|
preDrawLines->erase();
|
|
|
|
auto partBlockText = textBlock.getText().substr(0, signCounter);
|
|
auto blockFormat = textBlock.getFormat();
|
|
|
|
preDrawLines = makePreDrawLines(TextBlock(partBlockText, std::make_unique<TextFormat>(*blockFormat)));
|
|
|
|
debug_text("Text lines height: %d, preDraw height: %d, Widget max h: %d",
|
|
lines->linesHeight(),
|
|
preDrawLines->linesHeight(),
|
|
area(Area::Max).h);
|
|
|
|
if (preDrawLines->linesHeight() + getPadding().getSumInAxis(Axis::Y) <= area(Area::Max).h) {
|
|
|
|
preDrawLines->erase();
|
|
|
|
debug_text("Text at max size adding part of block. Original: %s, Fit part %s",
|
|
textBlock.getText().c_str(),
|
|
partBlockText.c_str());
|
|
|
|
return {AdditionBound::CanAddPart,
|
|
TextBlock(partBlockText, std::make_unique<TextFormat>(*blockFormat))};
|
|
}
|
|
}
|
|
|
|
preDrawLines->erase();
|
|
// If not a part of block can fit return hit bound.
|
|
return {AdditionBound::CantAdd, textBlock};
|
|
}
|
|
|
|
preDrawLines->erase();
|
|
return {AdditionBound::CanAddAll, textBlock};
|
|
}
|
|
|
|
auto Text::checkMaxLinesLimit(std::uint32_t utfVal, unsigned int limitVal) -> AdditionBound
|
|
{
|
|
auto returnValue = AdditionBound::CanAddAll;
|
|
|
|
auto preDrawLines = makePreDrawLines(utfVal);
|
|
|
|
debug_text("Text lines height: %d, preDraw height: %d, Widget max h: %d",
|
|
lines->linesHeight(),
|
|
preDrawLines->linesHeight(),
|
|
area(Area::Max).h);
|
|
|
|
if (preDrawLines->size() > limitVal) {
|
|
|
|
debug_text("Text at max size can't add more");
|
|
preDrawLines->erase();
|
|
returnValue = AdditionBound::CantAdd;
|
|
}
|
|
|
|
preDrawLines->erase();
|
|
return returnValue;
|
|
}
|
|
|
|
auto Text::checkMaxLinesLimit(const TextBlock &textBlock, unsigned int limitVal)
|
|
-> std::tuple<AdditionBound, TextBlock>
|
|
{
|
|
auto preDrawLines = makePreDrawLines(textBlock);
|
|
|
|
debug_text("Text lines height: %d, preDraw height: %d, Widget max h: %d",
|
|
lines->linesHeight(),
|
|
preDrawLines->linesHeight(),
|
|
area(Area::Max).h);
|
|
|
|
if (preDrawLines->maxWidth() == 0) {
|
|
preDrawLines->erase();
|
|
return {AdditionBound::CantAdd, textBlock};
|
|
}
|
|
|
|
if (preDrawLines->size() > limitVal) {
|
|
|
|
debug_text("Text at max line size can't add whole bock, try to split");
|
|
|
|
for (unsigned int signCounter = textBlock.length(); signCounter != 0; signCounter--) {
|
|
|
|
preDrawLines->erase();
|
|
|
|
auto partBlockText = textBlock.getText().substr(0, signCounter);
|
|
auto blockFormat = textBlock.getFormat();
|
|
|
|
preDrawLines = makePreDrawLines(TextBlock(partBlockText, std::make_unique<TextFormat>(*blockFormat)));
|
|
|
|
debug_text("Text lines height: %d, preDraw height: %d, Widget max h: %d",
|
|
lines->linesHeight(),
|
|
preDrawLines->linesHeight(),
|
|
area(Area::Max).h);
|
|
|
|
if (preDrawLines->size() <= limitVal) {
|
|
|
|
preDrawLines->erase();
|
|
|
|
debug_text("Text at max line size adding part of block. Original: %s, Fit part %s",
|
|
textBlock.getText().c_str(),
|
|
partBlockText.c_str());
|
|
|
|
return {AdditionBound::CanAddPart,
|
|
TextBlock(partBlockText, std::make_unique<TextFormat>(*blockFormat))};
|
|
}
|
|
}
|
|
|
|
preDrawLines->erase();
|
|
// If not a part of block can fit return hit bound.
|
|
return {AdditionBound::CantAdd, textBlock};
|
|
}
|
|
|
|
preDrawLines->erase();
|
|
return {AdditionBound::CanAddAll, textBlock};
|
|
}
|
|
|
|
auto Text::checkAdditionBounds(const std::uint32_t utfVal) -> AdditionBound
|
|
{
|
|
auto returnValue = AdditionBound::CanAddAll;
|
|
|
|
for (auto limit : limitsList) {
|
|
|
|
switch (limit.limitType) {
|
|
case TextLimitType::MaxSignsCount:
|
|
returnValue = checkMaxSignsLimit(limit.limitValue);
|
|
break;
|
|
case TextLimitType::MaxLines:
|
|
returnValue = checkMaxLinesLimit(utfVal, limit.limitValue);
|
|
break;
|
|
case TextLimitType::MaxSize:
|
|
returnValue = checkMaxSizeLimit(utfVal);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (returnValue == AdditionBound::CantAdd) {
|
|
return returnValue;
|
|
}
|
|
}
|
|
return returnValue;
|
|
}
|
|
|
|
auto Text::checkAdditionBounds(const TextBlock &textBlock) -> std::tuple<AdditionBound, TextBlock>
|
|
{
|
|
std::tuple<AdditionBound, TextBlock> returnValue = {AdditionBound::CanAddAll, textBlock};
|
|
auto shortestProcessedBlock = textBlock;
|
|
|
|
for (auto limit : limitsList) {
|
|
|
|
switch (limit.limitType) {
|
|
case TextLimitType::MaxSignsCount:
|
|
returnValue = checkMaxSignsLimit(textBlock, limit.limitValue);
|
|
break;
|
|
case TextLimitType::MaxLines:
|
|
returnValue = checkMaxLinesLimit(textBlock, limit.limitValue);
|
|
break;
|
|
case TextLimitType::MaxSize:
|
|
returnValue = checkMaxSizeLimit(textBlock);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (std::get<0>(returnValue) == AdditionBound::CanAddPart) {
|
|
if (std::get<1>(returnValue).length() < shortestProcessedBlock.length()) {
|
|
shortestProcessedBlock = std::get<1>(returnValue);
|
|
}
|
|
}
|
|
|
|
if (std::get<0>(returnValue) == AdditionBound::CantAdd) {
|
|
return returnValue;
|
|
}
|
|
}
|
|
|
|
return {std::get<0>(returnValue), shortestProcessedBlock};
|
|
}
|
|
|
|
bool Text::addChar(std::uint32_t utf_value)
|
|
{
|
|
cursor->addChar(utf_value);
|
|
auto block = document->getBlock(cursor);
|
|
|
|
if (block == nullptr) {
|
|
return false;
|
|
}
|
|
debug_text("value: %d, block len: %d", utf_value, block->length());
|
|
return true;
|
|
}
|
|
|
|
bool Text::removeChar()
|
|
{
|
|
if (!document->isEmpty()) {
|
|
cursor->removeChar();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Text::onTextChanged()
|
|
{
|
|
if (textChangedCallback) {
|
|
textChangedCallback(*this, getText());
|
|
}
|
|
}
|
|
|
|
TextBackup Text::backupText() const
|
|
{
|
|
return TextBackup{std::list<TextBlock>(document->getBlocks().begin(), document->getBlocks().end()),
|
|
cursor->getAbsolutePosition()};
|
|
}
|
|
|
|
void Text::restoreFrom(const TextBackup &backup)
|
|
{
|
|
setText(std::make_unique<TextDocument>(backup.document));
|
|
|
|
// If backup cursor position greater than new text length do not move cursor.
|
|
if (getText().length() > backup.cursorPos) {
|
|
auto cursorPosDiff = getText().length() - backup.cursorPos;
|
|
|
|
// Move cursor to backup position from end of document.
|
|
cursor->TextCursor::moveCursor(NavigationDirection::LEFT, cursorPosDiff);
|
|
}
|
|
}
|
|
|
|
void Text::accept(GuiVisitor &visitor)
|
|
{
|
|
visitor.visit(*this);
|
|
}
|
|
|
|
} /* namespace gui */
|