Sfoglia il codice sorgente

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.
Michael Ragazzon 8 mesi fa
parent
commit
d7d0b6e14c

+ 21 - 5
Source/Core/Elements/WidgetTextInput.cpp

@@ -286,7 +286,6 @@ void WidgetTextInput::SetValue(String value)
 	else
 	else
 	{
 	{
 		TransformValue(value);
 		TransformValue(value);
-		RMLUI_ASSERTMSG(value.size() == initial_size, "TransformValue must not change the text length.");
 
 
 		text_element->SetText(value);
 		text_element->SetText(value);
 
 
@@ -301,6 +300,16 @@ void WidgetTextInput::SetValue(String value)
 
 
 void WidgetTextInput::TransformValue(String& /*value*/) {}
 void WidgetTextInput::TransformValue(String& /*value*/) {}
 
 
+int WidgetTextInput::DisplayIndexToAttributeIndex(int display_index, const String& /*attribute_value*/)
+{
+	return display_index;
+}
+
+int WidgetTextInput::AttributeIndexToDisplayIndex(int attribute_index, const String& /*attribute_value*/)
+{
+	return attribute_index;
+}
+
 void WidgetTextInput::SetMaxLength(int _max_length)
 void WidgetTextInput::SetMaxLength(int _max_length)
 {
 {
 	if (max_length != _max_length)
 	if (max_length != _max_length)
@@ -739,9 +748,11 @@ bool WidgetTextInput::AddCharacters(String string)
 		return false;
 		return false;
 
 
 	String value = GetAttributeValue();
 	String value = GetAttributeValue();
-	value.insert(std::min<size_t>((size_t)absolute_cursor_index, value.size()), string);
+	const int attribute_insert_index = DisplayIndexToAttributeIndex(absolute_cursor_index, value);
+	value.insert(std::min<size_t>((size_t)attribute_insert_index, value.size()), string);
 
 
-	absolute_cursor_index += (int)string.size();
+	const int new_cursor_attribute_index = AttributeIndexToDisplayIndex(attribute_insert_index + (int)string.size(), value);
+	absolute_cursor_index = new_cursor_attribute_index;
 	parent->SetAttribute("value", value);
 	parent->SetAttribute("value", value);
 
 
 	if (UpdateSelection(false))
 	if (UpdateSelection(false))
@@ -1504,8 +1515,13 @@ void WidgetTextInput::DeleteSelection()
 	if (selection_length > 0)
 	if (selection_length > 0)
 	{
 	{
 		String new_value = GetAttributeValue();
 		String new_value = GetAttributeValue();
-		const size_t selection_begin = std::min((size_t)selection_begin_index, (size_t)new_value.size());
-		new_value.erase(selection_begin, (size_t)selection_length);
+		const int selection_begin_index_attribute = DisplayIndexToAttributeIndex(selection_begin_index, new_value);
+		const int selection_end_index_attribute = DisplayIndexToAttributeIndex(selection_begin_index + selection_length, new_value);
+		RMLUI_ASSERT(selection_end_index_attribute >= selection_begin_index_attribute);
+
+		const size_t selection_begin = std::min((size_t)selection_begin_index_attribute, (size_t)new_value.size());
+		const size_t attribute_selection_length = size_t(selection_end_index_attribute - selection_begin_index_attribute);
+		new_value.erase(selection_begin, (size_t)attribute_selection_length);
 
 
 		// Move the cursor to the beginning of the old selection.
 		// Move the cursor to the beginning of the old selection.
 		absolute_cursor_index = selection_begin_index;
 		absolute_cursor_index = selection_begin_index;

+ 14 - 1
Source/Core/Elements/WidgetTextInput.h

@@ -124,6 +124,19 @@ protected:
 	/// Transforms the displayed value of the text box, typically used for password fields.
 	/// Transforms the displayed value of the text box, typically used for password fields.
 	/// @note Only use this for transforming characters, do not modify the length of the string.
 	/// @note Only use this for transforming characters, do not modify the length of the string.
 	virtual void TransformValue(String& value);
 	virtual void TransformValue(String& value);
+	/// Converts a display index to an attribute index.
+	/// @param[in] display_index Absolute index into the displayed value (see GetValue).
+	/// @param[in] attribute_value The attribute value to be considered.
+	/// @return Absolute index into the attribute value (see GetAttributeValue).
+	/// @note All indices stored in this class refer to the displayed value, unless otherwise specified. The display and
+	/// attribute indices are typically identical, but may differ when the transformed value (see TransformValue) has
+	/// modified the contents to be displayed.
+	virtual int DisplayIndexToAttributeIndex(int display_index, const String& attribute_value);
+	/// Converts an attribute index to a display index.
+	/// @param[in] attribute_index Absolute index into the attribute value (see GetAttributeValue).
+	/// @param[in] attribute_value The attribute value to be considered.
+	/// @return Absolute index into the displayed value (see GetValue).
+	virtual int AttributeIndexToDisplayIndex(int attribute_index, const String& attribute_value);
 	/// Called when the user pressed enter.
 	/// Called when the user pressed enter.
 	virtual void LineBreak() = 0;
 	virtual void LineBreak() = 0;
 
 
@@ -160,7 +173,7 @@ private:
 	/// @return True if selection was changed.
 	/// @return True if selection was changed.
 	bool MoveCursorHorizontal(CursorMovement movement, bool select, bool& out_of_bounds);
 	bool MoveCursorHorizontal(CursorMovement movement, bool select, bool& out_of_bounds);
 	/// Moves the cursor up and down the text field.
 	/// Moves the cursor up and down the text field.
-	/// @param[in] x How far to move the cursor.
+	/// @param[in] distance How far to move the cursor.
 	/// @param[in] select True if the movement will also move the selection cursor, false if not.
 	/// @param[in] select True if the movement will also move the selection cursor, false if not.
 	/// @param[out] out_of_bounds Set to true if the resulting line position is out of bounds, false if not.
 	/// @param[out] out_of_bounds Set to true if the resulting line position is out of bounds, false if not.
 	/// @return True if selection was changed.
 	/// @return True if selection was changed.

+ 14 - 2
Source/Core/Elements/WidgetTextInputSingleLinePassword.cpp

@@ -35,8 +35,20 @@ WidgetTextInputSingleLinePassword::WidgetTextInputSingleLinePassword(ElementForm
 
 
 void WidgetTextInputSingleLinePassword::TransformValue(String& value)
 void WidgetTextInputSingleLinePassword::TransformValue(String& value)
 {
 {
-	for (auto& c : value)
-		c = '*';
+	const size_t character_length = StringUtilities::LengthUTF8(value);
+	value.replace(0, value.length(), character_length, '*');
+}
+
+int WidgetTextInputSingleLinePassword::DisplayIndexToAttributeIndex(int display_index, const String& attribute_value)
+{
+	// Transforming from the attribute value to the display value (above) essentially strips away all continuation
+	// bytes. Thus, here we effectively count them back in up to the offset.
+	return StringUtilities::ConvertCharacterOffsetToByteOffset(attribute_value, display_index);
+}
+
+int WidgetTextInputSingleLinePassword::AttributeIndexToDisplayIndex(int attribute_index, const String& attribute_value)
+{
+	return (int)StringUtilities::LengthUTF8(StringView(attribute_value, 0, (size_t)attribute_index));
 }
 }
 
 
 } // namespace Rml
 } // namespace Rml

+ 4 - 1
Source/Core/Elements/WidgetTextInputSingleLinePassword.h

@@ -42,8 +42,11 @@ public:
 	WidgetTextInputSingleLinePassword(ElementFormControl* parent);
 	WidgetTextInputSingleLinePassword(ElementFormControl* parent);
 
 
 protected:
 protected:
-	/// Transforms the displayed value of the text box, typically used for password fields.
 	void TransformValue(String& value) override;
 	void TransformValue(String& value) override;
+
+	int DisplayIndexToAttributeIndex(int display_index, const String& attribute_value) override;
+
+	int AttributeIndexToDisplayIndex(int attribute_index, const String& attribute_value) override;
 };
 };
 
 
 } // namespace Rml
 } // namespace Rml