From: Bowon Ryu Date: Tue, 29 Jun 2021 02:14:11 +0000 (+0000) Subject: Merge "Add InputFilter to TextField, TextEditor" into devel/master X-Git-Tag: dali_2.0.33~5 X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=commitdiff_plain;h=e172c89c895d46d430b2cf0a0dbceeea0ae09b29;hp=37cb4e81e6b8730f8919fc7b3684bc5cd83b5a73 Merge "Add InputFilter to TextField, TextEditor" into devel/master --- diff --git a/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp b/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp index 31d1762..ba8e3d8 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp @@ -107,6 +107,7 @@ const char* const PROPERTY_NAME_FONT_SIZE_SCALE = "fontSize const char* const PROPERTY_NAME_GRAB_HANDLE_COLOR = "grabHandleColor"; const char* const PROPERTY_NAME_ENABLE_GRAB_HANDLE_POPUP = "enableGrabHandlePopup"; const char* const PROPERTY_NAME_INPUT_METHOD_SETTINGS = "inputMethodSettings"; +const char* const PROPERTY_NAME_INPUT_FILTER = "inputFilter"; const Vector4 PLACEHOLDER_TEXT_COLOR( 0.8f, 0.8f, 0.8f, 0.8f ); const Dali::Vector4 LIGHT_BLUE( 0.75f, 0.96f, 1.f, 1.f ); // The text highlight color. @@ -135,6 +136,8 @@ const std::string DEFAULT_DEVICE_NAME("hwKeyboard"); static bool gAnchorClickedCallBackCalled; static bool gAnchorClickedCallBackNotCalled; static bool gTextChangedCallBackCalled; +static bool gInputFilteredAcceptedCallbackCalled; +static bool gInputFilteredRejectedCallbackCalled; static bool gInputStyleChangedCallbackCalled; static bool gMaxCharactersCallBackCalled; static Dali::Toolkit::TextEditor::InputStyle::Mask gInputStyleMask; @@ -187,6 +190,20 @@ static void TestMaxLengthReachedCallback( TextEditor control ) gMaxCharactersCallBackCalled = true; } +static void TestInputFilteredCallback(TextEditor control, Toolkit::InputFilter::Property::Type type) +{ + tet_infoline(" TestInputFilteredCallback"); + + if(type == Toolkit::InputFilter::Property::ACCEPTED) + { + gInputFilteredAcceptedCallbackCalled = true; + } + else if(type == Toolkit::InputFilter::Property::REJECTED) + { + gInputFilteredRejectedCallbackCalled = true; + } +} + // Generate a KeyEvent to send to Core. Integration::KeyEvent GenerateKey( const std::string& keyName, const std::string& logicalKey, @@ -515,6 +532,7 @@ int UtcDaliTextEditorGetPropertyP(void) DALI_TEST_CHECK( editor.GetPropertyIndex( PROPERTY_NAME_GRAB_HANDLE_COLOR ) == DevelTextEditor::Property::GRAB_HANDLE_COLOR ); DALI_TEST_CHECK( editor.GetPropertyIndex( PROPERTY_NAME_ENABLE_GRAB_HANDLE_POPUP ) == DevelTextEditor::Property::ENABLE_GRAB_HANDLE_POPUP ); DALI_TEST_CHECK( editor.GetPropertyIndex( PROPERTY_NAME_INPUT_METHOD_SETTINGS ) == DevelTextEditor::Property::INPUT_METHOD_SETTINGS ); + DALI_TEST_CHECK( editor.GetPropertyIndex( PROPERTY_NAME_INPUT_FILTER ) == DevelTextEditor::Property::INPUT_FILTER ); END_TEST; } @@ -969,6 +987,21 @@ int UtcDaliTextEditorSetPropertyP(void) DALI_TEST_CHECK( map[ "VARIATION" ].Get( variation ) ); DALI_TEST_EQUALS( inputVariation, variation, TEST_LOCATION ); + // Check the input filter property + Property::Map inputFilterMapSet; + Property::Map inputFilterMapGet; + inputFilterMapSet[InputFilter::Property::ACCEPTED] = "[\\w]"; + inputFilterMapSet[InputFilter::Property::REJECTED] = "[\\d]"; + + editor.SetProperty(DevelTextEditor::Property::INPUT_FILTER, inputFilterMapSet); + + inputFilterMapGet = editor.GetProperty(DevelTextEditor::Property::INPUT_FILTER); + DALI_TEST_EQUALS(inputFilterMapGet.Count(), inputFilterMapSet.Count(), TEST_LOCATION); + + // Clear + inputFilterMapSet.Clear(); + editor.SetProperty(DevelTextEditor::Property::INPUT_FILTER, inputFilterMapSet); + application.SendNotification(); application.Render(); @@ -3105,6 +3138,58 @@ int utcDaliTextEditorMaxCharactersReached(void) END_TEST; } +int utcDaliTextEditorInputFiltered(void) +{ + ToolkitTestApplication application; + tet_infoline(" utcDaliTextEditorInputFiltered"); + TextEditor editor = TextEditor::New(); + DALI_TEST_CHECK(editor); + + application.GetScene().Add(editor); + + Property::Map inputFilter; + + // Only digit is accepted. + inputFilter[InputFilter::Property::ACCEPTED] = "[\\d]"; + + // Set input filter to TextEditor. + editor.SetProperty(DevelTextEditor::Property::INPUT_FILTER, inputFilter); + + editor.SetKeyInputFocus(); + + // connect to the input filtered signal. + ConnectionTracker* testTracker = new ConnectionTracker(); + DevelTextEditor::InputFilteredSignal(editor).Connect(&TestInputFilteredCallback); + bool inputFilteredSignal = false; + editor.ConnectSignal(testTracker, "inputFiltered", CallbackFunctor(&inputFilteredSignal)); + + gInputFilteredAcceptedCallbackCalled = false; + + application.ProcessEvent(GenerateKey( "a", "", "a", KEY_A_CODE, 0, 0, Integration::KeyEvent::DOWN, "a", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE )); + + DALI_TEST_CHECK(gInputFilteredAcceptedCallbackCalled); + DALI_TEST_CHECK(inputFilteredSignal); + + // Word is rejected. + inputFilter[InputFilter::Property::ACCEPTED] = ""; + inputFilter[InputFilter::Property::REJECTED] = "[\\w]"; + + // Set input filter to TextEditor. + editor.SetProperty(DevelTextEditor::Property::INPUT_FILTER, inputFilter); + + editor.SetKeyInputFocus(); + + inputFilteredSignal = false; + gInputFilteredRejectedCallbackCalled = false; + + application.ProcessEvent(GenerateKey( "a", "", "a", KEY_A_CODE, 0, 0, Integration::KeyEvent::DOWN, "a", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + + DALI_TEST_CHECK(gInputFilteredAcceptedCallbackCalled); + DALI_TEST_CHECK(inputFilteredSignal); + + END_TEST; +} + int UtcDaliTextEditorSelectWholeText(void) { ToolkitTestApplication application; diff --git a/automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp b/automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp index 2044ea3..3f78b20 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp @@ -106,6 +106,7 @@ const char* const PROPERTY_NAME_ENABLE_GRAB_HANDLE_POPUP = "enableGr const char* const PROPERTY_NAME_BACKGROUND = "textBackground"; const char* const PROPERTY_NAME_FONT_SIZE_SCALE = "fontSizeScale"; const char* const PROPERTY_NAME_GRAB_HANDLE_COLOR = "grabHandleColor"; +const char* const PROPERTY_NAME_INPUT_FILTER = "inputFilter"; const Vector4 PLACEHOLDER_TEXT_COLOR( 0.8f, 0.8f, 0.8f, 0.8f ); const Dali::Vector4 LIGHT_BLUE( 0.75f, 0.96f, 1.f, 1.f ); // The text highlight color. @@ -125,6 +126,8 @@ static bool gAnchorClickedCallBackCalled; static bool gAnchorClickedCallBackNotCalled; static bool gTextChangedCallBackCalled; static bool gMaxCharactersCallBackCalled; +static bool gInputFilteredAcceptedCallbackCalled; +static bool gInputFilteredRejectedCallbackCalled; static bool gInputStyleChangedCallbackCalled; static Dali::Toolkit::TextField::InputStyle::Mask gInputStyleMask; @@ -234,6 +237,20 @@ static void TestMaxLengthReachedCallback( TextField control ) gMaxCharactersCallBackCalled = true; } +static void TestInputFilteredCallback(TextField control, Toolkit::InputFilter::Property::Type type) +{ + tet_infoline(" TestInputFilteredCallback"); + + if(type == Toolkit::InputFilter::Property::ACCEPTED) + { + gInputFilteredAcceptedCallbackCalled = true; + } + else if(type == Toolkit::InputFilter::Property::REJECTED) + { + gInputFilteredRejectedCallbackCalled = true; + } +} + static void TestInputStyleChangedCallback( TextField control, TextField::InputStyle::Mask mask ) { tet_infoline(" TestInputStyleChangedCallback"); @@ -526,6 +543,7 @@ int UtcDaliTextFieldGetPropertyP(void) DALI_TEST_CHECK( field.GetPropertyIndex( PROPERTY_NAME_ENABLE_GRAB_HANDLE_POPUP ) == DevelTextField::Property::ENABLE_GRAB_HANDLE_POPUP ); DALI_TEST_CHECK( field.GetPropertyIndex( PROPERTY_NAME_BACKGROUND ) == DevelTextField::Property::BACKGROUND ); DALI_TEST_CHECK( field.GetPropertyIndex( PROPERTY_NAME_GRAB_HANDLE_COLOR ) == DevelTextField::Property::GRAB_HANDLE_COLOR ); + DALI_TEST_CHECK( field.GetPropertyIndex( PROPERTY_NAME_INPUT_FILTER ) == DevelTextField::Property::INPUT_FILTER ); END_TEST; } @@ -1000,6 +1018,21 @@ int UtcDaliTextFieldSetPropertyP(void) field.SetProperty( DevelTextField::Property::GRAB_HANDLE_COLOR, Color::GREEN ); DALI_TEST_EQUALS( field.GetProperty( DevelTextField::Property::GRAB_HANDLE_COLOR ), Color::GREEN, TEST_LOCATION ); + // Check the input filter property + Property::Map inputFilterMapSet; + Property::Map inputFilterMapGet; + inputFilterMapSet[InputFilter::Property::ACCEPTED] = "[\\w]"; + inputFilterMapSet[InputFilter::Property::REJECTED] = "[\\d]"; + + field.SetProperty(DevelTextField::Property::INPUT_FILTER, inputFilterMapSet); + + inputFilterMapGet = field.GetProperty(DevelTextField::Property::INPUT_FILTER); + DALI_TEST_EQUALS(inputFilterMapGet.Count(), inputFilterMapSet.Count(), TEST_LOCATION); + + // Clear + inputFilterMapSet.Clear(); + field.SetProperty(DevelTextField::Property::INPUT_FILTER, inputFilterMapSet); + application.SendNotification(); application.Render(); @@ -1544,6 +1577,135 @@ int utcDaliTextFieldMaxCharactersReachedN(void) END_TEST; } +// Positive test for Input Filtered signal. +int utcDaliTextFieldInputFilteredP(void) +{ + ToolkitTestApplication application; + tet_infoline(" utcDaliTextFieldInputFilteredP"); + TextField field = TextField::New(); + DALI_TEST_CHECK(field); + + application.GetScene().Add(field); + + Property::Map inputFilter; + + // Only digit is accepted. + inputFilter[InputFilter::Property::ACCEPTED] = "[\\d]"; + + // Set input filter to TextField. + field.SetProperty(DevelTextField::Property::INPUT_FILTER, inputFilter); + + field.SetKeyInputFocus(); + + // connect to the input filtered signal. + ConnectionTracker* testTracker = new ConnectionTracker(); + DevelTextField::InputFilteredSignal(field).Connect(&TestInputFilteredCallback); + bool inputFilteredSignal = false; + field.ConnectSignal(testTracker, "inputFiltered", CallbackFunctor(&inputFilteredSignal)); + + gInputFilteredAcceptedCallbackCalled = false; + + application.ProcessEvent(GenerateKey( "a", "", "a", KEY_A_CODE, 0, 0, Integration::KeyEvent::DOWN, "a", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE )); + + DALI_TEST_CHECK(gInputFilteredAcceptedCallbackCalled); + DALI_TEST_CHECK(inputFilteredSignal); + + // Word is rejected. + inputFilter[InputFilter::Property::ACCEPTED] = ""; + inputFilter[InputFilter::Property::REJECTED] = "[\\w]"; + + // Set input filter to TextField. + field.SetProperty(DevelTextField::Property::INPUT_FILTER, inputFilter); + + field.SetKeyInputFocus(); + + inputFilteredSignal = false; + gInputFilteredRejectedCallbackCalled = false; + + application.ProcessEvent(GenerateKey( "a", "", "a", KEY_A_CODE, 0, 0, Integration::KeyEvent::DOWN, "a", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + + DALI_TEST_CHECK(gInputFilteredAcceptedCallbackCalled); + DALI_TEST_CHECK(inputFilteredSignal); + + END_TEST; +} + +// Negative test for Input Filtered signal. +int utcDaliTextFieldInputFilteredN(void) +{ + ToolkitTestApplication application; + tet_infoline(" utcDaliTextFieldInputFilteredP"); + TextField field = TextField::New(); + DALI_TEST_CHECK(field); + + application.GetScene().Add(field); + + Property::Map inputFilter; + + // Only word is accepted. + inputFilter[InputFilter::Property::ACCEPTED] = "[\\w]"; + + // Set input filter to TextField. + field.SetProperty(DevelTextField::Property::INPUT_FILTER, inputFilter); + + field.SetKeyInputFocus(); + + // connect to the input filtered signal. + ConnectionTracker* testTracker = new ConnectionTracker(); + DevelTextField::InputFilteredSignal(field).Connect(&TestInputFilteredCallback); + bool inputFilteredSignal = false; + field.ConnectSignal(testTracker, "inputFiltered", CallbackFunctor(&inputFilteredSignal)); + + gInputFilteredAcceptedCallbackCalled = false; + + // Key a, d should not be filtered. + application.ProcessEvent(GenerateKey("a", "", "a", KEY_A_CODE, 0, 0, Integration::KeyEvent::DOWN, "a", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + application.ProcessEvent(GenerateKey("a", "", "a", KEY_A_CODE, 0, 0, Integration::KeyEvent::UP, "a", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + application.ProcessEvent(GenerateKey("d", "", "d", KEY_D_CODE, 0, 0, Integration::KeyEvent::DOWN, "d", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + application.ProcessEvent(GenerateKey("d", "", "d", KEY_D_CODE, 0, 0, Integration::KeyEvent::UP, "d", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + + // Backspace, Delete should not be filtered. + application.ProcessEvent(GenerateKey( "", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + application.ProcessEvent(GenerateKey( "Delete", "", "Delete", Dali::DevelKey::DALI_KEY_DELETE, 0, 0, Integration::KeyEvent::DOWN, "Delete", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + + // Render and notify + application.SendNotification(); + application.Render(); + + DALI_TEST_CHECK(!gInputFilteredAcceptedCallbackCalled); + DALI_TEST_CHECK(!inputFilteredSignal); + + // Digit is rejected. + inputFilter[InputFilter::Property::ACCEPTED] = ""; + inputFilter[InputFilter::Property::REJECTED] = "[\\d]"; + + field.SetProperty(DevelTextField::Property::INPUT_FILTER, inputFilter); + + field.SetKeyInputFocus(); + + inputFilteredSignal = false; + gInputFilteredRejectedCallbackCalled = false; + + // Key a, d should not be filtered. + application.ProcessEvent(GenerateKey("a", "", "a", KEY_A_CODE, 0, 0, Integration::KeyEvent::DOWN, "a", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + application.ProcessEvent(GenerateKey("a", "", "a", KEY_A_CODE, 0, 0, Integration::KeyEvent::UP, "a", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + application.ProcessEvent(GenerateKey("d", "", "d", KEY_D_CODE, 0, 0, Integration::KeyEvent::DOWN, "d", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + application.ProcessEvent(GenerateKey("d", "", "d", KEY_D_CODE, 0, 0, Integration::KeyEvent::UP, "d", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + + // Backspace, Delete should not be filtered. + application.ProcessEvent(GenerateKey( "", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + application.ProcessEvent(GenerateKey( "Delete", "", "Delete", Dali::DevelKey::DALI_KEY_DELETE, 0, 0, Integration::KeyEvent::DOWN, "Delete", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + + // Render and notify + application.SendNotification(); + application.Render(); + + DALI_TEST_CHECK(!gInputFilteredAcceptedCallbackCalled); + DALI_TEST_CHECK(!inputFilteredSignal); + + END_TEST; +} + int utcDaliTextFieldInputStyleChanged01(void) { // The text-field emits signals when the input style changes. These changes of style are diff --git a/dali-toolkit/dali-toolkit.h b/dali-toolkit/dali-toolkit.h index 1cf4adc..839e96d 100644 --- a/dali-toolkit/dali-toolkit.h +++ b/dali-toolkit/dali-toolkit.h @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include diff --git a/dali-toolkit/devel-api/controls/text-controls/text-editor-devel.cpp b/dali-toolkit/devel-api/controls/text-controls/text-editor-devel.cpp index 37678d8..a685aee 100644 --- a/dali-toolkit/devel-api/controls/text-controls/text-editor-devel.cpp +++ b/dali-toolkit/devel-api/controls/text-controls/text-editor-devel.cpp @@ -40,6 +40,11 @@ AnchorClickedSignalType& AnchorClickedSignal(TextEditor textEditor) return GetImpl(textEditor).AnchorClickedSignal(); } +InputFilteredSignalType& InputFilteredSignal(TextEditor textEditor) +{ + return GetImpl(textEditor).InputFilteredSignal(); +} + void SelectWholeText(TextEditor textEditor) { GetImpl(textEditor).SelectWholeText(); diff --git a/dali-toolkit/devel-api/controls/text-controls/text-editor-devel.h b/dali-toolkit/devel-api/controls/text-controls/text-editor-devel.h index 665af7a..baa872f 100644 --- a/dali-toolkit/devel-api/controls/text-controls/text-editor-devel.h +++ b/dali-toolkit/devel-api/controls/text-controls/text-editor-devel.h @@ -21,6 +21,7 @@ #include // INTERNAL INCLUDES +#include #include namespace Dali @@ -220,6 +221,38 @@ enum Type * @endcode */ INPUT_METHOD_SETTINGS, + + /** + * @brief The input filter + * @details Name "inputFilter", type Property::MAP. + * + * The inputFilter map contains the following keys: + * + * | %Property Name | Type | Required | Description | + * |----------------------|----------|----------|---------------------------------------------------------------------------------------------------------------------| + * | accepted | STRING | No | A regular expression in the set of characters to be accepted by the inputFilter (the default value is empty string) | + * | rejected | STRING | No | A regular expression in the set of characters to be rejected by the inputFilter (the default value is empty string) | + * + * @note Optional. + * The character set must follow the regular expression rules. + * Behaviour can not be guaranteed for incorrect grammars. + * Refer the link below for detailed rules. + * The functions in std::regex library use the ECMAScript grammar: + * http://cplusplus.com/reference/regex/ECMAScript/ + * + * You can use enums instead of "accepted" and "rejected" strings. + * @see Dali::Toolkit::InputFilter::Property::Type + * + * Example Usage: + * @code + * Property::Map filter; + * filter[InputFilter::Property::ACCEPTED] = "[\\d]"; // accept whole digits + * filter[InputFilter::Property::REJECTED] = "[0-5]"; // reject 0, 1, 2, 3, 4, 5 + * + * editor.SetProperty(DevelTextEditor::Property::INPUT_FILTER, filter); // acceptable inputs are 6, 7, 8, 9 + * @endcode + */ + INPUT_FILTER, }; } // namespace Property @@ -271,6 +304,37 @@ using AnchorClickedSignalType = Signal; DALI_TOOLKIT_API AnchorClickedSignalType& AnchorClickedSignal(TextEditor textEditor); /** + * @brief Input filtered signal type. + */ +using InputFilteredSignalType = Signal; + +/** + * @brief This signal is emitted when the character to be inserted is filtered by the input filter. + * + * A callback of the following type may be connected: + * @code + * void YourCallbackName(TextEditor textEditor, Toolkit::InputFilter::Property::Type type); + * + * DevelTextEditor::InputFilteredSignal(textEditor).Connect(this, &OnInputFiltered); + * + * void OnInputFiltered(TextEditor textEditor, InputFilter::Property::Type type) + * { + * if (type == InputFilter::Property::ACCEPTED) + * { + * // If the input has been filtered with an accepted filter, the type is ACCEPTED. + * } + * else if (type == InputFilter::Property::REJECTED) + * { + * // If the input has been filtered with an rejected filter, the type is REJECTED. + * } + * } + * @endcode + * @param[in] textEditor The instance of TextEditor. + * @return The signal to connect to. + */ +DALI_TOOLKIT_API InputFilteredSignalType& InputFilteredSignal(TextEditor textEditor); + +/** * @brief Select the whole text of TextEditor. * * @param[in] textEditor The instance of TextEditor. diff --git a/dali-toolkit/devel-api/controls/text-controls/text-field-devel.cpp b/dali-toolkit/devel-api/controls/text-controls/text-field-devel.cpp index fdfac30..727b42a 100644 --- a/dali-toolkit/devel-api/controls/text-controls/text-field-devel.cpp +++ b/dali-toolkit/devel-api/controls/text-controls/text-field-devel.cpp @@ -35,6 +35,11 @@ AnchorClickedSignalType& AnchorClickedSignal(TextField textField) return GetImpl(textField).AnchorClickedSignal(); } +InputFilteredSignalType& InputFilteredSignal(TextField textField) +{ + return GetImpl(textField).InputFilteredSignal(); +} + void SelectWholeText(TextField textField) { GetImpl(textField).SelectWholeText(); diff --git a/dali-toolkit/devel-api/controls/text-controls/text-field-devel.h b/dali-toolkit/devel-api/controls/text-controls/text-field-devel.h index 3367bec..e1c18f0 100644 --- a/dali-toolkit/devel-api/controls/text-controls/text-field-devel.h +++ b/dali-toolkit/devel-api/controls/text-controls/text-field-devel.h @@ -21,6 +21,7 @@ #include // INTERNAL INCLUDES +#include #include namespace Dali @@ -172,6 +173,38 @@ enum * @details Name "grabHandleColor", type Property::VECTOR4. */ GRAB_HANDLE_COLOR, + + /** + * @brief The input filter + * @details Name "inputFilter", type Property::MAP. + * + * The inputFilter map contains the following keys: + * + * | %Property Name | Type | Required | Description | + * |----------------------|----------|----------|---------------------------------------------------------------------------------------------------------------------| + * | accepted | STRING | No | A regular expression in the set of characters to be accepted by the inputFilter (the default value is empty string) | + * | rejected | STRING | No | A regular expression in the set of characters to be rejected by the inputFilter (the default value is empty string) | + * + * @note Optional. + * The character set must follow the regular expression rules. + * Behaviour can not be guaranteed for incorrect grammars. + * Refer the link below for detailed rules. + * The functions in std::regex library use the ECMAScript grammar: + * http://cplusplus.com/reference/regex/ECMAScript/ + * + * You can use enums instead of "accepted" and "rejected" strings. + * @see Dali::Toolkit::InputFilter::Property::Type + * + * Example Usage: + * @code + * Property::Map filter; + * filter[InputFilter::Property::ACCEPTED] = "[\\d]"; // accept whole digits + * filter[InputFilter::Property::REJECTED] = "[0-5]"; // reject 0, 1, 2, 3, 4, 5 + * + * field.SetProperty(DevelTextField::Property::INPUT_FILTER, filter); // acceptable inputs are 6, 7, 8, 9 + * @endcode + */ + INPUT_FILTER, }; } // namespace Property @@ -206,6 +239,37 @@ using AnchorClickedSignalType = Signal; DALI_TOOLKIT_API AnchorClickedSignalType& AnchorClickedSignal(TextField textField); /** + * @brief Input filtered signal type. + */ +using InputFilteredSignalType = Signal; + +/** + * @brief This signal is emitted when the character to be inserted is filtered by the input filter. + * + * A callback of the following type may be connected: + * @code + * void YourCallbackName(TextField textField, Toolkit::InputFilter::Property::Type type); + * + * DevelTextField::InputFilteredSignal(textField).Connect(this, &OnInputFiltered); + * + * void OnInputFiltered(TextField textField, InputFilter::Property::Type type) + * { + * if (type == InputFilter::Property::ACCEPTED) + * { + * // If the input has been filtered with an accepted filter, the type is ACCEPTED. + * } + * else if (type == InputFilter::Property::REJECTED) + * { + * // If the input has been filtered with an rejected filter, the type is REJECTED. + * } + * } + * @endcode + * @param[in] textField The instance of TextField. + * @return The signal to connect to. + */ +DALI_TOOLKIT_API InputFilteredSignalType& InputFilteredSignal(TextField textField); + +/** * @brief Select the whole text of TextField. * * @param[in] textField The instance of TextField. diff --git a/dali-toolkit/internal/controls/text-controls/text-editor-impl.cpp b/dali-toolkit/internal/controls/text-controls/text-editor-impl.cpp index db7096a..56f43ff 100644 --- a/dali-toolkit/internal/controls/text-controls/text-editor-impl.cpp +++ b/dali-toolkit/internal/controls/text-controls/text-editor-impl.cpp @@ -149,11 +149,14 @@ DALI_DEVEL_PROPERTY_REGISTRATION(Toolkit, TextEditor, "primaryCursorPo DALI_DEVEL_PROPERTY_REGISTRATION(Toolkit, TextEditor, "grabHandleColor", VECTOR4, GRAB_HANDLE_COLOR ) DALI_DEVEL_PROPERTY_REGISTRATION(Toolkit, TextEditor, "enableGrabHandlePopup", BOOLEAN, ENABLE_GRAB_HANDLE_POPUP ) DALI_DEVEL_PROPERTY_REGISTRATION(Toolkit, TextEditor, "inputMethodSettings", MAP, INPUT_METHOD_SETTINGS ) +DALI_DEVEL_PROPERTY_REGISTRATION(Toolkit, TextEditor, "inputFilter", MAP, INPUT_FILTER ) DALI_SIGNAL_REGISTRATION(Toolkit, TextEditor, "textChanged", SIGNAL_TEXT_CHANGED ) DALI_SIGNAL_REGISTRATION(Toolkit, TextEditor, "inputStyleChanged", SIGNAL_INPUT_STYLE_CHANGED) DALI_SIGNAL_REGISTRATION(Toolkit, TextEditor, "maxLengthReached", SIGNAL_MAX_LENGTH_REACHED ) DALI_SIGNAL_REGISTRATION(Toolkit, TextEditor, "anchorClicked", SIGNAL_ANCHOR_CLICKED ) +DALI_SIGNAL_REGISTRATION(Toolkit, TextEditor, "inputFiltered", SIGNAL_INPUT_FILTERED ) + DALI_TYPE_REGISTRATION_END() // clang-format on @@ -801,6 +804,15 @@ void TextEditor::SetProperty(BaseObject* object, Property::Index index, const Pr } break; } + case Toolkit::DevelTextEditor::Property::INPUT_FILTER: + { + const Property::Map* map = value.GetMap(); + if(map) + { + impl.mController->SetInputFilterOption(*map); + } + break; + } } // switch } // texteditor } @@ -1174,6 +1186,13 @@ Property::Value TextEditor::GetProperty(BaseObject* object, Property::Index inde value = map; break; } + case Toolkit::DevelTextEditor::Property::INPUT_FILTER: + { + Property::Map map; + impl.mController->GetInputFilterOption(map); + value = map; + break; + } } //switch } @@ -1248,6 +1267,11 @@ DevelTextEditor::AnchorClickedSignalType& TextEditor::AnchorClickedSignal() return mAnchorClickedSignal; } +DevelTextEditor::InputFilteredSignalType& TextEditor::InputFilteredSignal() +{ + return mInputFilteredSignal; +} + Text::ControllerPtr TextEditor::getController() { return mController; @@ -1284,6 +1308,14 @@ bool TextEditor::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface* editorImpl.AnchorClickedSignal().Connect(tracker, functor); } } + else if(0 == strcmp(signalName.c_str(), SIGNAL_INPUT_FILTERED)) + { + if(editor) + { + Internal::TextEditor& editorImpl(GetImpl(editor)); + editorImpl.InputFilteredSignal().Connect(tracker, functor); + } + } else { // signalName does not match any signal @@ -1874,6 +1906,12 @@ void TextEditor::AnchorClicked(const std::string& href) mAnchorClickedSignal.Emit(handle, href.c_str(), href.length()); } +void TextEditor::InputFiltered(Toolkit::InputFilter::Property::Type type) +{ + Dali::Toolkit::TextEditor handle(GetOwner()); + mInputFilteredSignal.Emit(handle, type); +} + void TextEditor::AddDecoration(Actor& actor, bool needsClipping) { if(actor) diff --git a/dali-toolkit/internal/controls/text-controls/text-editor-impl.h b/dali-toolkit/internal/controls/text-controls/text-editor-impl.h index 0286da2..424cda5 100644 --- a/dali-toolkit/internal/controls/text-controls/text-editor-impl.h +++ b/dali-toolkit/internal/controls/text-controls/text-editor-impl.h @@ -93,6 +93,11 @@ public: DevelTextEditor::AnchorClickedSignalType& AnchorClickedSignal(); /** + * @copydoc Dali::Toollkit::TextEditor::InputFilteredSignal() + */ + DevelTextEditor::InputFilteredSignalType& InputFilteredSignal(); + + /** * Connects a callback function with the object's signals. * @param[in] object The object providing the signal. * @param[in] tracker Used to disconnect the signal. @@ -228,6 +233,11 @@ private: // From Control */ void AddDecoration(Actor& actor, bool needsClipping) override; + /** + * @copydoc Text::EditableControlInterface::InputFiltered() + */ + void InputFiltered(Toolkit::InputFilter::Property::Type type) override; + // From SelectableControlInterface public: /** @@ -398,6 +408,7 @@ private: // Data Toolkit::TextEditor::ScrollStateChangedSignalType mScrollStateChangedSignal; Toolkit::DevelTextEditor::MaxLengthReachedSignalType mMaxLengthReachedSignal; Toolkit::DevelTextEditor::AnchorClickedSignalType mAnchorClickedSignal; + Toolkit::DevelTextEditor::InputFilteredSignalType mInputFilteredSignal; InputMethodContext mInputMethodContext; Text::ControllerPtr mController; diff --git a/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp b/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp index 4e5cd9c..dbf5624 100644 --- a/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp +++ b/dali-toolkit/internal/controls/text-controls/text-field-impl.cpp @@ -138,11 +138,13 @@ DALI_DEVEL_PROPERTY_REGISTRATION(Toolkit, TextField, "enableEditing", DALI_DEVEL_PROPERTY_REGISTRATION(Toolkit, TextField, "fontSizeScale", FLOAT, FONT_SIZE_SCALE ) DALI_DEVEL_PROPERTY_REGISTRATION(Toolkit, TextField, "primaryCursorPosition", INTEGER, PRIMARY_CURSOR_POSITION ) DALI_DEVEL_PROPERTY_REGISTRATION(Toolkit, TextField, "grabHandleColor", VECTOR4, GRAB_HANDLE_COLOR ) +DALI_DEVEL_PROPERTY_REGISTRATION(Toolkit, TextField, "inputFilter", MAP, INPUT_FILTER ) DALI_SIGNAL_REGISTRATION(Toolkit, TextField, "textChanged", SIGNAL_TEXT_CHANGED ) DALI_SIGNAL_REGISTRATION(Toolkit, TextField, "maxLengthReached", SIGNAL_MAX_LENGTH_REACHED ) DALI_SIGNAL_REGISTRATION(Toolkit, TextField, "inputStyleChanged", SIGNAL_INPUT_STYLE_CHANGED) DALI_SIGNAL_REGISTRATION(Toolkit, TextField, "anchorClicked", SIGNAL_ANCHOR_CLICKED ) +DALI_SIGNAL_REGISTRATION(Toolkit, TextField, "inputFiltered", SIGNAL_INPUT_FILTERED ) DALI_TYPE_REGISTRATION_END() // clang-format on @@ -765,6 +767,15 @@ void TextField::SetProperty(BaseObject* object, Property::Index index, const Pro impl.RequestTextRelayout(); break; } + case Toolkit::DevelTextField::Property::INPUT_FILTER: + { + const Property::Map* map = value.GetMap(); + if(map) + { + impl.mController->SetInputFilterOption(*map); + } + break; + } } // switch } // textfield } @@ -1120,6 +1131,13 @@ Property::Value TextField::GetProperty(BaseObject* object, Property::Index index value = impl.mDecorator->GetHandleColor(); break; } + case Toolkit::DevelTextField::Property::INPUT_FILTER: + { + Property::Map map; + impl.mController->GetInputFilterOption(map); + value = map; + break; + } } //switch } @@ -1204,6 +1222,14 @@ bool TextField::DoConnectSignal(BaseObject* object, ConnectionTrackerInterface* fieldImpl.AnchorClickedSignal().Connect(tracker, functor); } } + else if(0 == strcmp(signalName.c_str(), SIGNAL_INPUT_FILTERED)) + { + if(field) + { + Internal::TextField& fieldImpl(GetImpl(field)); + fieldImpl.InputFilteredSignal().Connect(tracker, functor); + } + } else { // signalName does not match any signal @@ -1233,6 +1259,11 @@ DevelTextField::AnchorClickedSignalType& TextField::AnchorClickedSignal() return mAnchorClickedSignal; } +DevelTextField::InputFilteredSignalType& TextField::InputFilteredSignal() +{ + return mInputFilteredSignal; +} + void TextField::OnInitialize() { Actor self = Self(); @@ -1804,6 +1835,12 @@ void TextField::AnchorClicked(const std::string& href) mAnchorClickedSignal.Emit(handle, href.c_str(), href.length()); } +void TextField::InputFiltered(Toolkit::InputFilter::Property::Type type) +{ + Dali::Toolkit::TextField handle(GetOwner()); + mInputFilteredSignal.Emit(handle, type); +} + void TextField::AddDecoration(Actor& actor, bool needsClipping) { if(actor) diff --git a/dali-toolkit/internal/controls/text-controls/text-field-impl.h b/dali-toolkit/internal/controls/text-controls/text-field-impl.h index c76f05a..d1a2671 100644 --- a/dali-toolkit/internal/controls/text-controls/text-field-impl.h +++ b/dali-toolkit/internal/controls/text-controls/text-field-impl.h @@ -109,6 +109,11 @@ public: */ DevelTextField::AnchorClickedSignalType& AnchorClickedSignal(); + /** + * @copydoc TextField::InputFilteredSignal() + */ + DevelTextField::InputFilteredSignalType& InputFilteredSignal(); + Text::ControllerPtr getController(); private: // From Control @@ -221,6 +226,11 @@ private: // From Control */ void AddDecoration(Actor& actor, bool needsClipping) override; + /** + * @copydoc Text::EditableControlInterface::InputFiltered() + */ + void InputFiltered(Toolkit::InputFilter::Property::Type type) override; + // From SelectableControlInterface public: /** @@ -359,6 +369,7 @@ private: // Data Toolkit::TextField::MaxLengthReachedSignalType mMaxLengthReachedSignal; Toolkit::TextField::InputStyleChangedSignalType mInputStyleChangedSignal; Toolkit::DevelTextField::AnchorClickedSignalType mAnchorClickedSignal; + Toolkit::DevelTextField::InputFilteredSignalType mInputFilteredSignal; InputMethodContext mInputMethodContext; Text::ControllerPtr mController; diff --git a/dali-toolkit/internal/file.list b/dali-toolkit/internal/file.list index 41d9978..9cab1a6 100644 --- a/dali-toolkit/internal/file.list +++ b/dali-toolkit/internal/file.list @@ -141,6 +141,7 @@ SET( toolkit_src_files ${toolkit_src_dir}/text/markup-processor-helper-functions.cpp ${toolkit_src_dir}/text/multi-language-support.cpp ${toolkit_src_dir}/text/hidden-text.cpp + ${toolkit_src_dir}/text/input-filter.cpp ${toolkit_src_dir}/text/property-string-parser.cpp ${toolkit_src_dir}/text/segmentation.cpp ${toolkit_src_dir}/text/shaper.cpp diff --git a/dali-toolkit/internal/text/input-filter.cpp b/dali-toolkit/internal/text/input-filter.cpp new file mode 100644 index 0000000..eafcfe7 --- /dev/null +++ b/dali-toolkit/internal/text/input-filter.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// CLASS HEADER +#include +#include + +// INTERNAL INCLUDES + +using namespace Dali::Toolkit; + +namespace Dali +{ +namespace Toolkit +{ +namespace Text +{ +const char* const PROPERTY_ACCEPTED = "accepted"; +const char* const PROPERTY_REJECTED = "rejected"; + +InputFilter::InputFilter() +: mAccepted(""), + mRejected("") +{ +} + +void InputFilter::SetProperties(const Property::Map& map) +{ + const Property::Map::SizeType count = map.Count(); + + for(Property::Map::SizeType position = 0; position < count; ++position) + { + KeyValuePair keyValue = map.GetKeyValue(position); + Property::Key& key = keyValue.first; + Property::Value& value = keyValue.second; + + if(key == Toolkit::InputFilter::Property::ACCEPTED || key == PROPERTY_ACCEPTED) + { + value.Get(mAccepted); + } + else if(key == Toolkit::InputFilter::Property::REJECTED || key == PROPERTY_REJECTED) + { + value.Get(mRejected); + } + } +} + +void InputFilter::GetProperties(Property::Map& map) +{ + map[Toolkit::InputFilter::Property::ACCEPTED] = mAccepted.c_str(); + map[Toolkit::InputFilter::Property::REJECTED] = mRejected.c_str(); +} + +bool InputFilter::Contains(Toolkit::InputFilter::Property::Type type, std::string source) +{ + bool match = false; + std::regex pattern; + + if(type == Toolkit::InputFilter::Property::ACCEPTED) + { + if(mAccepted.empty()) + { + return true; + } + pattern = mAccepted; + } + else if(type == Toolkit::InputFilter::Property::REJECTED) + { + if(mRejected.empty()) + { + return false; + } + pattern = mRejected; + } + + match = std::regex_match(source, pattern); + + return match; +} + +} // namespace Text + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/internal/text/input-filter.h b/dali-toolkit/internal/text/input-filter.h new file mode 100644 index 0000000..6de0132 --- /dev/null +++ b/dali-toolkit/internal/text/input-filter.h @@ -0,0 +1,77 @@ +#ifndef DALI_INPUT_FILTER_H +#define DALI_INPUT_FILTER_H + +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// EXTERNAL INCLUDES +#include + +// INTERNAL INCLUDES +#include +#include + +namespace Dali +{ +namespace Toolkit +{ +namespace Text +{ +/** + * Class to handle the input text filtering + */ +class InputFilter : public ConnectionTracker +{ +public: + /** + * @brief Constructor + */ + InputFilter(); + +public: // Intended for internal use + /** + * @brief Used to set options of input filter. + * @param[in] map The property map describing the option. + */ + void SetProperties(const Property::Map& map); + + /** + * @brief Retrieve property map of input filter options. + * @param[out] map The input filter option. + */ + void GetProperties(Property::Map& map); + + /** + * @brief Check if the source is contained in regex. + * @param[in] type ACCEPTED or REJECTED + * @param[in] source The original text. + * @return @e true if the source is contained in regex, otherwise returns @e false. + */ + bool Contains(Toolkit::InputFilter::Property::Type type, std::string source); + +private: + std::string mAccepted; + std::string mRejected; +}; + +} // namespace Text + +} // namespace Toolkit + +} // namespace Dali + +#endif // DALI_INPUT_FILTER_H diff --git a/dali-toolkit/internal/text/text-controller-event-handler.cpp b/dali-toolkit/internal/text/text-controller-event-handler.cpp index c17c820..15805c8 100644 --- a/dali-toolkit/internal/text/text-controller-event-handler.cpp +++ b/dali-toolkit/internal/text/text-controller-event-handler.cpp @@ -247,12 +247,36 @@ bool Controller::EventHandler::KeyEvent(Controller& controller, const Dali::KeyE DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::KeyEvent %p keyString %s\n", &controller, keyString.c_str()); if(!controller.IsEditable()) return false; - if(!keyString.empty()) + std::string refinedKey = keyString; + if(controller.mImpl->mInputFilter != NULL && !refinedKey.empty()) + { + bool accepted = false; + bool rejected = false; + accepted = controller.mImpl->mInputFilter->Contains(Toolkit::InputFilter::Property::ACCEPTED, keyString); + rejected = controller.mImpl->mInputFilter->Contains(Toolkit::InputFilter::Property::REJECTED, keyString); + + if(!accepted) + { + // The filtered key is set to empty. + refinedKey = ""; + // Signal emits when the character to be inserted is filtered by the accepted filter. + controller.mImpl->mEditableControlInterface->InputFiltered(Toolkit::InputFilter::Property::ACCEPTED); + } + if(rejected) + { + // The filtered key is set to empty. + refinedKey = ""; + // Signal emits when the character to be inserted is filtered by the rejected filter. + controller.mImpl->mEditableControlInterface->InputFiltered(Toolkit::InputFilter::Property::REJECTED); + } + } + + if(!refinedKey.empty()) { // InputMethodContext is no longer handling key-events controller.mImpl->ClearPreEditFlag(); - controller.InsertText(keyString, COMMIT); + controller.InsertText(refinedKey, COMMIT); textChanged = true; diff --git a/dali-toolkit/internal/text/text-controller-impl.h b/dali-toolkit/internal/text/text-controller-impl.h index 9f637d3..55184ef 100644 --- a/dali-toolkit/internal/text/text-controller-impl.h +++ b/dali-toolkit/internal/text/text-controller-impl.h @@ -338,6 +338,7 @@ struct Controller::Impl mOperationsPending(NO_OPERATION), mMaximumNumberOfCharacters(50u), mHiddenInput(NULL), + mInputFilter(nullptr), mRecalculateNaturalSize(true), mMarkupProcessorEnabled(false), mClipboardHideEnabled(true), @@ -386,7 +387,6 @@ struct Controller::Impl ~Impl() { delete mHiddenInput; - delete mFontDefaults; delete mUnderlineDefaults; delete mShadowDefaults; @@ -793,29 +793,30 @@ private: void CopyUnderlinedFromLogicalToVisualModels(bool shouldClearPreUnderlineRuns); public: - ControlInterface* mControlInterface; ///< Reference to the text controller. - EditableControlInterface* mEditableControlInterface; ///< Reference to the editable text controller. - SelectableControlInterface* mSelectableControlInterface; ///< Reference to the selectable text controller. - AnchorControlInterface* mAnchorControlInterface; ///< Reference to the anchor controller. - ModelPtr mModel; ///< Pointer to the text's model. - FontDefaults* mFontDefaults; ///< Avoid allocating this when the user does not specify a font. - UnderlineDefaults* mUnderlineDefaults; ///< Avoid allocating this when the user does not specify underline parameters. - ShadowDefaults* mShadowDefaults; ///< Avoid allocating this when the user does not specify shadow parameters. - EmbossDefaults* mEmbossDefaults; ///< Avoid allocating this when the user does not specify emboss parameters. - OutlineDefaults* mOutlineDefaults; ///< Avoid allocating this when the user does not specify outline parameters. - EventData* mEventData; ///< Avoid allocating everything for text input until EnableTextInput(). - TextAbstraction::FontClient mFontClient; ///< Handle to the font client. - Clipboard mClipboard; ///< Handle to the system clipboard - View mView; ///< The view interface to the rendering back-end. - MetricsPtr mMetrics; ///< A wrapper around FontClient used to get metrics & potentially down-scaled Emoji metrics. - Layout::Engine mLayoutEngine; ///< The layout engine. - Vector mModifyEvents; ///< Temporary stores the text set until the next relayout. - Vector4 mTextColor; ///< The regular text color - TextUpdateInfo mTextUpdateInfo; ///< Info of the characters updated. - OperationsMask mOperationsPending; ///< Operations pending to be done to layout the text. - Length mMaximumNumberOfCharacters; ///< Maximum number of characters that can be inserted. - HiddenText* mHiddenInput; ///< Avoid allocating this when the user does not specify hidden input mode. - Vector2 mTextFitContentSize; ///< Size of Text fit content + ControlInterface* mControlInterface; ///< Reference to the text controller. + EditableControlInterface* mEditableControlInterface; ///< Reference to the editable text controller. + SelectableControlInterface* mSelectableControlInterface; ///< Reference to the selectable text controller. + AnchorControlInterface* mAnchorControlInterface; ///< Reference to the anchor controller. + ModelPtr mModel; ///< Pointer to the text's model. + FontDefaults* mFontDefaults; ///< Avoid allocating this when the user does not specify a font. + UnderlineDefaults* mUnderlineDefaults; ///< Avoid allocating this when the user does not specify underline parameters. + ShadowDefaults* mShadowDefaults; ///< Avoid allocating this when the user does not specify shadow parameters. + EmbossDefaults* mEmbossDefaults; ///< Avoid allocating this when the user does not specify emboss parameters. + OutlineDefaults* mOutlineDefaults; ///< Avoid allocating this when the user does not specify outline parameters. + EventData* mEventData; ///< Avoid allocating everything for text input until EnableTextInput(). + TextAbstraction::FontClient mFontClient; ///< Handle to the font client. + Clipboard mClipboard; ///< Handle to the system clipboard + View mView; ///< The view interface to the rendering back-end. + MetricsPtr mMetrics; ///< A wrapper around FontClient used to get metrics & potentially down-scaled Emoji metrics. + Layout::Engine mLayoutEngine; ///< The layout engine. + Vector mModifyEvents; ///< Temporary stores the text set until the next relayout. + Vector4 mTextColor; ///< The regular text color + TextUpdateInfo mTextUpdateInfo; ///< Info of the characters updated. + OperationsMask mOperationsPending; ///< Operations pending to be done to layout the text. + Length mMaximumNumberOfCharacters; ///< Maximum number of characters that can be inserted. + HiddenText* mHiddenInput; ///< Avoid allocating this when the user does not specify hidden input mode. + std::unique_ptr mInputFilter; ///< Avoid allocating this when the user does not specify input filter mode. + Vector2 mTextFitContentSize; ///< Size of Text fit content bool mRecalculateNaturalSize : 1; ///< Whether the natural size needs to be recalculated. bool mMarkupProcessorEnabled : 1; ///< Whether the mark-up procesor is enabled. diff --git a/dali-toolkit/internal/text/text-controller.cpp b/dali-toolkit/internal/text/text-controller.cpp index a863317..b2b6550 100644 --- a/dali-toolkit/internal/text/text-controller.cpp +++ b/dali-toolkit/internal/text/text-controller.cpp @@ -1618,6 +1618,23 @@ void Controller::GetHiddenInputOption(Property::Map& options) } } +void Controller::SetInputFilterOption(const Property::Map& options) +{ + if(!mImpl->mInputFilter) + { + mImpl->mInputFilter = std::unique_ptr(new InputFilter()); + } + mImpl->mInputFilter->SetProperties(options); +} + +void Controller::GetInputFilterOption(Property::Map& options) +{ + if(NULL != mImpl->mInputFilter) + { + mImpl->mInputFilter->GetProperties(options); + } +} + void Controller::SetPlaceholderProperty(const Property::Map& map) { PlaceholderHandler::SetPlaceholderProperty(*this, map); diff --git a/dali-toolkit/internal/text/text-controller.h b/dali-toolkit/internal/text/text-controller.h index aaa3a66..0802a77 100644 --- a/dali-toolkit/internal/text/text-controller.h +++ b/dali-toolkit/internal/text/text-controller.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -1386,6 +1387,16 @@ public: // Queries & retrieves. void GetHiddenInputOption(Property::Map& options); /** + * @brief Used to set the input filter option + */ + void SetInputFilterOption(const Property::Map& options); + + /** + * @brief Used to get the input filter option + */ + void GetInputFilterOption(Property::Map& options); + + /** * @brief Sets the Placeholder Properties. * * @param[in] map The placeholder property map diff --git a/dali-toolkit/internal/text/text-editable-control-interface.h b/dali-toolkit/internal/text/text-editable-control-interface.h index 4bd0de0..af2abd0 100644 --- a/dali-toolkit/internal/text/text-editable-control-interface.h +++ b/dali-toolkit/internal/text/text-editable-control-interface.h @@ -20,6 +20,7 @@ // INTERNAL INCLUDES #include +#include namespace Dali { @@ -77,6 +78,13 @@ public: virtual void InputStyleChanged(InputStyle::Mask inputStyleMask) = 0; /** + * @brief Called when the character to be inserted is filtered by the input filter. + * + * @param[in] type The filter type is ACCEPTED or REJECTED. + */ + virtual void InputFiltered(Toolkit::InputFilter::Property::Type type) = 0; + + /** * @brief Add a decoration. * * @param[in] decoration The actor displaying a decoration. diff --git a/dali-toolkit/public-api/controls/text-controls/input-filter-properties.h b/dali-toolkit/public-api/controls/text-controls/input-filter-properties.h new file mode 100644 index 0000000..6f9b07f --- /dev/null +++ b/dali-toolkit/public-api/controls/text-controls/input-filter-properties.h @@ -0,0 +1,99 @@ +#ifndef DALI_INPUT_FILTER_PROPERTIES_H +#define DALI_INPUT_FILTER_PROPERTIES_H + +/* + * Copyright (c) 2021 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +namespace Dali +{ +namespace Toolkit +{ +/** + * @addtogroup dali_toolkit_controls_text_controls + * @{ + */ + +namespace InputFilter +{ +/** + * @brief InputFilter Property. + * @SINCE_2_0.33 + */ +namespace Property +{ +/** + * @brief Enumeration for the type of InputFilter. + * + * An enum that determines the input filter type of the InputFilter map. + * Users can set the ACCEPTED or REJECTED character set, or both. + * If both are used, REJECTED has higher priority. + * The character set must follow the regular expression rules. + * Behaviour can not be guaranteed for incorrect grammars. + * + * Useful Meta characters: + * + * | %Meta characters | Description + * |-------------------|------------------------------------------------------------------------------------------------------------| + * | \\w | Matches an alphanumeric character, including "_"; same as [A-Za-z0-9_]. | + * | \\W | Matches a non-alphanumeric character, excluding "_"; same as [^A-Za-z0-9_]. | + * | \\s | Matches a whitespace character, which in ASCII are tab, line feed, form feed, carriage return, and space. | + * | \\S | Matches anything but a whitespace. | + * | \\d | Matches a digit; same as [0-9]. | + * | \\D | Matches a non-digit; same as [^0-9]. | + * + * Example Usage: + * @code + * Property::Map filter; + * filter[InputFilter::Property::ACCEPTED] = "[\\d]"; // accept whole digits + * filter[InputFilter::Property::REJECTED] = "[0-5]"; // reject 0, 1, 2, 3, 4, 5 + * + * field.SetProperty(DevelTextField::Property::INPUT_FILTER, filter); // acceptable inputs are 6, 7, 8, 9 + * @endcode + * @SINCE_2_0.33 + */ +enum Type +{ + /** + * @brief The set of characters to be accepted. + * @details Name "accepted", type Property::STRING. + * @SINCE_2_0.33 + * @note Available on regex string. + */ + ACCEPTED, + + /** + * @brief The set of characters to be rejected. + * @details Name "rejected", type Property::STRING. + * @SINCE_2_0.33 + * @note Available on regex string. + */ + REJECTED +}; + +} // namespace Property + +} // namespace InputFilter + +/** + * @} + */ + +} // namespace Toolkit + +} // namespace Dali + +#endif // DALI_INPUT_FILTER_PROPERTIES_H diff --git a/dali-toolkit/public-api/file.list b/dali-toolkit/public-api/file.list index 895bfc7..15e202b 100644 --- a/dali-toolkit/public-api/file.list +++ b/dali-toolkit/public-api/file.list @@ -114,6 +114,7 @@ SET( public_api_styling_header_files SET( public_api_text_controls_header_files ${public_api_src_dir}/controls/text-controls/hidden-input-properties.h + ${public_api_src_dir}/controls/text-controls/input-filter-properties.h ${public_api_src_dir}/controls/text-controls/placeholder-properties.h ${public_api_src_dir}/controls/text-controls/text-editor.h ${public_api_src_dir}/controls/text-controls/text-label.h