Skip to content

Commit d7d0b6e

Browse files
committed
Handle multi-byte characters in password input fields, see #735
Previsouly, the field displayed one asterisk per byte, which would result in multiple asterisks for multi-byte (non-ASCII) characters.
1 parent a9a3994 commit d7d0b6e

File tree

4 files changed

+53
-9
lines changed

4 files changed

+53
-9
lines changed

Source/Core/Elements/WidgetTextInput.cpp

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,6 @@ void WidgetTextInput::SetValue(String value)
286286
else
287287
{
288288
TransformValue(value);
289-
RMLUI_ASSERTMSG(value.size() == initial_size, "TransformValue must not change the text length.");
290289

291290
text_element->SetText(value);
292291

@@ -301,6 +300,16 @@ void WidgetTextInput::SetValue(String value)
301300

302301
void WidgetTextInput::TransformValue(String& /*value*/) {}
303302

303+
int WidgetTextInput::DisplayIndexToAttributeIndex(int display_index, const String& /*attribute_value*/)
304+
{
305+
return display_index;
306+
}
307+
308+
int WidgetTextInput::AttributeIndexToDisplayIndex(int attribute_index, const String& /*attribute_value*/)
309+
{
310+
return attribute_index;
311+
}
312+
304313
void WidgetTextInput::SetMaxLength(int _max_length)
305314
{
306315
if (max_length != _max_length)
@@ -739,9 +748,11 @@ bool WidgetTextInput::AddCharacters(String string)
739748
return false;
740749

741750
String value = GetAttributeValue();
742-
value.insert(std::min<size_t>((size_t)absolute_cursor_index, value.size()), string);
751+
const int attribute_insert_index = DisplayIndexToAttributeIndex(absolute_cursor_index, value);
752+
value.insert(std::min<size_t>((size_t)attribute_insert_index, value.size()), string);
743753

744-
absolute_cursor_index += (int)string.size();
754+
const int new_cursor_attribute_index = AttributeIndexToDisplayIndex(attribute_insert_index + (int)string.size(), value);
755+
absolute_cursor_index = new_cursor_attribute_index;
745756
parent->SetAttribute("value", value);
746757

747758
if (UpdateSelection(false))
@@ -1504,8 +1515,13 @@ void WidgetTextInput::DeleteSelection()
15041515
if (selection_length > 0)
15051516
{
15061517
String new_value = GetAttributeValue();
1507-
const size_t selection_begin = std::min((size_t)selection_begin_index, (size_t)new_value.size());
1508-
new_value.erase(selection_begin, (size_t)selection_length);
1518+
const int selection_begin_index_attribute = DisplayIndexToAttributeIndex(selection_begin_index, new_value);
1519+
const int selection_end_index_attribute = DisplayIndexToAttributeIndex(selection_begin_index + selection_length, new_value);
1520+
RMLUI_ASSERT(selection_end_index_attribute >= selection_begin_index_attribute);
1521+
1522+
const size_t selection_begin = std::min((size_t)selection_begin_index_attribute, (size_t)new_value.size());
1523+
const size_t attribute_selection_length = size_t(selection_end_index_attribute - selection_begin_index_attribute);
1524+
new_value.erase(selection_begin, (size_t)attribute_selection_length);
15091525

15101526
// Move the cursor to the beginning of the old selection.
15111527
absolute_cursor_index = selection_begin_index;

Source/Core/Elements/WidgetTextInput.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ class WidgetTextInput : public EventListener {
124124
/// Transforms the displayed value of the text box, typically used for password fields.
125125
/// @note Only use this for transforming characters, do not modify the length of the string.
126126
virtual void TransformValue(String& value);
127+
/// Converts a display index to an attribute index.
128+
/// @param[in] display_index Absolute index into the displayed value (see GetValue).
129+
/// @param[in] attribute_value The attribute value to be considered.
130+
/// @return Absolute index into the attribute value (see GetAttributeValue).
131+
/// @note All indices stored in this class refer to the displayed value, unless otherwise specified. The display and
132+
/// attribute indices are typically identical, but may differ when the transformed value (see TransformValue) has
133+
/// modified the contents to be displayed.
134+
virtual int DisplayIndexToAttributeIndex(int display_index, const String& attribute_value);
135+
/// Converts an attribute index to a display index.
136+
/// @param[in] attribute_index Absolute index into the attribute value (see GetAttributeValue).
137+
/// @param[in] attribute_value The attribute value to be considered.
138+
/// @return Absolute index into the displayed value (see GetValue).
139+
virtual int AttributeIndexToDisplayIndex(int attribute_index, const String& attribute_value);
127140
/// Called when the user pressed enter.
128141
virtual void LineBreak() = 0;
129142

@@ -160,7 +173,7 @@ class WidgetTextInput : public EventListener {
160173
/// @return True if selection was changed.
161174
bool MoveCursorHorizontal(CursorMovement movement, bool select, bool& out_of_bounds);
162175
/// Moves the cursor up and down the text field.
163-
/// @param[in] x How far to move the cursor.
176+
/// @param[in] distance How far to move the cursor.
164177
/// @param[in] select True if the movement will also move the selection cursor, false if not.
165178
/// @param[out] out_of_bounds Set to true if the resulting line position is out of bounds, false if not.
166179
/// @return True if selection was changed.

Source/Core/Elements/WidgetTextInputSingleLinePassword.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,20 @@ WidgetTextInputSingleLinePassword::WidgetTextInputSingleLinePassword(ElementForm
3535

3636
void WidgetTextInputSingleLinePassword::TransformValue(String& value)
3737
{
38-
for (auto& c : value)
39-
c = '*';
38+
const size_t character_length = StringUtilities::LengthUTF8(value);
39+
value.replace(0, value.length(), character_length, '*');
40+
}
41+
42+
int WidgetTextInputSingleLinePassword::DisplayIndexToAttributeIndex(int display_index, const String& attribute_value)
43+
{
44+
// Transforming from the attribute value to the display value (above) essentially strips away all continuation
45+
// bytes. Thus, here we effectively count them back in up to the offset.
46+
return StringUtilities::ConvertCharacterOffsetToByteOffset(attribute_value, display_index);
47+
}
48+
49+
int WidgetTextInputSingleLinePassword::AttributeIndexToDisplayIndex(int attribute_index, const String& attribute_value)
50+
{
51+
return (int)StringUtilities::LengthUTF8(StringView(attribute_value, 0, (size_t)attribute_index));
4052
}
4153

4254
} // namespace Rml

Source/Core/Elements/WidgetTextInputSingleLinePassword.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,11 @@ class WidgetTextInputSingleLinePassword : public WidgetTextInputSingleLine {
4242
WidgetTextInputSingleLinePassword(ElementFormControl* parent);
4343

4444
protected:
45-
/// Transforms the displayed value of the text box, typically used for password fields.
4645
void TransformValue(String& value) override;
46+
47+
int DisplayIndexToAttributeIndex(int display_index, const String& attribute_value) override;
48+
49+
int AttributeIndexToDisplayIndex(int attribute_index, const String& attribute_value) override;
4750
};
4851

4952
} // namespace Rml

0 commit comments

Comments
 (0)