Add InputFilter to TextField, TextEditor 75/258375/14
authorBowon Ryu <bowon.ryu@samsung.com>
Fri, 14 May 2021 07:50:39 +0000 (16:50 +0900)
committerBowon Ryu <bowon.ryu@samsung.com>
Mon, 28 Jun 2021 07:18:49 +0000 (16:18 +0900)
Text input filter based on regular expressions.

// example
Property::Map filter;
filter[InputFilter::Property::ACCEPTED] = "[\\d]";
filter[InputFilter::Property::REJECTED] = "[0-5]";

field.SetProperty(DevelTextField::Property::INPUT_FILTER, filter);

DevelTextField::InputFilteredSignal(field).Connect(this, &OnInputFiltered);

```

void OnInputFiltered(TextField field, InputFilter::Property::Type type)
{
  Property::Map filter = field.GetProperty<Property::Map>(DevelTextField::Property::INPUT_FILTER);
  if (type == InputFilter::Property::ACCEPTED)
  {
    std::cout << "only follow character set is accepted " << filter[type] << std::endl;
  }
  else if (type == InputFilter::Property::REJECTED)
  {
    std::cout << "follow character set is rejected " << filter[type] << std::endl;
  }
}

Change-Id: Ia512438c9cb8aeb28c81f38e7b0220544a0751f0
Signed-off-by: Bowon Ryu <bowon.ryu@samsung.com>
21 files changed:
automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp
automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp
dali-toolkit/dali-toolkit.h
dali-toolkit/devel-api/controls/text-controls/text-editor-devel.cpp
dali-toolkit/devel-api/controls/text-controls/text-editor-devel.h
dali-toolkit/devel-api/controls/text-controls/text-field-devel.cpp
dali-toolkit/devel-api/controls/text-controls/text-field-devel.h
dali-toolkit/internal/controls/text-controls/text-editor-impl.cpp
dali-toolkit/internal/controls/text-controls/text-editor-impl.h
dali-toolkit/internal/controls/text-controls/text-field-impl.cpp
dali-toolkit/internal/controls/text-controls/text-field-impl.h
dali-toolkit/internal/file.list
dali-toolkit/internal/text/input-filter.cpp [new file with mode: 0644]
dali-toolkit/internal/text/input-filter.h [new file with mode: 0644]
dali-toolkit/internal/text/text-controller-event-handler.cpp
dali-toolkit/internal/text/text-controller-impl.h
dali-toolkit/internal/text/text-controller.cpp
dali-toolkit/internal/text/text-controller.h
dali-toolkit/internal/text/text-editable-control-interface.h
dali-toolkit/public-api/controls/text-controls/input-filter-properties.h [new file with mode: 0644]
dali-toolkit/public-api/file.list

index 31d1762..ba8e3d8 100644 (file)
@@ -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<Property::Map>(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;
index 2044ea3..3f78b20 100644 (file)
@@ -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<Vector4>( 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<Property::Map>(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
index 1cf4adc..839e96d 100644 (file)
@@ -44,6 +44,7 @@
 #include <dali-toolkit/public-api/controls/scrollable/scrollable.h>
 #include <dali-toolkit/public-api/controls/slider/slider.h>
 #include <dali-toolkit/public-api/controls/text-controls/hidden-input-properties.h>
+#include <dali-toolkit/public-api/controls/text-controls/input-filter-properties.h>
 #include <dali-toolkit/public-api/controls/text-controls/placeholder-properties.h>
 #include <dali-toolkit/public-api/controls/text-controls/text-editor.h>
 #include <dali-toolkit/public-api/controls/text-controls/text-field.h>
index 37678d8..a685aee 100644 (file)
@@ -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();
index 665af7a..baa872f 100644 (file)
@@ -21,6 +21,7 @@
 #include <dali/devel-api/adaptor-framework/input-method-context.h>
 
 // INTERNAL INCLUDES
+#include <dali-toolkit/public-api/controls/text-controls/input-filter-properties.h>
 #include <dali-toolkit/public-api/controls/text-controls/text-editor.h>
 
 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<void(TextEditor, const char*, uint32_t)>;
 DALI_TOOLKIT_API AnchorClickedSignalType& AnchorClickedSignal(TextEditor textEditor);
 
 /**
+ * @brief Input filtered signal type.
+ */
+using InputFilteredSignalType = Signal<void(TextEditor, Toolkit::InputFilter::Property::Type)>;
+
+/**
+ * @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.
index fdfac30..727b42a 100644 (file)
@@ -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();
index 3367bec..e1c18f0 100644 (file)
@@ -21,6 +21,7 @@
 #include <dali/devel-api/adaptor-framework/input-method-context.h>
 
 // INTERNAL INCLUDES
+#include <dali-toolkit/public-api/controls/text-controls/input-filter-properties.h>
 #include <dali-toolkit/public-api/controls/text-controls/text-field.h>
 
 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<void(TextField, const char*, uint32_t)>;
 DALI_TOOLKIT_API AnchorClickedSignalType& AnchorClickedSignal(TextField textField);
 
 /**
+ * @brief Input filtered signal type.
+ */
+using InputFilteredSignalType = Signal<void(TextField, Toolkit::InputFilter::Property::Type)>;
+
+/**
+ * @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.
index db7096a..56f43ff 100644 (file)
@@ -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)
index 0286da2..424cda5 100644 (file)
@@ -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;
index 4e5cd9c..dbf5624 100644 (file)
@@ -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)
index c76f05a..d1a2671 100644 (file)
@@ -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;
index f712e9f..01dc60f 100644 (file)
@@ -140,6 +140,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 (file)
index 0000000..eafcfe7
--- /dev/null
@@ -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 <dali-toolkit/internal/text/hidden-text.h>
+#include <dali-toolkit/internal/text/input-filter.h>
+
+// 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 (file)
index 0000000..6de0132
--- /dev/null
@@ -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 <dali/public-api/object/property-map.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/controls/text-controls/input-filter-properties.h>
+#include <regex>
+
+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
index c17c820..15805c8 100644 (file)
@@ -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;
 
index 9f637d3..55184ef 100644 (file)
@@ -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<ModifyEvent>         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<ModifyEvent>          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<InputFilter> 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.
index a863317..b2b6550 100644 (file)
@@ -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<InputFilter>(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);
index aaa3a66..0802a77 100644 (file)
@@ -28,6 +28,7 @@
 #include <dali-toolkit/devel-api/text/text-enumerations-devel.h>
 #include <dali-toolkit/internal/text/decorator/text-decorator.h>
 #include <dali-toolkit/internal/text/hidden-text.h>
+#include <dali-toolkit/internal/text/input-filter.h>
 #include <dali-toolkit/internal/text/layouts/layout-engine.h>
 #include <dali-toolkit/internal/text/text-anchor-control-interface.h>
 #include <dali-toolkit/internal/text/text-model-interface.h>
@@ -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
index 4bd0de0..af2abd0 100644 (file)
@@ -20,6 +20,7 @@
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/internal/text/input-style.h>
+#include <dali-toolkit/public-api/controls/text-controls/input-filter-properties.h>
 
 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 (file)
index 0000000..6f9b07f
--- /dev/null
@@ -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
index 895bfc7..15e202b 100644 (file)
@@ -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