1182 lines
25 KiB
C++
1182 lines
25 KiB
C++
|
|
#include "widgets/lineedit/lineedit.h"
|
|
#include "core/utf8reader.h"
|
|
#include "core/colorf.h"
|
|
#include <algorithm>
|
|
|
|
LineEdit::LineEdit(Widget* parent) : Widget(parent)
|
|
{
|
|
SetStyleClass("lineedit");
|
|
|
|
timer = new Timer(this);
|
|
timer->FuncExpired = [=]() { OnTimerExpired(); };
|
|
|
|
scroll_timer = new Timer(this);
|
|
scroll_timer->FuncExpired = [=]() { OnScrollTimerExpired(); };
|
|
|
|
SetCursor(StandardCursor::ibeam);
|
|
}
|
|
|
|
LineEdit::~LineEdit()
|
|
{
|
|
}
|
|
|
|
bool LineEdit::IsReadOnly() const
|
|
{
|
|
return readonly;
|
|
}
|
|
|
|
LineEdit::Alignment LineEdit::GetAlignment() const
|
|
{
|
|
return align_left;
|
|
}
|
|
|
|
bool LineEdit::IsLowercase() const
|
|
{
|
|
return lowercase;
|
|
}
|
|
|
|
bool LineEdit::IsUppercase() const
|
|
{
|
|
return uppercase;
|
|
}
|
|
|
|
bool LineEdit::IsPasswordMode() const
|
|
{
|
|
return password_mode;
|
|
}
|
|
|
|
int LineEdit::GetMaxLength() const
|
|
{
|
|
return max_length;
|
|
}
|
|
|
|
std::string LineEdit::GetText() const
|
|
{
|
|
return text;
|
|
}
|
|
|
|
int LineEdit::GetTextInt() const
|
|
{
|
|
return std::atoi(text.c_str());
|
|
}
|
|
|
|
float LineEdit::GetTextFloat() const
|
|
{
|
|
return (float)std::atof(text.c_str());
|
|
}
|
|
|
|
std::string LineEdit::GetSelection() const
|
|
{
|
|
int start = std::min(selection_start, selection_start + selection_length);
|
|
return text.substr(start, std::abs(selection_length));
|
|
}
|
|
|
|
int LineEdit::GetSelectionStart() const
|
|
{
|
|
return selection_start;
|
|
}
|
|
|
|
int LineEdit::GetSelectionLength() const
|
|
{
|
|
return selection_length;
|
|
}
|
|
|
|
int LineEdit::GetCursorPos() const
|
|
{
|
|
return cursor_pos;
|
|
}
|
|
|
|
Size LineEdit::GetTextSize()
|
|
{
|
|
Canvas* canvas = GetCanvas();
|
|
return GetVisualTextSize(canvas);
|
|
}
|
|
|
|
Size LineEdit::GetTextSize(const std::string& str)
|
|
{
|
|
Canvas* canvas = GetCanvas();
|
|
return canvas->measureText(str).size();
|
|
}
|
|
|
|
double LineEdit::GetPreferredContentWidth()
|
|
{
|
|
return GetTextSize().width;
|
|
}
|
|
|
|
double LineEdit::GetPreferredContentHeight(double width)
|
|
{
|
|
return GetTextSize().height;
|
|
}
|
|
|
|
void LineEdit::SelectAll()
|
|
{
|
|
SetTextSelection(0, (int)text.size());
|
|
Update();
|
|
}
|
|
|
|
void LineEdit::SetReadOnly(bool enable)
|
|
{
|
|
if (readonly != enable)
|
|
{
|
|
readonly = enable;
|
|
Update();
|
|
}
|
|
}
|
|
|
|
void LineEdit::SetAlignment(Alignment newalignment)
|
|
{
|
|
if (alignment != newalignment)
|
|
{
|
|
alignment = newalignment;
|
|
Update();
|
|
}
|
|
}
|
|
|
|
void LineEdit::SetLowercase(bool enable)
|
|
{
|
|
if (lowercase != enable)
|
|
{
|
|
lowercase = enable;
|
|
text = ToLower(text);
|
|
Update();
|
|
}
|
|
}
|
|
|
|
void LineEdit::SetUppercase(bool enable)
|
|
{
|
|
if (uppercase != enable)
|
|
{
|
|
uppercase = enable;
|
|
text = ToUpper(text);
|
|
Update();
|
|
}
|
|
}
|
|
|
|
void LineEdit::SetPasswordMode(bool enable)
|
|
{
|
|
if (password_mode != enable)
|
|
{
|
|
password_mode = enable;
|
|
Update();
|
|
}
|
|
}
|
|
|
|
void LineEdit::SetMaxLength(int length)
|
|
{
|
|
if (max_length != length)
|
|
{
|
|
max_length = length;
|
|
if ((int)text.length() > length)
|
|
{
|
|
if (FuncBeforeEditChanged)
|
|
FuncBeforeEditChanged();
|
|
text = text.substr(0, length);
|
|
if (FuncAfterEditChanged)
|
|
FuncAfterEditChanged();
|
|
}
|
|
Update();
|
|
}
|
|
}
|
|
|
|
void LineEdit::SetText(const std::string& newtext)
|
|
{
|
|
if (lowercase)
|
|
text = ToLower(newtext);
|
|
else if (uppercase)
|
|
text = ToUpper(newtext);
|
|
else
|
|
text = newtext;
|
|
|
|
clip_start_offset = 0;
|
|
UpdateTextClipping();
|
|
SetCursorPos((int)text.size());
|
|
SetTextSelection(0, 0);
|
|
Update();
|
|
}
|
|
|
|
void LineEdit::SetTextInt(int number)
|
|
{
|
|
text = std::to_string(number);
|
|
clip_start_offset = 0;
|
|
UpdateTextClipping();
|
|
SetCursorPos((int)text.size());
|
|
SetTextSelection(0, 0);
|
|
Update();
|
|
}
|
|
|
|
void LineEdit::SetTextFloat(float number, int num_decimal_places)
|
|
{
|
|
text = ToFixed(number, num_decimal_places);
|
|
clip_start_offset = 0;
|
|
UpdateTextClipping();
|
|
SetCursorPos((int)text.size());
|
|
SetTextSelection(0, 0);
|
|
Update();
|
|
}
|
|
|
|
void LineEdit::SetSelection(int pos, int length)
|
|
{
|
|
//don't call FuncSelectionChanged() here, because this
|
|
//member is for public usage
|
|
selection_start = pos;
|
|
selection_length = length;
|
|
Update();
|
|
}
|
|
|
|
void LineEdit::ClearSelection()
|
|
{
|
|
//don't call FuncSelectionChanged() here, because this
|
|
//member is for public usage
|
|
SetSelection(0, 0);
|
|
Update();
|
|
}
|
|
|
|
void LineEdit::DeleteSelectedText()
|
|
{
|
|
if (GetSelectionLength() == 0)
|
|
return;
|
|
|
|
int sel_start = selection_start;
|
|
int sel_end = selection_start + selection_length;
|
|
if (sel_start > sel_end)
|
|
std::swap(sel_start, sel_end);
|
|
|
|
text = text.substr(0, sel_start) + text.substr(sel_end, text.size());
|
|
cursor_pos = sel_start;
|
|
SetTextSelection(0, 0);
|
|
int old_pos = GetCursorPos();
|
|
SetCursorPos(0);
|
|
SetCursorPos(old_pos);
|
|
}
|
|
|
|
void LineEdit::SetCursorPos(int pos)
|
|
{
|
|
cursor_pos = pos;
|
|
UpdateTextClipping();
|
|
Update();
|
|
}
|
|
|
|
void LineEdit::SetInputMask(const std::string& mask)
|
|
{
|
|
input_mask = mask;
|
|
}
|
|
|
|
void LineEdit::SetNumericMode(bool enable, bool decimals)
|
|
{
|
|
numeric_mode = enable;
|
|
numeric_mode_decimals = decimals;
|
|
}
|
|
|
|
void LineEdit::SetDecimalCharacter(const std::string& new_decimal_char)
|
|
{
|
|
decimal_char = new_decimal_char;
|
|
}
|
|
|
|
void LineEdit::SetSelectAllOnFocusGain(bool enable)
|
|
{
|
|
select_all_on_focus_gain = enable;
|
|
}
|
|
|
|
void LineEdit::OnMouseMove(const Point& pos)
|
|
{
|
|
if (mouse_selecting && !ignore_mouse_events)
|
|
{
|
|
if (pos.x < 0.0 || pos.x >= GetWidth())
|
|
{
|
|
if (pos.x < 0.0)
|
|
mouse_moves_left = true;
|
|
else
|
|
mouse_moves_left = false;
|
|
|
|
if (!readonly)
|
|
scroll_timer->Start(50, true);
|
|
}
|
|
else
|
|
{
|
|
scroll_timer->Stop();
|
|
cursor_pos = GetCharacterIndex(pos.x);
|
|
SetSelectionLength(cursor_pos - selection_start);
|
|
Update();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool LineEdit::OnMouseDown(const Point& pos, InputKey key)
|
|
{
|
|
if (key == InputKey::LeftMouse)
|
|
{
|
|
if (HasFocus())
|
|
{
|
|
SetPointerCapture();
|
|
mouse_selecting = true;
|
|
cursor_pos = GetCharacterIndex(pos.x);
|
|
SetTextSelection(cursor_pos, 0);
|
|
}
|
|
else
|
|
{
|
|
SetFocus();
|
|
}
|
|
Update();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LineEdit::OnMouseDoubleclick(const Point& pos, InputKey key)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool LineEdit::OnMouseUp(const Point& pos, InputKey key)
|
|
{
|
|
if (mouse_selecting && key == InputKey::LeftMouse)
|
|
{
|
|
if (ignore_mouse_events) // This prevents text selection from changing from what was set when focus was gained.
|
|
{
|
|
ReleasePointerCapture();
|
|
ignore_mouse_events = false;
|
|
mouse_selecting = false;
|
|
}
|
|
else
|
|
{
|
|
scroll_timer->Stop();
|
|
ReleasePointerCapture();
|
|
mouse_selecting = false;
|
|
int sel_end = GetCharacterIndex(pos.x);
|
|
SetSelectionLength(sel_end - selection_start);
|
|
cursor_pos = sel_end;
|
|
SetFocus();
|
|
Update();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void LineEdit::OnKeyChar(std::string chars)
|
|
{
|
|
if (FuncFilterKeyChar)
|
|
{
|
|
chars = FuncFilterKeyChar(chars);
|
|
if (chars.empty())
|
|
return;
|
|
}
|
|
|
|
if (!chars.empty() && !(chars[0] >= 0 && chars[0] < 32))
|
|
{
|
|
if (FuncBeforeEditChanged)
|
|
FuncBeforeEditChanged();
|
|
|
|
DeleteSelectedText();
|
|
if (input_mask.empty())
|
|
{
|
|
if (numeric_mode)
|
|
{
|
|
// '-' can only be added once, and only as the first character.
|
|
if (chars == "-" && cursor_pos == 0 && text.find("-") == std::string::npos)
|
|
{
|
|
if (InsertText(cursor_pos, chars))
|
|
cursor_pos += (int)chars.size();
|
|
}
|
|
else if (numeric_mode_decimals && chars == decimal_char && cursor_pos > 0) // add decimal char
|
|
{
|
|
if (text.find(decimal_char) == std::string::npos) // allow only one decimal char.
|
|
{
|
|
if (InsertText(cursor_pos, chars))
|
|
cursor_pos += (int)chars.size();
|
|
}
|
|
}
|
|
else if (numeric_mode_characters.find(chars) != std::string::npos) // 0-9
|
|
{
|
|
if (InsertText(cursor_pos, chars))
|
|
cursor_pos += (int)chars.size();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// not in any special mode, just insert the string.
|
|
if (InsertText(cursor_pos, chars))
|
|
cursor_pos += (int)chars.size();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (InputMaskAcceptsInput(cursor_pos, chars))
|
|
{
|
|
if (InsertText(cursor_pos, chars))
|
|
cursor_pos += (int)chars.size();
|
|
}
|
|
}
|
|
UpdateTextClipping();
|
|
|
|
if (FuncAfterEditChanged)
|
|
FuncAfterEditChanged();
|
|
}
|
|
}
|
|
|
|
void LineEdit::OnKeyDown(InputKey key)
|
|
{
|
|
if (FuncIgnoreKeyDown && FuncIgnoreKeyDown(key))
|
|
return;
|
|
|
|
if (key == InputKey::Enter)
|
|
{
|
|
if (FuncEnterPressed)
|
|
FuncEnterPressed();
|
|
return;
|
|
}
|
|
|
|
if (!readonly) // Do not flash cursor when readonly
|
|
{
|
|
cursor_blink_visible = true;
|
|
timer->Start(500); // don't blink cursor when moving or typing.
|
|
}
|
|
|
|
if (key == InputKey::Enter || key == InputKey::Escape || key == InputKey::Tab)
|
|
{
|
|
// Do not consume these.
|
|
return;
|
|
}
|
|
else if (key == InputKey::A && GetKeyState(InputKey::Ctrl))
|
|
{
|
|
// select all
|
|
SetTextSelection(0, (int)text.size());
|
|
cursor_pos = selection_length;
|
|
UpdateTextClipping();
|
|
Update();
|
|
}
|
|
else if (key == InputKey::C && GetKeyState(InputKey::Ctrl))
|
|
{
|
|
if (!password_mode) // Do not allow copying the password to clipboard
|
|
{
|
|
std::string str = GetSelection();
|
|
SetClipboardText(str);
|
|
}
|
|
}
|
|
else if (readonly)
|
|
{
|
|
// Do not consume messages on read only component (only allow CTRL-A and CTRL-C)
|
|
return;
|
|
}
|
|
else if (key == InputKey::Left)
|
|
{
|
|
Move(-1, GetKeyState(InputKey::Ctrl), GetKeyState(InputKey::Shift));
|
|
}
|
|
else if (key == InputKey::Right)
|
|
{
|
|
Move(1, GetKeyState(InputKey::Ctrl), GetKeyState(InputKey::Shift));
|
|
}
|
|
else if (key == InputKey::Backspace)
|
|
{
|
|
Backspace();
|
|
UpdateTextClipping();
|
|
}
|
|
else if (key == InputKey::Delete)
|
|
{
|
|
Del();
|
|
UpdateTextClipping();
|
|
}
|
|
else if (key == InputKey::Home)
|
|
{
|
|
SetSelectionStart(cursor_pos);
|
|
cursor_pos = 0;
|
|
if (GetKeyState(InputKey::Shift))
|
|
SetSelectionLength(-selection_start);
|
|
else
|
|
SetTextSelection(0, 0);
|
|
UpdateTextClipping();
|
|
Update();
|
|
}
|
|
else if (key == InputKey::End)
|
|
{
|
|
SetSelectionStart(cursor_pos);
|
|
cursor_pos = (int)text.size();
|
|
if (GetKeyState(InputKey::Shift))
|
|
SetSelectionLength((int)text.size() - selection_start);
|
|
else
|
|
SetTextSelection(0, 0);
|
|
UpdateTextClipping();
|
|
Update();
|
|
}
|
|
else if (key == InputKey::X && GetKeyState(InputKey::Ctrl))
|
|
{
|
|
std::string str = GetSelection();
|
|
DeleteSelectedText();
|
|
SetClipboardText(str);
|
|
UpdateTextClipping();
|
|
}
|
|
else if (key == InputKey::V && GetKeyState(InputKey::Ctrl))
|
|
{
|
|
std::string str = GetClipboardText();
|
|
std::string::const_iterator end_str = std::remove(str.begin(), str.end(), '\n');
|
|
str.resize(end_str - str.begin());
|
|
end_str = std::remove(str.begin(), str.end(), '\r');
|
|
str.resize(end_str - str.begin());
|
|
DeleteSelectedText();
|
|
|
|
if (input_mask.empty())
|
|
{
|
|
if (numeric_mode)
|
|
{
|
|
std::string present_text = GetText();
|
|
|
|
bool present_minus = present_text.find('-') != std::string::npos;
|
|
bool str_minus = str.find('-') != std::string::npos;
|
|
|
|
if (!present_minus || !str_minus)
|
|
{
|
|
if ((!present_minus && !str_minus) || //if no minus found
|
|
(str_minus && cursor_pos == 0 && str[0] == '-') || //if there's minus in text to paste
|
|
(present_minus && cursor_pos > 0)) //if there's minus in the beginning of control's text
|
|
{
|
|
if (numeric_mode_decimals)
|
|
{
|
|
std::string::size_type decimal_point_pos;
|
|
if ((decimal_point_pos = str.find_first_not_of(numeric_mode_characters, str[0] == '-' ? 1 : 0)) == std::string::npos) //no decimal char inside string to paste
|
|
{ //we don't look at the position of decimal char inside of text in the textbox, if it's present
|
|
if (InsertText(cursor_pos, str))
|
|
SetCursorPos(cursor_pos + (int)str.length());
|
|
}
|
|
else
|
|
{
|
|
if (present_text.find(decimal_char) == std::string::npos &&
|
|
str[decimal_point_pos] == decimal_char[0] &&
|
|
str.find_first_not_of(numeric_mode_characters, decimal_point_pos + 1) == std::string::npos) //allow only one decimal char in the string to paste
|
|
{
|
|
if (InsertText(cursor_pos, str))
|
|
SetCursorPos(cursor_pos + (int)str.length());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (str.find_first_not_of(numeric_mode_characters, str[0] == '-' ? 1 : 0) == std::string::npos)
|
|
{
|
|
if (InsertText(cursor_pos, str))
|
|
SetCursorPos(cursor_pos + (int)str.length());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (InsertText(cursor_pos, str))
|
|
SetCursorPos(cursor_pos + (int)str.length());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (InputMaskAcceptsInput(cursor_pos, str))
|
|
{
|
|
if (InsertText(cursor_pos, str))
|
|
SetCursorPos(cursor_pos + (int)str.length());
|
|
}
|
|
}
|
|
|
|
UpdateTextClipping();
|
|
}
|
|
else if (GetKeyState(InputKey::Ctrl) && key == InputKey::Z)
|
|
{
|
|
if (!readonly)
|
|
{
|
|
std::string tmp = undo_info.undo_text;
|
|
undo_info.undo_text = GetText();
|
|
SetText(tmp);
|
|
}
|
|
}
|
|
else if (key == InputKey::Shift)
|
|
{
|
|
if (selection_start == -1)
|
|
SetTextSelection(cursor_pos, 0);
|
|
}
|
|
|
|
if (FuncAfterEditChanged)
|
|
FuncAfterEditChanged();
|
|
}
|
|
|
|
void LineEdit::OnKeyUp(InputKey key)
|
|
{
|
|
}
|
|
|
|
void LineEdit::OnSetFocus()
|
|
{
|
|
if (!readonly)
|
|
timer->Start(500);
|
|
if (select_all_on_focus_gain)
|
|
SelectAll();
|
|
ignore_mouse_events = true;
|
|
cursor_pos = (int)text.length();
|
|
|
|
Update();
|
|
|
|
if (FuncFocusGained)
|
|
FuncFocusGained();
|
|
}
|
|
|
|
void LineEdit::OnLostFocus()
|
|
{
|
|
timer->Stop();
|
|
SetTextSelection(0, 0);
|
|
|
|
Update();
|
|
|
|
if (FuncFocusLost)
|
|
FuncFocusLost();
|
|
}
|
|
|
|
void LineEdit::Move(int steps, bool ctrl, bool shift)
|
|
{
|
|
if (shift && selection_length == 0)
|
|
SetSelectionStart(cursor_pos);
|
|
|
|
// Jump over words if control is pressed.
|
|
if (ctrl)
|
|
{
|
|
if (steps < 0)
|
|
steps = FindPreviousBreakCharacter(cursor_pos - 1) - cursor_pos;
|
|
else
|
|
steps = FindNextBreakCharacter(cursor_pos + 1) - cursor_pos;
|
|
|
|
cursor_pos += steps;
|
|
if (cursor_pos < 0)
|
|
cursor_pos = 0;
|
|
if (cursor_pos > (int)text.size())
|
|
cursor_pos = (int)text.size();
|
|
}
|
|
else
|
|
{
|
|
UTF8Reader utf8_reader(text.data(), text.length());
|
|
utf8_reader.set_position(cursor_pos);
|
|
if (steps > 0)
|
|
{
|
|
for (int i = 0; i < steps; i++)
|
|
utf8_reader.next();
|
|
}
|
|
else if (steps < 0)
|
|
{
|
|
for (int i = 0; i < -steps; i++)
|
|
utf8_reader.prev();
|
|
}
|
|
|
|
cursor_pos = (int)utf8_reader.position();
|
|
}
|
|
|
|
|
|
// Clear the selection if a cursor key is pressed but shift isn't down.
|
|
if (shift)
|
|
SetSelectionLength(cursor_pos - selection_start);
|
|
else
|
|
SetTextSelection(-1, 0);
|
|
|
|
UpdateTextClipping();
|
|
|
|
Update();
|
|
|
|
undo_info.first_text_insert = true;
|
|
}
|
|
|
|
bool LineEdit::InsertText(int pos, const std::string& str)
|
|
{
|
|
undo_info.first_erase = false;
|
|
if (undo_info.first_text_insert)
|
|
{
|
|
undo_info.undo_text = GetText();
|
|
undo_info.first_text_insert = false;
|
|
}
|
|
|
|
// checking if insert exceeds max length
|
|
if (UTF8Reader::utf8_length(text) + UTF8Reader::utf8_length(str) > max_length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (lowercase)
|
|
text.insert(pos, ToLower(str));
|
|
else if (uppercase)
|
|
text.insert(pos, ToUpper(str));
|
|
else
|
|
text.insert(pos, str);
|
|
|
|
UpdateTextClipping();
|
|
Update();
|
|
return true;
|
|
}
|
|
|
|
void LineEdit::Backspace()
|
|
{
|
|
if (undo_info.first_erase)
|
|
{
|
|
undo_info.first_erase = false;
|
|
undo_info.undo_text = GetText();
|
|
}
|
|
|
|
if (GetSelectionLength() != 0)
|
|
{
|
|
DeleteSelectedText();
|
|
Update();
|
|
}
|
|
else
|
|
{
|
|
if (cursor_pos > 0)
|
|
{
|
|
UTF8Reader utf8_reader(text.data(), text.length());
|
|
utf8_reader.set_position(cursor_pos);
|
|
utf8_reader.prev();
|
|
size_t length = utf8_reader.char_length();
|
|
text.erase(cursor_pos - length, length);
|
|
cursor_pos -= (int)length;
|
|
Update();
|
|
}
|
|
}
|
|
|
|
int old_pos = GetCursorPos();
|
|
SetCursorPos(0);
|
|
SetCursorPos(old_pos);
|
|
}
|
|
|
|
void LineEdit::Del()
|
|
{
|
|
if (undo_info.first_erase)
|
|
{
|
|
undo_info.first_erase = false;
|
|
undo_info.undo_text = GetText();
|
|
}
|
|
|
|
if (GetSelectionLength() != 0)
|
|
{
|
|
DeleteSelectedText();
|
|
Update();
|
|
}
|
|
else
|
|
{
|
|
if (cursor_pos < (int)text.size())
|
|
{
|
|
UTF8Reader utf8_reader(text.data(), text.length());
|
|
utf8_reader.set_position(cursor_pos);
|
|
size_t length = utf8_reader.char_length();
|
|
text.erase(cursor_pos, length);
|
|
Update();
|
|
}
|
|
}
|
|
}
|
|
|
|
int LineEdit::GetCharacterIndex(double mouse_x)
|
|
{
|
|
if (text.size() <= 1)
|
|
{
|
|
return (int)text.size();
|
|
}
|
|
|
|
Canvas* canvas = GetCanvas();
|
|
UTF8Reader utf8_reader(text.data(), text.length());
|
|
|
|
int seek_start = clip_start_offset;
|
|
int seek_end = (int)text.size();
|
|
int seek_center = (seek_start + seek_end) / 2;
|
|
|
|
//fast search
|
|
while (true)
|
|
{
|
|
utf8_reader.set_position(seek_center);
|
|
utf8_reader.move_to_leadbyte();
|
|
|
|
seek_center = (int)utf8_reader.position();
|
|
|
|
Size text_size = GetVisualTextSize(canvas, clip_start_offset, seek_center - clip_start_offset);
|
|
|
|
if (text_size.width > mouse_x)
|
|
seek_end = seek_center;
|
|
else
|
|
seek_start = seek_center;
|
|
|
|
if (seek_end - seek_start < 7)
|
|
break; //go to accurate search
|
|
|
|
seek_center = (seek_start + seek_end) / 2;
|
|
}
|
|
|
|
utf8_reader.set_position(seek_start);
|
|
utf8_reader.move_to_leadbyte();
|
|
|
|
//accurate search
|
|
while (true)
|
|
{
|
|
seek_center = (int)utf8_reader.position();
|
|
|
|
Size text_size = GetVisualTextSize(canvas, clip_start_offset, seek_center - clip_start_offset);
|
|
if (text_size.width > mouse_x || utf8_reader.is_end())
|
|
break;
|
|
|
|
utf8_reader.next();
|
|
}
|
|
|
|
return seek_center;
|
|
}
|
|
|
|
void LineEdit::UpdateTextClipping()
|
|
{
|
|
Canvas* canvas = GetCanvas();
|
|
if (!canvas)
|
|
return;
|
|
|
|
Size text_size = GetVisualTextSize(canvas, clip_start_offset, (int)text.size() - clip_start_offset);
|
|
|
|
if (cursor_pos < clip_start_offset)
|
|
clip_start_offset = cursor_pos;
|
|
|
|
Rect cursor_rect = GetCursorRect();
|
|
|
|
UTF8Reader utf8_reader(text.data(), text.length());
|
|
double width = GetWidth();
|
|
while (cursor_rect.x + cursor_rect.width > width)
|
|
{
|
|
utf8_reader.set_position(clip_start_offset);
|
|
utf8_reader.next();
|
|
clip_start_offset = (int)utf8_reader.position();
|
|
if (clip_start_offset == text.size())
|
|
break;
|
|
cursor_rect = GetCursorRect();
|
|
}
|
|
|
|
// Get number of chars of current text fitting in the lineedit.
|
|
int search_upper = (int)text.size();
|
|
int search_lower = clip_start_offset;
|
|
|
|
while (true)
|
|
{
|
|
int midpoint = (search_lower + search_upper) / 2;
|
|
|
|
utf8_reader.set_position(midpoint);
|
|
utf8_reader.move_to_leadbyte();
|
|
if (midpoint != utf8_reader.position())
|
|
utf8_reader.next();
|
|
midpoint = (int)utf8_reader.position();
|
|
|
|
if (midpoint == search_lower || midpoint == search_upper)
|
|
break;
|
|
|
|
Size midpoint_size = GetVisualTextSize(canvas, clip_start_offset, midpoint - clip_start_offset);
|
|
|
|
if (width < midpoint_size.width)
|
|
search_upper = midpoint;
|
|
else
|
|
search_lower = midpoint;
|
|
}
|
|
clip_end_offset = search_upper;
|
|
|
|
if (cursor_rect.x < 0.0)
|
|
{
|
|
clip_start_offset = cursor_pos;
|
|
}
|
|
}
|
|
|
|
Rect LineEdit::GetCursorRect()
|
|
{
|
|
Canvas* canvas = GetCanvas();
|
|
|
|
int substr_end = cursor_pos - clip_start_offset;
|
|
if (substr_end < 0)
|
|
substr_end = 0;
|
|
|
|
std::string clipped_text = text.substr(clip_start_offset, substr_end);
|
|
|
|
if (password_mode)
|
|
{
|
|
// If we are in password mode, we gonna return the right characters
|
|
clipped_text = CreatePassword(UTF8Reader::utf8_length(clipped_text));
|
|
}
|
|
|
|
Size text_size_before_cursor = canvas->measureText(clipped_text).size();
|
|
|
|
Rect cursor_rect;
|
|
cursor_rect.x = text_size_before_cursor.width;
|
|
cursor_rect.width = 1.0f;
|
|
|
|
cursor_rect.y = vertical_text_align.top;
|
|
cursor_rect.height = vertical_text_align.bottom - vertical_text_align.top;
|
|
|
|
return cursor_rect;
|
|
}
|
|
|
|
Rect LineEdit::GetSelectionRect()
|
|
{
|
|
Canvas* canvas = GetCanvas();
|
|
|
|
// text before selection:
|
|
|
|
std::string txt_before = GetVisibleTextBeforeSelection();
|
|
Size text_size_before_selection = canvas->measureText(txt_before).size();
|
|
|
|
// selection text:
|
|
std::string txt_selected = GetVisibleSelectedText();
|
|
Size text_size_selection = canvas->measureText(txt_selected).size();
|
|
|
|
Rect selection_rect;
|
|
selection_rect.x = text_size_before_selection.width;
|
|
selection_rect.width = text_size_selection.width;
|
|
selection_rect.y = vertical_text_align.top;
|
|
selection_rect.height = vertical_text_align.bottom - vertical_text_align.top;
|
|
return selection_rect;
|
|
}
|
|
|
|
int LineEdit::FindNextBreakCharacter(int search_start)
|
|
{
|
|
if (search_start >= int(text.size()) - 1)
|
|
return (int)text.size();
|
|
|
|
size_t pos = text.find_first_of(break_characters, search_start);
|
|
if (pos == std::string::npos)
|
|
return (int)text.size();
|
|
return (int)pos;
|
|
}
|
|
|
|
int LineEdit::FindPreviousBreakCharacter(int search_start)
|
|
{
|
|
if (search_start <= 0)
|
|
return 0;
|
|
size_t pos = text.find_last_of(break_characters, search_start);
|
|
if (pos == std::string::npos)
|
|
return 0;
|
|
return (int)pos;
|
|
}
|
|
|
|
void LineEdit::OnTimerExpired()
|
|
{
|
|
if (!IsVisible())
|
|
{
|
|
timer->Stop();
|
|
return;
|
|
}
|
|
|
|
if (cursor_blink_visible)
|
|
timer->Start(500);
|
|
else
|
|
timer->Start(500);
|
|
|
|
cursor_blink_visible = !cursor_blink_visible;
|
|
Update();
|
|
}
|
|
|
|
void LineEdit::OnGeometryChanged()
|
|
{
|
|
Canvas* canvas = GetCanvas();
|
|
|
|
vertical_text_align = canvas->verticalTextAlign();
|
|
|
|
clip_start_offset = 0;
|
|
cursor_pos = 0;
|
|
UpdateTextClipping();
|
|
}
|
|
|
|
std::string LineEdit::GetVisibleTextBeforeSelection()
|
|
{
|
|
std::string ret;
|
|
int sel_start = std::min(selection_start, selection_start + selection_length);
|
|
int start = std::min(sel_start, clip_start_offset);
|
|
|
|
if (start < clip_start_offset)
|
|
return ret;
|
|
|
|
int end = std::min(sel_start, clip_end_offset);
|
|
|
|
ret = text.substr(start, end - start);
|
|
|
|
// If we are in password mode, we gonna return the right characters
|
|
if (password_mode)
|
|
ret = CreatePassword(UTF8Reader::utf8_length(ret));
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::string LineEdit::GetVisibleSelectedText()
|
|
{
|
|
std::string ret;
|
|
|
|
if (selection_length == 0)
|
|
return ret;
|
|
|
|
int sel_start = std::min(selection_start, selection_start + selection_length);
|
|
int sel_end = std::max(selection_start, selection_start + selection_length);
|
|
int end = std::min(clip_end_offset, sel_end);
|
|
int start = std::max(clip_start_offset, sel_start);
|
|
|
|
if (start > end)
|
|
return ret;
|
|
|
|
if (start == end)
|
|
return ret;
|
|
|
|
ret = text.substr(start, end - start);
|
|
|
|
// If we are in password mode, we gonna return the right characters
|
|
if (password_mode)
|
|
ret = CreatePassword(UTF8Reader::utf8_length(ret));
|
|
|
|
return ret;
|
|
}
|
|
|
|
void LineEdit::SetSelectionStart(int start)
|
|
{
|
|
if (FuncSelectionChanged && selection_length && selection_start != start)
|
|
FuncSelectionChanged();
|
|
|
|
selection_start = start;
|
|
}
|
|
|
|
void LineEdit::SetSelectionLength(int length)
|
|
{
|
|
if (FuncSelectionChanged && selection_length != length)
|
|
FuncSelectionChanged();
|
|
|
|
selection_length = length;
|
|
}
|
|
|
|
void LineEdit::SetTextSelection(int start, int length)
|
|
{
|
|
if (FuncSelectionChanged && (selection_length != length || (selection_length && selection_start != start)))
|
|
FuncSelectionChanged();
|
|
|
|
selection_start = start;
|
|
selection_length = length;
|
|
}
|
|
|
|
std::string LineEdit::GetVisibleTextAfterSelection()
|
|
{
|
|
// returns the whole visible string if there is no selection.
|
|
std::string ret;
|
|
|
|
int sel_end = std::max(selection_start, selection_start + selection_length);
|
|
int start = std::max(clip_start_offset, sel_end);
|
|
|
|
int end = clip_end_offset;
|
|
if (start > end)
|
|
return ret;
|
|
|
|
if (clip_end_offset == sel_end)
|
|
return ret;
|
|
|
|
if (sel_end <= 0)
|
|
return ret;
|
|
else
|
|
{
|
|
ret = text.substr(start, end - start);
|
|
// If we are in password mode, we gonna return the right characters
|
|
if (password_mode)
|
|
ret = CreatePassword(UTF8Reader::utf8_length(ret));
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
void LineEdit::OnPaint(Canvas* canvas)
|
|
{
|
|
std::string txt_before = GetVisibleTextBeforeSelection();
|
|
std::string txt_selected = GetVisibleSelectedText();
|
|
std::string txt_after = GetVisibleTextAfterSelection();
|
|
|
|
if (txt_before.empty() && txt_selected.empty() && txt_after.empty())
|
|
{
|
|
txt_after = text.substr(clip_start_offset, clip_end_offset - clip_start_offset);
|
|
|
|
// If we are in password mode, we gonna return the right characters
|
|
if (password_mode)
|
|
txt_after = CreatePassword(UTF8Reader::utf8_length(txt_after));
|
|
}
|
|
|
|
Size size_before = canvas->measureText(txt_before).size();
|
|
Size size_selected = canvas->measureText(txt_selected).size();
|
|
|
|
if (!txt_selected.empty())
|
|
{
|
|
// Draw selection box.
|
|
Rect selection_rect = GetSelectionRect();
|
|
canvas->fillRect(selection_rect, HasFocus() ? GetStyleColor("selection-color") : GetStyleColor("no-focus-selection-color"));
|
|
}
|
|
|
|
// Draw text before selection
|
|
if (!txt_before.empty())
|
|
{
|
|
canvas->drawText(Point(0.0, canvas->verticalTextAlign().baseline), GetStyleColor("color"), txt_before);
|
|
}
|
|
if (!txt_selected.empty())
|
|
{
|
|
canvas->drawText(Point(size_before.width, canvas->verticalTextAlign().baseline), GetStyleColor("color"), txt_selected);
|
|
}
|
|
if (!txt_after.empty())
|
|
{
|
|
canvas->drawText(Point(size_before.width + size_selected.width, canvas->verticalTextAlign().baseline), GetStyleColor("color"), txt_after);
|
|
}
|
|
|
|
// draw cursor
|
|
if (HasFocus())
|
|
{
|
|
if (cursor_blink_visible)
|
|
{
|
|
Rect cursor_rect = GetCursorRect();
|
|
canvas->fillRect(cursor_rect, GetStyleColor("color"));
|
|
}
|
|
}
|
|
}
|
|
|
|
void LineEdit::OnScrollTimerExpired()
|
|
{
|
|
if (mouse_moves_left)
|
|
Move(-1, false, false);
|
|
else
|
|
Move(1, false, false);
|
|
}
|
|
|
|
void LineEdit::OnEnableChanged()
|
|
{
|
|
bool enabled = IsEnabled();
|
|
|
|
if (!enabled)
|
|
{
|
|
cursor_blink_visible = false;
|
|
timer->Stop();
|
|
}
|
|
Update();
|
|
}
|
|
|
|
bool LineEdit::InputMaskAcceptsInput(int cursor_pos, const std::string& str)
|
|
{
|
|
return str.find_first_not_of(input_mask) == std::string::npos;
|
|
}
|
|
|
|
std::string LineEdit::CreatePassword(std::string::size_type num_letters) const
|
|
{
|
|
return std::string(num_letters, '*');
|
|
}
|
|
|
|
Size LineEdit::GetVisualTextSize(Canvas* canvas, int pos, int npos) const
|
|
{
|
|
return canvas->measureText(password_mode ? CreatePassword(UTF8Reader::utf8_length(text.substr(pos, npos))) : text.substr(pos, npos)).size();
|
|
}
|
|
|
|
Size LineEdit::GetVisualTextSize(Canvas* canvas) const
|
|
{
|
|
return canvas->measureText(password_mode ? CreatePassword(UTF8Reader::utf8_length(text)) : text).size();
|
|
}
|
|
|
|
std::string LineEdit::ToFixed(float number, int num_decimal_places)
|
|
{
|
|
for (int i = 0; i < num_decimal_places; i++)
|
|
number *= 10.0f;
|
|
std::string val = std::to_string((int)std::round(number));
|
|
if ((int)val.size() < num_decimal_places)
|
|
val.resize(num_decimal_places + 1, 0);
|
|
return val.substr(0, val.size() - num_decimal_places) + "." + val.substr(val.size() - num_decimal_places);
|
|
}
|
|
|
|
std::string LineEdit::ToLower(const std::string& text)
|
|
{
|
|
return text;
|
|
}
|
|
|
|
std::string LineEdit::ToUpper(const std::string& text)
|
|
{
|
|
return text;
|
|
}
|
|
|
|
const std::string LineEdit::break_characters = " ::;,.-";
|
|
const std::string LineEdit::numeric_mode_characters = "0123456789";
|