Elm helper: add the posix regex validator.
authorVyacheslav Reutskiy <v.reutskiy@samsung.com>
Wed, 1 Apr 2015 15:10:37 +0000 (16:10 +0100)
committerTom Hacohen <tom@stosb.com>
Wed, 1 Apr 2015 15:11:18 +0000 (16:11 +0100)
Summary:
Added to Entry new signal "validate", this signal called from entry every
time when the text inputed to entry.
The regex validation add as elm_helper.
The styles of Entries scrollers are changed to allow highlightion that
is needed by regex processing.

For use regex with entry need register the regex helper as callback
to event: ELM_ENTRY_EVENT_VALIDATE

@feature

Test Plan:
See elementary_test "Entry Regex" test.
Note: when the string matches to regex the highlighting (green) is reset on unfocusing.

Reviewers: herdsman, raster, cedric, tasn

Reviewed By: cedric, tasn

Subscribers: seoz

Differential Revision: https://phab.enlightenment.org/D2043

data/themes/edc/elm/entry.edc
src/bin/test.c
src/bin/test_entry.c
src/lib/Elementary.h.in
src/lib/Makefile.am
src/lib/elm_entry.c
src/lib/elm_entry.eo
src/lib/elm_entry.h
src/lib/elm_helper.c [new file with mode: 0644]
src/lib/elm_helper.h [new file with mode: 0644]
src/lib/elm_widget_entry.h

index cf836fc..4967f67 100644 (file)
@@ -2,6 +2,20 @@ group { name: "elm/scroller/entry/default";
    inherit: "elm/scroller/base/default";
    image: "bg_glow_in.png" COMP;
    parts {
+      part { name: "validation_glow";
+         type: RECT;
+         insert_before: "bg";
+         mouse_events: 0;
+         description { state: "default" 0.0;
+            color: 0 0 0 0;
+         }
+         description { state: "pass" 0.0;
+            color: 0 255 0 90;
+         }
+         description { state: "fail" 0.0;
+            color: 255 0 0 90;
+         }
+      }
       part { name: "bg";
          description { state: "default" 0.0;
             color: DARK_GREY_BG_COLOR;
@@ -113,6 +127,24 @@ group { name: "elm/scroller/entry/default";
          transition: DECELERATE 0.3;
          target: "glow";
       }
+      program { name: "validation_fail";
+         signal: "validation,default,fail";
+         source: "elm";
+         action: STATE_SET "fail" 0.0;
+         target: "validation_glow";
+      }
+      program { name: "validation_pass";
+         signal: "validation,default,pass";
+         source: "elm";
+         action: STATE_SET "pass" 0.0;
+         target: "validation_glow";
+      }
+      program { name: "validation_off";
+         signal: "validation,default";
+         source: "elm";
+         action: STATE_SET "default" 0.0;
+         target: "validation_glow";
+      }
    }
 }
 
@@ -124,6 +156,20 @@ group { name: "elm/scroller/entry_single/default";
    data.item: "focus_highlight" "on";
    
    parts {
+      part { name: "validation_glow";
+         type: RECT;
+         insert_before: "bg";
+         mouse_events: 0;
+         description { state: "default" 0.0;
+            color: 0 0 0 0;
+         }
+         description { state: "pass" 0.0;
+            color: 0 255 0 90;
+         }
+         description { state: "fail" 0.0;
+            color: 255 0 0 90;
+         }
+      }
       part { name: "sb_vbar_base"; type: RECT;
          description { state: "default" 0.0;
             fixed: 1 1;
@@ -315,6 +361,24 @@ group { name: "elm/scroller/entry_single/default";
          transition: DECELERATE 0.3;
          target: "glow";
       }
+      program { name: "validation_fail";
+         signal: "validation,default,fail";
+         source: "elm";
+         action: STATE_SET "fail" 0.0;
+         target: "validation_glow";
+      }
+      program { name: "validation_pass";
+         signal: "validation,default,pass";
+         source: "elm";
+         action: STATE_SET "pass" 0.0;
+         target: "validation_glow";
+      }
+      program { name: "validation_off";
+         signal: "validation,default";
+         source: "elm";
+         action: STATE_SET "default" 0.0;
+         target: "validation_glow";
+      }
    }
 }
 
index a30109c..4a8a35d 100644 (file)
@@ -62,6 +62,7 @@ void test_entry5(void *data, Evas_Object *obj, void *event_info);
 void test_entry6(void *data, Evas_Object *obj, void *event_info);
 void test_entry7(void *data, Evas_Object *obj, void *event_info);
 void test_entry8(void *data, Evas_Object *obj, void *event_info);
+void test_entry_regex(void *data, Evas_Object *obj, void *event_info);
 void test_entry_notepad(void *data, Evas_Object *obj, void *event_info);
 void test_multibuttonentry(void *data, Evas_Object *obj, void *event_info);
 void test_entry_anchor2(void *data, Evas_Object *obj, void *event_info);
@@ -580,6 +581,7 @@ add_tests:
    ADD_TEST(NULL, "Entries", "Entry 6", test_entry6);
    ADD_TEST(NULL, "Entries", "Entry 7", test_entry7);
    ADD_TEST(NULL, "Entries", "Entry 8", test_entry8);
+   ADD_TEST(NULL, "Entries", "Entry Regex", test_entry_regex);
    ADD_TEST(NULL, "Entries", "Entry Notepad", test_entry_notepad);
    ADD_TEST(NULL, "Entries", "Multibuttonentry", test_multibuttonentry);
    ADD_TEST(NULL, "Entries", "Entry Anchor", test_entry_anchor);
index 025e7c7..3c87c8d 100644 (file)
@@ -2445,6 +2445,170 @@ test_entry8(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_in
    evas_object_show(win);
 }
 
+typedef struct {
+     Evas_Object *en_regex;
+     Evas_Object *lb_regex;
+     Evas_Object *en_single;
+     Evas_Object *en_multi;
+     Elm_Validator_Regexp *re;
+} _test_regex_data_t;
+
+static void
+_test_regex_data_del(void *data,
+                     Evas *e EINA_UNUSED,
+                     Evas_Object *obj EINA_UNUSED,
+                     void *event_info EINA_UNUSED)
+{
+   _test_regex_data_t *test_data = data;
+   if (test_data->re) elm_validator_regexp_free(test_data->re);
+   free(test_data);
+}
+
+static void
+_test_regex_bt_cb(void *data,
+                  Evas_Object *obj EINA_UNUSED,
+                  void *event_info EINA_UNUSED)
+{
+   int status;
+   _test_regex_data_t *test_data = data;
+   Eina_Strbuf *tmp;
+
+   if (test_data->re)
+     {
+        eo_do(test_data->en_single, eo_event_callback_del(ELM_ENTRY_EVENT_VALIDATE, elm_validator_regexp_helper, test_data->re));
+        eo_do(test_data->en_multi, eo_event_callback_del(ELM_ENTRY_EVENT_VALIDATE, elm_validator_regexp_helper, test_data->re));
+        elm_validator_regexp_free(test_data->re);
+     }
+   test_data->re = elm_validator_regexp_new(elm_entry_entry_get(test_data->en_regex), NULL);
+
+   status = elm_validator_regexp_status_get(test_data->re);
+   tmp = eina_strbuf_manage_new(strdup("Regex error: "));
+   switch (status)
+     {
+     case ELM_REG_NOERROR:
+          {
+             eina_strbuf_append(tmp, "No error. Regex maches to the Entrys text.");
+             break;
+          }
+     case ELM_REG_BADPAT:
+          {
+             eina_strbuf_append(tmp, "Invalid regular expression.");
+             break;
+          }
+     default:
+        break;
+     }
+   if (!status)
+     {
+        eo_do(test_data->en_single, eo_event_callback_add(ELM_ENTRY_EVENT_VALIDATE, elm_validator_regexp_helper, test_data->re));
+        eo_do(test_data->en_multi, eo_event_callback_add(ELM_ENTRY_EVENT_VALIDATE, elm_validator_regexp_helper, test_data->re));
+     }
+
+   elm_object_text_set(test_data->lb_regex, eina_strbuf_string_get(tmp));
+   eina_strbuf_free(tmp);
+}
+
+static inline Evas_Object *
+_test_regex_entry_add(Evas_Object *parent, Eina_Bool is_singleline)
+{
+   Evas_Object *entry = elm_entry_add(parent);
+   elm_entry_scrollable_set(entry, EINA_TRUE);
+   elm_entry_single_line_set(entry, is_singleline);
+   evas_object_size_hint_weight_set(entry, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+   evas_object_size_hint_align_set(entry, EVAS_HINT_FILL, EVAS_HINT_FILL);
+   elm_entry_editable_set(entry, EINA_TRUE);
+   evas_object_show(entry);
+   return entry;
+}
+
+void
+test_entry_regex(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
+{
+   Evas_Object *win, *scr, *box;
+   Evas_Object *item, *label, *bt, *sep;
+   _test_regex_data_t *test_data;
+#define REGEX "^[0-9]*$"
+
+   test_data = malloc(sizeof(_test_regex_data_t));
+
+   win = elm_win_util_standard_add("Entry", "Entry Regex Test");
+   elm_win_autodel_set(win, EINA_TRUE);
+   evas_object_event_callback_add(win, EVAS_CALLBACK_DEL,
+                                  _test_regex_data_del, test_data);
+
+   scr = elm_scroller_add(win);
+   elm_scroller_bounce_set(scr, EINA_FALSE, EINA_TRUE);
+   elm_scroller_policy_set(scr, ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_AUTO);
+   evas_object_size_hint_weight_set(scr, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+   elm_win_resize_object_add(win, scr);
+   evas_object_show(scr);
+
+   box = elm_box_add(win);
+   evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+   elm_box_homogeneous_set(box, EINA_FALSE);
+   elm_object_content_set(scr, box);
+
+   evas_object_show(box);
+
+   label = elm_label_add(box);
+   elm_object_text_set(label, "Regular Expression");
+   elm_box_pack_end(box, label);
+   evas_object_show(label);
+
+   item = elm_box_add(win);
+   evas_object_size_hint_weight_set(item, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+   evas_object_size_hint_align_set(item, EVAS_HINT_FILL, EVAS_HINT_FILL);
+   elm_box_horizontal_set(item, EINA_TRUE);
+   elm_box_homogeneous_set(item, EINA_FALSE);
+   evas_object_show(item);
+   elm_box_pack_end(box, item);
+
+   test_data->en_regex =  _test_regex_entry_add(item, EINA_TRUE);
+   elm_box_pack_end(item, test_data->en_regex);
+
+   bt = elm_button_add(item);
+   elm_object_text_set(bt, "Set");
+   evas_object_smart_callback_add(bt, "clicked", _test_regex_bt_cb, test_data);
+   evas_object_show(bt);
+   elm_box_pack_end(item, bt);
+
+   test_data->lb_regex = elm_label_add(box);
+   elm_object_text_set(test_data->lb_regex, "Regex error: no error.");
+   elm_box_pack_end(box, test_data->lb_regex);
+   evas_object_show(test_data->lb_regex);
+
+   sep = elm_separator_add(box);
+   elm_separator_horizontal_set(sep, EINA_TRUE);
+   evas_object_show(sep);
+   elm_box_pack_end(box, sep);
+
+   label = elm_label_add(box);
+   elm_object_text_set(label, "Test regex in single line entry");
+   elm_box_pack_end(box, label);
+   evas_object_show(label);
+
+   test_data->en_single =  _test_regex_entry_add(box, EINA_TRUE);
+   elm_box_pack_end(box, test_data->en_single);
+
+   label = elm_label_add(box);
+   elm_object_text_set(label, "Test regex in multi line entry");
+   elm_box_pack_end(box, label);
+   evas_object_show(label);
+
+   test_data->en_multi =  _test_regex_entry_add(box, EINA_FALSE);
+   elm_box_pack_end(box, test_data->en_multi);
+
+   test_data->re = elm_validator_regexp_new(REGEX, NULL);
+   elm_entry_entry_set(test_data->en_regex, REGEX);
+   eo_do(test_data->en_single, eo_event_callback_add(ELM_ENTRY_EVENT_VALIDATE, elm_validator_regexp_helper, test_data->re));
+   eo_do(test_data->en_multi, eo_event_callback_add(ELM_ENTRY_EVENT_VALIDATE, elm_validator_regexp_helper, test_data->re));
+
+   evas_object_resize(win, 400, 400);
+   evas_object_show(win);
+
+#undef REGEX
+}
+
 static void
 _scrolled_entry_clear(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
 {
index 6e87bf0..92e0adc 100644 (file)
@@ -264,6 +264,8 @@ EAPI extern Elm_Version *elm_version;
 /* include deprecated calls last of all */
 #include <elm_deprecated.h>
 
+#include <elm_helper.h>
+
 #ifdef __cplusplus
 }
 #endif
index cd28380..cad8594 100644 (file)
@@ -383,7 +383,8 @@ elm_web_legacy.h \
 elm_win.h \
 elm_win_common.h \
 elm_win_eo.h \
-elm_win_legacy.h
+elm_win_legacy.h \
+elm_helper.h
 includesubdir = $(includedir)/elementary-@VMAJ@/
 
 libelementary_la_SOURCES = \
@@ -495,6 +496,7 @@ elm_video.c \
 elm_web2.c \
 elm_widget.c \
 elm_win.c \
+elm_helper.c \
 els_box.c \
 els_cursor.c \
 els_tooltip.c \
index 8c664e5..3dace14 100644 (file)
@@ -282,6 +282,25 @@ _elm_entry_guide_update(Evas_Object *obj,
    sd->has_text = has_text;
 }
 
+static void
+_validate(Evas_Object *obj)
+{
+   ELM_ENTRY_DATA_GET(obj, sd);
+   Eina_Bool res;
+   Elm_Validate_Content vc;
+   Eina_Strbuf *buf;
+
+   if (sd->validators == 0) return;
+
+   vc.text = edje_object_part_text_get(sd->entry_edje, "elm.text");
+   eo_do(obj, res = eo_event_callback_call(ELM_ENTRY_EVENT_VALIDATE, (void *)&vc));
+   buf = eina_strbuf_new();
+   eina_strbuf_append_printf(buf, "validation,%s,%s", vc.signal, res == EO_CALLBACK_STOP ? "fail" : "pass");
+   edje_object_signal_emit(sd->scr_edje, eina_strbuf_string_get(buf), "elm");
+   eina_tmpstr_del(vc.signal);
+   eina_strbuf_free(buf);
+}
+
 static Elm_Entry_Markup_Filter *
 _filter_new(Elm_Entry_Filter_Cb func,
             void *data)
@@ -1081,6 +1100,7 @@ _elm_entry_elm_widget_on_focus(Eo *obj, Elm_Entry_Data *sd)
           elm_win_keyboard_mode_set(top, ELM_WIN_KEYBOARD_ON);
         evas_object_smart_callback_call(obj, SIG_FOCUSED, NULL);
         _return_key_enabled_check(obj);
+        _validate(obj);
      }
    else
      {
@@ -1102,6 +1122,7 @@ _elm_entry_elm_widget_on_focus(Eo *obj, Elm_Entry_Data *sd)
                   edje_object_part_text_select_none(sd->entry_edje, "elm.text");
                }
           }
+        edje_object_signal_emit(sd->scr_edje, "validation,default", "elm");
      }
 
    return EINA_TRUE;
@@ -1873,6 +1894,8 @@ _entry_changed_handle(void *data,
         else
           _elm_entry_guide_update(data, EINA_FALSE);
      }
+   _validate(data);
+
    /* callback - this could call callbacks that delete the
     * entry... thus... any access to sd after this could be
     * invalid */
@@ -3671,6 +3694,35 @@ elm_entry_add(Evas_Object *parent)
    return obj;
 }
 
+static Eina_Bool
+_cb_added(void *data EINA_UNUSED,
+          Eo *obj,
+          const Eo_Event_Description *desc EINA_UNUSED,
+          void *event_info)
+{
+   const Eo_Callback_Array_Item *event = event_info;
+
+   ELM_ENTRY_DATA_GET(obj, sd);
+   if (event->desc == ELM_ENTRY_EVENT_VALIDATE)
+     sd->validators++;
+   return EO_CALLBACK_CONTINUE;
+}
+
+static Eina_Bool
+_cb_deled(void *data EINA_UNUSED,
+          Eo *obj,
+          const Eo_Event_Description *desc EINA_UNUSED,
+          void *event_info)
+{
+   const Eo_Callback_Array_Item *event = event_info;
+
+   ELM_ENTRY_DATA_GET(obj, sd);
+   if (event->desc == ELM_ENTRY_EVENT_VALIDATE)
+     sd->validators--;
+   return EO_CALLBACK_CONTINUE;
+
+}
+
 EOLIAN static void
 _elm_entry_eo_base_constructor(Eo *obj, Elm_Entry_Data *_pd EINA_UNUSED)
 {
@@ -3678,7 +3730,9 @@ _elm_entry_eo_base_constructor(Eo *obj, Elm_Entry_Data *_pd EINA_UNUSED)
    eo_do(obj,
          evas_obj_type_set(MY_CLASS_NAME_LEGACY),
          evas_obj_smart_callbacks_descriptions_set(_smart_callbacks),
-         elm_interface_atspi_accessible_role_set(ELM_ATSPI_ROLE_ENTRY));
+         elm_interface_atspi_accessible_role_set(ELM_ATSPI_ROLE_ENTRY),
+         eo_event_callback_add(EO_EV_CALLBACK_ADD, _cb_added, NULL),
+         eo_event_callback_add(EO_EV_CALLBACK_DEL, _cb_deled, NULL));
 }
 
 EOLIAN static void
index 1a444be..59f8209 100644 (file)
@@ -1232,6 +1232,7 @@ class Elm_Entry (Elm_Layout, Elm_Interface_Scrollable, Evas.Clickable_Interface,
       activated;
       changed;
       changed,user;
+      validate;
    }
 
 }
index c38adf8..c4dd8bd 100644 (file)
  * @li "theme,changed": Called when the theme is changed.
  * @li "undo,request": Called on undo request.
  * @li "rejected": Called when some of inputs are rejected by the filter. (since 1.9)
+ * @li "validate": Called when entry text ready to validate. (since 1.14)
  *
  * Default content parts of the entry items that you can use for are:
  * @li "icon" - An icon in the entry
diff --git a/src/lib/elm_helper.c b/src/lib/elm_helper.c
new file mode 100644 (file)
index 0000000..57a0423
--- /dev/null
@@ -0,0 +1,53 @@
+#ifdef HAVE_CONFIG_H
+# include "elementary_config.h"
+#endif
+
+#include <Elementary.h>
+#include <regex.h>
+
+struct _Elm_Validator_Regexp
+{
+   Eina_Stringshare *signal;
+   int status;
+   regex_t regex;
+};
+
+EAPI Elm_Validator_Regexp *
+elm_validator_regexp_new(const char *pattern, const char *signal)
+{
+   Elm_Validator_Regexp *validator;
+
+   validator = calloc(1, sizeof(Elm_Validator_Regexp));
+   validator->signal = eina_stringshare_add(signal ? signal : "default");
+   validator->status = regcomp(&validator->regex, pattern, REG_EXTENDED | REG_NOSUB) ? ELM_REG_BADPAT : ELM_REG_NOERROR;
+
+   return validator;
+}
+
+EAPI void
+elm_validator_regexp_free(Elm_Validator_Regexp *validator)
+{
+   eina_stringshare_del(validator->signal);
+   regfree(&validator->regex);
+   free(validator);
+}
+
+EAPI Elm_Regexp_Status
+elm_validator_regexp_status_get(Elm_Validator_Regexp *validator)
+{
+   return validator->status;
+}
+
+EAPI Eina_Bool
+elm_validator_regexp_helper(void *data,
+                            Eo *obj EINA_UNUSED,
+                            const Eo_Event_Description *desc EINA_UNUSED,
+                            void *event_info)
+{
+   Elm_Validate_Content *vc = event_info;
+   Elm_Validator_Regexp *validator = (Elm_Validator_Regexp *)data;
+
+   validator->status = regexec(&validator->regex, vc->text, (size_t)0, NULL, 0) ? ELM_REG_NOMATCH : ELM_REG_NOERROR;
+   vc->signal = validator->signal;
+   return validator->status ? EO_CALLBACK_STOP : EO_CALLBACK_CONTINUE;
+}
diff --git a/src/lib/elm_helper.h b/src/lib/elm_helper.h
new file mode 100644 (file)
index 0000000..c09fd6f
--- /dev/null
@@ -0,0 +1,108 @@
+/**
+ * @defgroup Helper Helper
+ * @ingroup Elementary
+ *
+ * The validation helper feature.
+ *
+ * @{
+ */
+
+struct _Elm_Validate_Content
+{
+   const char *text;
+   Eina_Tmpstr *signal;
+};
+/**
+ * Data for the elm_validator_regexp_helper()
+ */
+typedef struct _Elm_Validate_Content Elm_Validate_Content;
+
+/**
+ * The Regexp validator data.
+ */
+typedef struct _Elm_Validator_Regexp Elm_Validator_Regexp;
+
+/**
+ * @brief Enumeration that defines the regex error codes
+ * @since 1.14
+ */
+typedef enum
+{
+   /** Regex maches to the Entrys text. */
+   ELM_REG_NOERROR = 0,
+   /** Failed to match. */
+   ELM_REG_NOMATCH,
+   /** Invalid regular expression. */
+   ELM_REG_BADPAT,
+} Elm_Regexp_Status;
+
+/**
+ * @brief Create a new regex validator.
+ * General designed for validate inputed entry text.
+ *
+ * @param pattern The regex pattern
+ * @param signal The part of signal name, which will be emited to style
+ * @return The regex validator
+ *
+ * @see elm_validator_regexp_del()
+ * @see elm_validator_regex_regex_set()
+ * @see elm_validator_regexp_status_get()
+ * @see elm_validator_regexp_helper()
+ *
+ * @since 1.14
+ */
+EAPI Elm_Validator_Regexp *
+elm_validator_regexp_new(const char *pattern, const char *signal) EINA_ARG_NONNULL(1);
+
+/**
+ * @brief Delete the existing regex validator.
+ *
+ * @param validator The given validator
+ *
+ * @see elm_validator_regexp_add()
+ *
+ * @since 1.14
+ */
+EAPI void
+elm_validator_regexp_free(Elm_Validator_Regexp *validator) EINA_ARG_NONNULL(1);
+
+/**
+ * @brief Get the validation status.
+ *
+ * @param The given validator
+ *
+ * @note All return value see here: http://www.gnu.org/software/libc/manual/html_node/Regular-Expressions.html
+ *
+ * @since 1.14
+ */
+EAPI Elm_Regexp_Status
+elm_validator_regexp_status_get(Elm_Validator_Regexp *validator) EINA_ARG_NONNULL(1);
+
+/**
+ * @brief The regex validator. Used as callback to validate event.
+ *
+ * Example:
+ * @code
+ * extern Evas_Object *parent;
+ * Evas_Object *entry;
+ * Elm_Validator_Regexp *re;
+ *
+ * //add validator
+ * entry = elm_entry_add(parent);
+ * re = elm_validator_regexp_add("^[0-9]*$");
+ * eo_do(entry, eo_event_callback_add(ELM_ENTRY_EVENT_VALIDATE, elm_validator_regexp_helper, re));
+ *
+ * //delete validator
+ * eo_do(entry, eo_event_callback_del(ELM_ENTRY_EVENT_VALIDATE, elm_validator_regexp_helper, re));
+ * @endcode
+ *
+ * @see elm_validator_regexp_add()
+ * @see elm_validotor_regex_regex_set()
+ * @since 1.14
+ */
+EAPI Eina_Bool
+elm_validator_regexp_helper(void *data, Eo *obj, const Eo_Event_Description *desc, void *event_info);
+
+/**
+ * @}
+ */
index c29275b..4af06b9 100644 (file)
@@ -72,6 +72,7 @@ struct _Elm_Entry_Data
    void                                 *input_panel_imdata;
    int                                   input_panel_imdata_len;
    int                                   input_panel_layout_variation;
+   int                                   validators;
    struct
      {
         Evas_Object *hover_parent; /**< hover parent object. entry is a hover parent object by default */