elm_datetime: elm_widget_can_focus_set before module init for support TV UX
[platform/upstream/elementary.git] / src / lib / elm_datetime.c
1 #ifdef HAVE_CONFIG_H
2 # include "elementary_config.h"
3 #endif
4
5 #define ELM_INTERFACE_ATSPI_ACCESSIBLE_PROTECTED
6
7 #include <Elementary.h>
8 #include "elm_priv.h"
9 #include "elm_widget_datetime.h"
10
11 #define MY_CLASS ELM_DATETIME_CLASS
12
13 #define MY_CLASS_NAME "Elm_Datetime"
14 #define MY_CLASS_NAME_LEGACY "elm_datetime"
15
16 #ifdef HAVE_LOCALE_H
17 # include <locale.h>
18 #endif
19
20 #ifdef HAVE_LANGINFO_H
21 # include <langinfo.h>
22 #endif
23
24 #define MAX_SEPARATOR_LEN              6
25 #define MIN_DAYS_IN_MONTH              28
26 #define BUFFER_SIZE                    1024
27
28 /* interface between EDC & C code (field & signal names). values 0 to
29  * ELM_DATETIME_TYPE_COUNT are in the valid range, and must get in the
30  * place of "%d".
31  */
32 #define EDC_DATETIME_FOCUSIN_SIG_STR   "elm,action,focus"
33 #define EDC_DATETIME_FOCUSOUT_SIG_STR  "elm,action,unfocus"
34 #define EDC_PART_FIELD_STR             "field%d"
35 #define EDC_PART_SEPARATOR_STR         "separator%d"
36 #define EDC_PART_FIELD_ENABLE_SIG_STR  "field%d,enable"
37 #define EDC_PART_FIELD_DISABLE_SIG_STR "field%d,disable"
38
39 /* struct tm does not define the fields in the order year, month,
40  * date, hour, minute. values are reassigned to an array for easy
41  * handling.
42  */
43 #define DATETIME_TM_ARRAY(intptr, tmptr) \
44   int *intptr[] = {                      \
45      &(tmptr)->tm_year,                  \
46      &(tmptr)->tm_mon,                   \
47      &(tmptr)->tm_mday,                  \
48      &(tmptr)->tm_hour,                  \
49      &(tmptr)->tm_min,                  \
50      &(tmptr)->tm_sec}
51
52 // default limits for individual fields
53 static Format_Map mapping[ELM_DATETIME_TYPE_COUNT] = {
54    [ELM_DATETIME_YEAR] = { "Yy", -1, -1, "" },
55    [ELM_DATETIME_MONTH] = { "mbBh", 0, 11, "" },
56    [ELM_DATETIME_DATE] = { "de", 1, 31, "" },
57    [ELM_DATETIME_HOUR] = { "IHkl", 0, 23, "" },
58    [ELM_DATETIME_MINUTE] = { "M", 0, 59, ":" },
59    [ELM_DATETIME_AMPM] = { "pP", 0, 1, "" }
60 };
61
62 static const char *multifield_formats = "cxXrRTDF";
63 static const char *ignore_separators = "()";
64 static const char *ignore_extensions = "E0_-O^#";
65 static Datetime_Mod_Api *dt_mod = NULL;
66
67 static const char SIG_CHANGED[] = "changed";
68 static const Evas_Smart_Cb_Description _smart_callbacks[] = {
69    {SIG_CHANGED, ""},
70    {SIG_WIDGET_LANG_CHANGED, ""}, /**< handled by elm_widget */
71    {SIG_WIDGET_ACCESS_CHANGED, ""}, /**< handled by elm_widget */
72    {SIG_LAYOUT_FOCUSED, ""}, /**< handled by elm_layout */
73    {SIG_LAYOUT_UNFOCUSED, ""}, /**< handled by elm_layout */
74    {NULL, NULL}
75 };
76
77 static Datetime_Mod_Api *
78 _dt_mod_init()
79 {
80    Elm_Module *mod = NULL;
81
82    if (!(mod = _elm_module_find_as("datetime/api"))) return NULL;
83
84    mod->api = malloc(sizeof(Datetime_Mod_Api));
85    if (!mod->api) return NULL;
86
87    ((Datetime_Mod_Api *)(mod->api))->obj_hook =
88      _elm_module_symbol_get(mod, "obj_hook");
89    ((Datetime_Mod_Api *)(mod->api))->obj_unhook =
90      _elm_module_symbol_get(mod, "obj_unhook");
91    ((Datetime_Mod_Api *)(mod->api))->obj_hide =
92      _elm_module_symbol_get(mod, "obj_hide");
93    ((Datetime_Mod_Api *)(mod->api))->field_create =
94      _elm_module_symbol_get(mod, "field_create");
95    ((Datetime_Mod_Api *)(mod->api))->field_value_display =
96      _elm_module_symbol_get(mod, "field_value_display");
97    //TIZEN_ONLY(20151218): Support Mobile UX
98    ((Datetime_Mod_Api *)(mod->api))->field_format_changed =
99      _elm_module_symbol_get(mod, "field_format_changed");
100    //
101
102    return mod->api;
103 }
104
105 static void
106 _field_list_display(Evas_Object *obj)
107 {
108    Datetime_Field *field;
109    unsigned int idx = 0;
110
111    ELM_DATETIME_DATA_GET(obj, sd);
112
113    if (!dt_mod || !dt_mod->field_value_display) return;
114
115    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
116      {
117         field = sd->field_list + idx;
118         if (field->fmt_exist && field->visible)
119           dt_mod->field_value_display(sd->mod_data, field->item_obj);
120      }
121 }
122
123 // FIXME: provide nl_langinfo on Windows if possible
124 // returns expanded format string for corresponding multi-field format character
125 static char *
126 _expanded_fmt_str_get(char ch)
127 {
128    char *exp_fmt = "";
129    switch (ch)
130      {
131       case 'c':
132 #if defined(HAVE_LANGINFO_H) || defined (HAVE_EVIL)
133         exp_fmt = nl_langinfo(D_T_FMT);
134 #else
135         exp_fmt = "";
136 #endif
137         break;
138
139       case 'x':
140 #if defined(HAVE_LANGINFO_H) || defined (HAVE_EVIL)
141         exp_fmt = nl_langinfo(D_FMT);
142 #else
143         exp_fmt = "";
144 #endif
145         break;
146
147       case 'X':
148 #if defined(HAVE_LANGINFO_H) || defined (HAVE_EVIL)
149         exp_fmt = nl_langinfo(T_FMT);
150 #else
151         exp_fmt = "";
152 #endif
153         break;
154
155       case 'r':
156 #if defined(HAVE_LANGINFO_H) || defined (HAVE_EVIL)
157         exp_fmt = nl_langinfo(T_FMT_AMPM);
158 #else
159         exp_fmt = "";
160 #endif
161         break;
162
163       case 'R':
164         exp_fmt = "%H:%M";
165         break;
166
167       case 'T':
168         exp_fmt = "%H:%M:%S";
169         break;
170
171       case 'D':
172         exp_fmt = "%m/%d/%y";
173         break;
174
175       case 'F':
176         exp_fmt = "%Y-%m-%d";
177         break;
178
179       default:
180         exp_fmt = "";
181         break;
182      }
183
184    return exp_fmt;
185 }
186
187 static void
188 _expand_format(char *dt_fmt)
189 {
190    char *ptr, *expanded_fmt, ch;
191    unsigned int idx, len = 0;
192    char buf[ELM_DATETIME_MAX_FORMAT_LEN] = {0, };
193    Eina_Bool fmt_char, fmt_expanded;
194
195    do {
196      idx = 0;
197      fmt_char = EINA_FALSE;
198      fmt_expanded = EINA_FALSE;
199      ptr = dt_fmt;
200      while ((ch = *ptr))
201        {
202           if ((fmt_char) && (strchr(multifield_formats, ch)))
203             {
204                /* replace the multi-field format characters with
205                 * corresponding expanded format */
206                expanded_fmt = _expanded_fmt_str_get(ch);
207                len = strlen(expanded_fmt);
208                if (len > 0) fmt_expanded = EINA_TRUE;
209                buf[--idx] = 0;
210                strncat(buf, expanded_fmt, len);
211                idx += len;
212             }
213           else buf[idx++] = ch;
214
215           if (ch == '%') fmt_char = EINA_TRUE;
216           else fmt_char = EINA_FALSE;
217
218           ptr++;
219        }
220
221      buf[idx] = 0;
222      strncpy(dt_fmt, buf, ELM_DATETIME_MAX_FORMAT_LEN);
223    } while (fmt_expanded);
224 }
225
226 static void
227 _field_list_arrange(Evas_Object *obj)
228 {
229    Datetime_Field *field;
230    char buf[BUFFER_SIZE];
231    int idx;
232    Eina_Bool freeze;
233
234    ELM_DATETIME_DATA_GET(obj, sd);
235
236    freeze = sd->freeze_sizing;
237    sd->freeze_sizing = EINA_TRUE;
238
239    //////////////////////////////////////////////////////////////////////////////
240    //TIZEN_ONLY(20150225): Open source datetime code doesn't consider dynamically
241    //                      field sequence changing case.
242    /*
243    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
244      {
245         field = sd->field_list + idx;
246         snprintf(buf, sizeof(buf), EDC_PART_FIELD_STR, field->location);
247
248         if (field->visible && field->fmt_exist)
249           {
250              elm_layout_content_unset(obj, buf);
251              elm_layout_content_set(obj, buf, field->item_obj);
252           }
253         else
254           evas_object_hide(elm_layout_content_unset(obj, buf));
255      }
256    */
257    // remove all the content widget from the layout
258    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
259      {
260         snprintf(buf, sizeof(buf), EDC_PART_FIELD_STR, idx);
261         elm_layout_content_unset(obj, buf);
262      }
263    // set as content widget or hide it depending on location
264    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
265      {
266         field = sd->field_list + idx;
267         snprintf(buf, sizeof(buf), EDC_PART_FIELD_STR, field->location);
268
269         if (field->visible && field->fmt_exist)
270           elm_layout_content_set(obj, buf, field->item_obj);
271         else
272           evas_object_hide(field->item_obj);
273      }
274    //////////////////////////////////////////////////////////////////////////////
275
276    sd->freeze_sizing = freeze;
277
278    elm_layout_sizing_eval(obj);
279    _field_list_display(obj);
280 }
281
282 static unsigned int
283 _parse_format(Evas_Object *obj,
284               char *fmt_ptr)
285 {
286    Eina_Bool fmt_parsing = EINA_FALSE, sep_parsing = EINA_FALSE,
287              sep_lookup = EINA_FALSE;
288    //TIZEN_ONLY(20151216) - swap format locations to support some locale.
289    Eina_Bool location_swap = EINA_FALSE;
290    //
291    unsigned int len = 0, idx = 0, location = 0;
292    char separator[MAX_SEPARATOR_LEN];
293    Datetime_Field *field = NULL;
294    char cur;
295
296    ELM_DATETIME_DATA_GET(obj, sd);
297
298    while ((cur = *fmt_ptr))
299      {
300         if (fmt_parsing)
301           {
302              if (strchr(ignore_extensions, cur))
303                {
304                   fmt_ptr++;
305                   continue;
306                }
307              fmt_parsing = EINA_FALSE;
308
309              //TIZEN_ONLY(20151216) - swap format locations to support some locale.
310              if (location == 0 &&
311                  (strchr(mapping[ELM_DATETIME_HOUR].fmt_char, cur) ||
312                   strchr(mapping[ELM_DATETIME_MINUTE].fmt_char, cur) ||
313                   strchr(mapping[ELM_DATETIME_AMPM].fmt_char, cur)))
314                location_swap = EINA_TRUE;
315              //
316              for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
317                {
318                   if (strchr(mapping[idx].fmt_char, cur))
319                     {
320                        field = sd->field_list + idx;
321                        /* ignore the fields already have or disabled
322                         * valid formats, means already parsed &
323                         * repeated, ignore. */
324                        if (field->location != -1) break;
325                        field->fmt[1] = cur;
326                        field->fmt_exist = EINA_TRUE;
327                        field->location = location++;
328                        sep_lookup = EINA_TRUE;
329                        len = 0;
330                        break;
331                     }
332                }
333           }
334         if (cur == '%')
335           {
336              fmt_parsing = EINA_TRUE;
337              sep_parsing = EINA_FALSE;
338              // set the separator to previous field
339              separator[len] = 0;
340              if (field) eina_stringshare_replace(&field->separator, separator);
341           }
342         // ignore the set of chars (global, field specific) as field separators
343         if (sep_parsing &&
344             (len < MAX_SEPARATOR_LEN - 1) &&
345             (field->type != ELM_DATETIME_AMPM) &&
346             (!strchr(ignore_separators, cur)) &&
347             (!strchr(mapping[idx].ignore_sep, cur)))
348           separator[len++] = cur;
349         if (sep_lookup) sep_parsing = EINA_TRUE;
350         sep_lookup = EINA_FALSE;
351         fmt_ptr++;
352      }
353
354    //TIZEN_ONLY(20151216) - swap format locations to support some locale.
355    if (location_swap)
356      {
357         int time_fmt_count;
358
359         time_fmt_count = location - ELM_DATETIME_HOUR;
360         for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
361           {
362              field = sd->field_list + idx;
363              /* ignore the fields already disabled
364               * valid formats, means already ignore. */
365              if (field->location == -1) continue;
366              if (idx < ELM_DATETIME_HOUR)
367                field->location -= time_fmt_count;
368              else
369                field->location += ELM_DATETIME_HOUR;
370           }
371      }
372    //
373
374    // return the number of valid fields parsed.
375    return location;
376 }
377
378 // TIZEN_ONLY(20151218): Support Mobile UX
379 static void
380 _notify_format_change(Evas_Object *obj)
381 {
382    Datetime_Field *field;
383    unsigned int idx = 0;
384
385    ELM_DATETIME_DATA_GET(obj, sd);
386
387    if (!dt_mod || !dt_mod->field_format_changed) return;
388
389    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
390      {
391         field = sd->field_list + idx;
392         if (field->item_obj)
393           dt_mod->field_format_changed(sd->mod_data, field->item_obj);
394      }
395 }
396 //
397
398 static void
399 _reload_format(Evas_Object *obj)
400 {
401    unsigned int idx, field_count;
402    Datetime_Field *field;
403    char buf[BUFFER_SIZE];
404    char *dt_fmt;
405
406    ELM_DATETIME_DATA_GET(obj, sd);
407    ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd);
408
409    // FIXME: provide nl_langinfo on Windows if possible
410    // fetch the default format from Libc.
411    if (!sd->user_format)
412 #if defined(HAVE_LANGINFO_H) || defined (HAVE_EVIL)
413      strncpy(sd->format, nl_langinfo(D_T_FMT), ELM_DATETIME_MAX_FORMAT_LEN);
414 #else
415      strncpy(sd->format, "", ELM_DATETIME_MAX_FORMAT_LEN);
416 #endif
417    sd->format[ELM_DATETIME_MAX_FORMAT_LEN - 1] = '\0';
418
419    dt_fmt = (char *)malloc(ELM_DATETIME_MAX_FORMAT_LEN);
420    if (!dt_fmt) return;
421
422    strncpy(dt_fmt, sd->format, ELM_DATETIME_MAX_FORMAT_LEN);
423
424    _expand_format(dt_fmt);
425
426    // reset all the fields to disable state
427    sd->enabled_field_count = 0;
428    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
429      {
430         field = sd->field_list + idx;
431         field->fmt_exist = EINA_FALSE;
432         field->location = -1;
433      }
434
435    field_count = _parse_format(obj, dt_fmt);
436    free(dt_fmt);
437
438    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
439      {
440         field = sd->field_list + idx;
441         if (field->fmt_exist && field->visible)
442           sd->enabled_field_count++;
443      }
444
445    // assign locations to disabled fields for uniform usage
446    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
447      {
448         field = sd->field_list + idx;
449         if (field->location == -1) field->location = field_count++;
450
451         if (field->fmt_exist && field->visible)
452           {
453              snprintf(buf, sizeof(buf), EDC_PART_FIELD_ENABLE_SIG_STR,
454                       field->location);
455              elm_layout_signal_emit(obj, buf, "elm");
456           }
457         else
458           {
459              snprintf(buf, sizeof(buf), EDC_PART_FIELD_DISABLE_SIG_STR,
460                       field->location);
461              elm_layout_signal_emit(obj, buf, "elm");
462           }
463         snprintf
464           (buf, sizeof(buf), EDC_PART_SEPARATOR_STR, (field->location + 1));
465         elm_layout_text_set(obj, buf, field->separator);
466      }
467
468    edje_object_message_signal_process(wd->resize_obj);
469    _field_list_arrange(obj);
470
471    //TIZEN_ONLY(20151218): Support Mobile UX
472    _notify_format_change(obj);
473    //
474 }
475
476 EOLIAN static Eina_Bool
477 _elm_datetime_elm_widget_translate(Eo *obj, Elm_Datetime_Data *sd)
478 {
479    if (!sd->user_format) _reload_format(obj);
480    else _field_list_display(obj);
481
482    eo_do_super(obj, MY_CLASS, elm_obj_widget_translate());
483
484    return EINA_TRUE;
485 }
486
487 static Eina_List *
488 _datetime_items_get(const Evas_Object *obj)
489 {
490    Eina_List *items = NULL;
491    Datetime_Field *field;
492    unsigned int idx;
493    //TIZEN_ONLY(20170818): Prevent Null pointer ref.
494    Datetime_Field *sorted_fields[ELM_DATETIME_TYPE_COUNT] = {0};
495    //Datetime_Field *sorted_fields[ELM_DATETIME_TYPE_COUNT];
496    //
497
498    ELM_DATETIME_DATA_GET(obj, sd);
499
500    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
501      {
502         field = sd->field_list + idx;
503         sorted_fields[field->location] = field;
504      }
505
506    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
507      {
508         field = sorted_fields[idx];
509         //TIZEN_ONLY(20170818): Prevent Null pointer ref.
510         if (!field) continue;
511         //
512         if (field->fmt_exist && field->visible)
513           items = eina_list_append(items, field->item_obj);
514      }
515
516    // ACCESS
517    if (_elm_config->access_mode == ELM_ACCESS_MODE_ON)
518      items = eina_list_append(items, sd->access_obj);
519
520    return items;
521 }
522
523 EOLIAN static Eina_Bool
524 _elm_datetime_elm_widget_focus_next_manager_is(Eo *obj EINA_UNUSED, Elm_Datetime_Data *_pd EINA_UNUSED)
525 {
526    return EINA_TRUE;
527 }
528
529 EOLIAN static Eina_Bool
530 _elm_datetime_elm_widget_focus_next(Eo *obj, Elm_Datetime_Data *_pd EINA_UNUSED, Elm_Focus_Direction dir, Evas_Object **next, Elm_Object_Item **next_item)
531 {
532    const Eina_List *items;
533    Eina_List *(*list_free)(Eina_List *list);
534    void *(*list_data_get)(const Eina_List *list);
535
536    Eina_Bool int_ret;
537
538    if ((items = elm_widget_focus_custom_chain_get(obj)))
539      {
540         list_data_get = eina_list_data_get;
541         list_free = NULL;
542      }
543    else
544      {
545         items = _datetime_items_get(obj);
546         list_data_get = eina_list_data_get;
547         list_free = eina_list_free;
548         if (!items) return EINA_FALSE;
549      }
550
551    int_ret = elm_widget_focus_list_next_get(obj, items, list_data_get, dir, next, next_item);
552    if (list_free) list_free((Eina_List *)items);
553
554    return int_ret;
555 }
556
557 EOLIAN static Eina_Bool
558 _elm_datetime_elm_widget_on_focus(Eo *obj, Elm_Datetime_Data *sd, Elm_Object_Item *item EINA_UNUSED)
559 {
560    Eina_Bool int_ret = EINA_FALSE;
561
562    eo_do_super(obj, MY_CLASS, int_ret = elm_obj_widget_on_focus(NULL));
563    if (!int_ret) return EINA_FALSE;
564
565    if (!elm_widget_focus_get(obj))
566      {
567         if ((dt_mod) && (dt_mod->obj_hide))
568           dt_mod->obj_hide(sd->mod_data);
569      }
570
571    return EINA_TRUE;
572 }
573
574 EOLIAN static Eina_Bool
575 _elm_datetime_elm_widget_disable(Eo *obj, Elm_Datetime_Data *sd)
576 {
577    Datetime_Field *field;
578    unsigned int idx = 0;
579    Eina_Bool int_ret = EINA_FALSE;
580
581    eo_do_super(obj, MY_CLASS, int_ret = elm_obj_widget_disable());
582    if (!int_ret) return EINA_FALSE;
583
584    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
585      {
586         field = sd->field_list + idx;
587         elm_object_disabled_set(field->item_obj, elm_object_disabled_get(obj));
588      }
589    return EINA_TRUE;
590 }
591
592 EOLIAN static void
593 _elm_datetime_elm_layout_sizing_eval(Eo *obj, Elm_Datetime_Data *sd)
594 {
595    Evas_Coord minw = -1, minh = -1;
596
597    if (sd->freeze_sizing) return;
598
599    ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd);
600
601    if (sd->enabled_field_count)
602      elm_coords_finger_size_adjust(sd->enabled_field_count, &minw, 1, &minh);
603
604    edje_object_size_min_restricted_calc
605      (wd->resize_obj, &minw, &minh, minw, minh);
606    evas_object_size_hint_min_set(obj, minw, minh);
607    evas_object_size_hint_max_set(obj, -1, -1);
608 }
609
610 EOLIAN static Elm_Theme_Apply
611 _elm_datetime_elm_widget_theme_apply(Eo *obj, Elm_Datetime_Data *sd)
612 {
613    Elm_Theme_Apply int_ret = ELM_THEME_APPLY_FAILED;
614
615    Datetime_Field *field;
616    char buf[BUFFER_SIZE];
617    unsigned int idx;
618
619    ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd, ELM_THEME_APPLY_FAILED);
620
621    eo_do_super(obj, MY_CLASS, int_ret = elm_obj_widget_theme_apply());
622    if (!int_ret) return ELM_THEME_APPLY_FAILED;
623
624    if ((!dt_mod) || (!dt_mod->field_value_display)) return int_ret;
625
626    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
627      {
628         field = sd->field_list + idx;
629         if (field->fmt_exist && field->visible)
630           {
631              snprintf(buf, sizeof(buf), EDC_PART_FIELD_ENABLE_SIG_STR,
632                       field->location);
633              elm_layout_signal_emit(obj, buf, "elm");
634
635              snprintf
636                (buf, sizeof(buf), EDC_PART_SEPARATOR_STR, field->location);
637              elm_layout_text_set(obj, buf, field->separator);
638
639              dt_mod->field_value_display(sd->mod_data, field->item_obj);
640           }
641         else
642           {
643              snprintf(buf, sizeof(buf), EDC_PART_FIELD_DISABLE_SIG_STR,
644                       field->location);
645              elm_layout_signal_emit(obj, buf, "elm");
646           }
647      }
648
649    edje_object_message_signal_process(wd->resize_obj);
650    elm_layout_sizing_eval(obj);
651
652    return int_ret;
653 }
654
655 //TIZEN_ONLY(20170807): Apply UI Mirroring for Tizen 4.0 UX
656 EOLIAN static Eina_Bool
657 _elm_datetime_elm_widget_mirrored_get(Eo *obj EINA_UNUSED, Elm_Datetime_Data *sd EINA_UNUSED)
658 {
659    return EINA_FALSE;
660 }
661
662 EOLIAN static void
663 _elm_datetime_elm_widget_mirrored_set(Eo *obj EINA_UNUSED, Elm_Datetime_Data *sd EINA_UNUSED, Eina_Bool mirrored EINA_UNUSED)
664 {
665    return;
666 }
667 //
668
669 static int
670 _max_days_get(int year,
671               int month)
672 {
673    struct tm time1;
674    time_t t;
675    int day;
676
677    t = time(NULL);
678    localtime_r(&t, &time1);
679    time1.tm_year = year;
680    time1.tm_mon = month;
681    for (day = MIN_DAYS_IN_MONTH; day <= mapping[ELM_DATETIME_DATE].def_max;
682         day++)
683      {
684         time1.tm_mday = day;
685         mktime(&time1);
686         /* To restrict month wrapping because of summer time in some locales,
687         * ignore day light saving mode in mktime(). */
688         time1.tm_isdst = -1;
689         if (time1.tm_mday == 1) break;
690      }
691    day--;
692
693    return day;
694 }
695
696 static Eina_Bool
697 _date_cmp(const struct tm *time1,
698           const struct tm *time2)
699 {
700    unsigned int idx;
701
702    const DATETIME_TM_ARRAY(timearr1, time1);
703    const DATETIME_TM_ARRAY(timearr2, time2);
704
705    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
706      {
707         if (*timearr1[idx] != *timearr2[idx])
708           return EINA_FALSE;
709      }
710
711    return EINA_TRUE;
712 }
713
714 static Eina_Bool
715 _field_cmp(Elm_Datetime_Field_Type field_type,
716           struct tm *time1,
717           struct tm *time2)
718 {
719    DATETIME_TM_ARRAY(timearr1, time1);
720    DATETIME_TM_ARRAY(timearr2, time2);
721
722    if (*timearr1[field_type] != *timearr2[field_type])
723      return EINA_FALSE;
724    else
725      return EINA_TRUE;
726 }
727
728 // validates curr_time/min_limt/max_limit according to the newly set value
729 static void
730 _validate_datetime_limits(struct tm *time1,
731                           struct tm *time2,
732                           Eina_Bool swap)
733 {
734    struct tm *t1, *t2;
735    unsigned int idx;
736
737    if (!time1 || !time2) return;
738
739    t1 = (swap) ? time2 : time1;
740    t2 = (swap) ? time1 : time2;
741
742    DATETIME_TM_ARRAY(timearr1, time1);
743    DATETIME_TM_ARRAY(timearr2, time2);
744    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT - 1; idx++)
745      {
746         if (*timearr1[idx] < *timearr2[idx])
747           {
748              memcpy(t1, t2, sizeof(struct tm));
749              break;
750           }
751         else if (*timearr1[idx] > *timearr2[idx])
752           break;
753      }
754 }
755
756 static void
757 _apply_field_limits(Evas_Object *obj)
758 {
759    Datetime_Field *field;
760    unsigned int idx = 0;
761    int val;
762
763    ELM_DATETIME_DATA_GET(obj, sd);
764
765    DATETIME_TM_ARRAY(timearr, &sd->curr_time);
766    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT - 1; idx++)
767      {
768         field = sd->field_list + idx;
769         val = *timearr[idx];
770         if (val < field->min)
771           *timearr[idx] = field->min;
772         else if (val > field->max)
773           *timearr[idx] = field->max;
774      }
775
776    _field_list_display(obj);
777 }
778
779 static void
780 _apply_range_restrictions(struct tm *tim)
781 {
782    unsigned int idx;
783    int val, min, max;
784
785    if (!tim) return;
786
787    DATETIME_TM_ARRAY(timearr, tim);
788    for (idx = ELM_DATETIME_MONTH; idx < ELM_DATETIME_TYPE_COUNT - 1; idx++)
789      {
790         val = *timearr[idx];
791         min = mapping[idx].def_min;
792         max = mapping[idx].def_max;
793         if (idx == ELM_DATETIME_DATE)
794           max = _max_days_get(tim->tm_year, tim->tm_mon);
795         if (val < min)
796           *timearr[idx] = min;
797         else if (val > max)
798           *timearr[idx] = max;
799      }
800 }
801
802 static const char *
803 _field_format_get(Evas_Object *obj,
804                   Elm_Datetime_Field_Type field_type)
805 {
806    Datetime_Field *field;
807
808    if (field_type > ELM_DATETIME_AMPM) return NULL;
809
810    ELM_DATETIME_DATA_GET(obj, sd);
811
812    field = sd->field_list + field_type;
813
814    return field->fmt;
815 }
816
817 static void
818 _field_limit_get(Evas_Object *obj,
819                  Elm_Datetime_Field_Type field_type,
820                  int *range_min,
821                  int *range_max)
822 {
823    int min, max, max_days;
824    Datetime_Field *field;
825    unsigned int idx;
826
827    if (field_type > ELM_DATETIME_MINUTE) return;
828
829    ELM_DATETIME_DATA_GET(obj, sd);
830
831    field = sd->field_list + field_type;
832
833    min = field->min;
834    max = field->max;
835
836    DATETIME_TM_ARRAY(curr_timearr, &sd->curr_time);
837    DATETIME_TM_ARRAY(min_timearr, &sd->min_limit);
838    DATETIME_TM_ARRAY(max_timearr, &sd->max_limit);
839
840    for (idx = 0; idx < field->type; idx++)
841      if (*curr_timearr[idx] > *min_timearr[idx]) break;
842    if ((idx == field_type) && (min < *min_timearr[field_type]))
843      min = *min_timearr[field_type];
844    if (field_type == ELM_DATETIME_DATE)
845      {
846         max_days = _max_days_get(sd->curr_time.tm_year, sd->curr_time.tm_mon);
847         if (max > max_days) max = max_days;
848      }
849    for (idx = 0; idx < field->type; idx++)
850      if (*curr_timearr[idx] < *max_timearr[idx]) break;
851    if ((idx == field_type) && (max > *max_timearr[field_type]))
852      max = *max_timearr[field_type];
853
854    *range_min = min;
855    *range_max = max;
856 }
857
858 //TIZEN_ONLY(20151218): Support Mobile UX
859 static Eina_Bool
860 _field_location_get(Evas_Object *obj, Elm_Datetime_Field_Type field_type,
861                     int *loc)
862 {
863    Datetime_Field *field;
864
865    ELM_DATETIME_DATA_GET(obj, sd);
866
867    field = sd->field_list + field_type;
868    if (!field) return EINA_FALSE;
869
870    if (loc) *loc = field->location;
871
872    return (field->fmt_exist && field->visible);
873 }
874
875 static Eina_List *
876 _fields_sorted_get(Evas_Object *obj)
877 {
878    Eina_List *items = NULL;
879    Datetime_Field *field;
880    unsigned int idx;
881    Datetime_Field *sorted_fields[ELM_DATETIME_TYPE_COUNT];
882
883    ELM_DATETIME_DATA_GET(obj, sd);
884
885    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
886      {
887         field = sd->field_list + idx;
888         sorted_fields[field->location] = field;
889      }
890
891    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
892      {
893         field = sorted_fields[idx];
894         if (field->fmt_exist && field->visible)
895           items = eina_list_append(items, field->item_obj);
896      }
897
898    return items;
899 }
900 //
901
902 static void
903 _field_list_init(Evas_Object *obj)
904 {
905    Datetime_Field *field;
906    unsigned int idx;
907    time_t t;
908
909    ELM_DATETIME_DATA_GET(obj, sd);
910
911    t = time(NULL);
912    localtime_r(&t, &sd->curr_time);
913
914    mapping[ELM_DATETIME_YEAR].def_min = _elm_config->year_min;
915    mapping[ELM_DATETIME_YEAR].def_max = _elm_config->year_max;
916    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
917      {
918         field = sd->field_list + idx;
919         field->type = ELM_DATETIME_YEAR + idx;
920         field->fmt[0] = '%';
921         field->fmt_exist = EINA_FALSE;
922         field->visible = EINA_TRUE;
923         field->min = mapping[idx].def_min;
924         field->max = mapping[idx].def_max;
925      }
926    DATETIME_TM_ARRAY(min_timearr, &sd->min_limit);
927    DATETIME_TM_ARRAY(max_timearr, &sd->max_limit);
928    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT - 1; idx++)
929      {
930         *min_timearr[idx] = mapping[idx].def_min;
931         *max_timearr[idx] = mapping[idx].def_max;
932      }
933 }
934
935 static char *
936 _access_info_cb(void *data, Evas_Object *obj EINA_UNUSED)
937 {
938    char *ret;
939    Eina_Strbuf *buf;
940    buf = eina_strbuf_new();
941
942    ELM_DATETIME_DATA_GET(data, sd);
943    eina_strbuf_append_printf(buf,
944                              "%d year, %d month, %d date, %d hour, %d minute",
945                              sd->curr_time.tm_year, sd->curr_time.tm_mon + 1,
946                              sd->curr_time.tm_mday, sd->curr_time.tm_hour,
947                              sd->curr_time.tm_min);
948
949    ret = eina_strbuf_string_steal(buf);
950    eina_strbuf_free(buf);
951    return ret;
952 }
953
954 EOLIAN static void
955 _elm_datetime_evas_object_smart_add(Eo *obj, Elm_Datetime_Data *priv)
956 {
957    Datetime_Field *field;
958    int idx;
959
960    eo_do_super(obj, MY_CLASS, evas_obj_smart_add());
961    elm_widget_sub_object_parent_add(obj);
962
963    /* TIZEN_ONLY(20180111): elm_widget_can_focus_set before module init for support TV UX */
964    elm_widget_can_focus_set(obj, EINA_TRUE);
965    //
966
967    // module - initialise module for datetime
968    if (!dt_mod) dt_mod = _dt_mod_init();
969    if (dt_mod)
970      {
971         if (dt_mod->obj_hook)
972           {
973              priv->mod_data = dt_mod->obj_hook(obj);
974
975              // update module data
976              if (priv->mod_data)
977                {
978                   priv->mod_data->base = obj;
979                   priv->mod_data->field_limit_get = _field_limit_get;
980                   priv->mod_data->field_format_get = _field_format_get;
981                   //TIZEN_ONLY(20151218): Support Mobile UX
982                   priv->mod_data->field_location_get = _field_location_get;
983                   priv->mod_data->fields_sorted_get = _fields_sorted_get;
984                   //
985                }
986           }
987
988         if (dt_mod->field_create)
989           {
990              for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
991                {
992                   field = priv->field_list + idx;
993                   field->item_obj = dt_mod->field_create(priv->mod_data, idx);
994                   /* TIZEN_ONLY(20161031): apply color_class parent-child relationship to all widgets */
995                   _elm_widget_color_class_parent_set(field->item_obj, obj);
996                   /* END */
997                }
998           }
999      }
1000
1001    priv->freeze_sizing = EINA_TRUE;
1002    if (!elm_layout_theme_set(obj, "datetime", "base",
1003                              elm_widget_style_get(obj)))
1004      CRI("Failed to set layout!");
1005
1006    _field_list_init(obj);
1007    _reload_format(obj);
1008
1009    /* TIZEN_ONLY(20180111): elm_widget_can_focus_set before module init for support TV UX */
1010    //elm_widget_can_focus_set(obj, EINA_TRUE);
1011    //
1012
1013    priv->freeze_sizing = EINA_FALSE;
1014    elm_layout_sizing_eval(obj);
1015
1016    // ACCESS
1017    if (_elm_config->access_mode == ELM_ACCESS_MODE_ON)
1018      {
1019         Elm_Access_Info *ai;
1020
1021         priv->access_obj = _elm_access_edje_object_part_object_register
1022           (obj, elm_layout_edje_get(obj), "elm.access");
1023         if (!priv->access_obj)
1024           priv->access_obj = _elm_access_edje_object_part_object_register
1025           (obj, elm_layout_edje_get(obj), "access");
1026
1027         ai = _elm_access_info_get(priv->access_obj);
1028         _elm_access_text_set(ai, ELM_ACCESS_TYPE, "date time");
1029         _elm_access_callback_set(ai, ELM_ACCESS_INFO, _access_info_cb, obj);
1030      }
1031 }
1032
1033 EOLIAN static void
1034 _elm_datetime_evas_object_smart_del(Eo *obj, Elm_Datetime_Data *sd)
1035 {
1036    Datetime_Field *tmp;
1037    unsigned int idx;
1038
1039    for (idx = 0; idx < ELM_DATETIME_TYPE_COUNT; idx++)
1040      {
1041         tmp = sd->field_list + idx;
1042         evas_object_del(tmp->item_obj);
1043         eina_stringshare_del(tmp->separator);
1044      }
1045
1046    if ((dt_mod) && (dt_mod->obj_unhook))
1047      dt_mod->obj_unhook(sd->mod_data);  // module - unhook
1048
1049    eo_do_super(obj, MY_CLASS, evas_obj_smart_del());
1050 }
1051
1052 EAPI Evas_Object *
1053 elm_datetime_add(Evas_Object *parent)
1054 {
1055    EINA_SAFETY_ON_NULL_RETURN_VAL(parent, NULL);
1056    Evas_Object *obj = eo_add(MY_CLASS, parent);
1057    return obj;
1058 }
1059
1060 EOLIAN static Eo *
1061 _elm_datetime_eo_base_constructor(Eo *obj, Elm_Datetime_Data *_pd EINA_UNUSED)
1062 {
1063    obj = eo_do_super_ret(obj, MY_CLASS, obj, eo_constructor());
1064    eo_do(obj,
1065          evas_obj_type_set(MY_CLASS_NAME_LEGACY),
1066          evas_obj_smart_callbacks_descriptions_set(_smart_callbacks),
1067          elm_interface_atspi_accessible_role_set(ELM_ATSPI_ROLE_DATE_EDITOR));
1068
1069    return obj;
1070 }
1071
1072 EOLIAN static const char*
1073 _elm_datetime_format_get(Eo *obj EINA_UNUSED, Elm_Datetime_Data *sd)
1074 {
1075    return sd->format;
1076 }
1077
1078 EOLIAN static void
1079 _elm_datetime_format_set(Eo *obj, Elm_Datetime_Data *sd, const char *fmt)
1080 {
1081    if (fmt)
1082      {
1083         strncpy(sd->format, fmt, ELM_DATETIME_MAX_FORMAT_LEN);
1084         sd->format[ELM_DATETIME_MAX_FORMAT_LEN - 1] = '\0';
1085         sd->user_format = EINA_TRUE;
1086      }
1087    else sd->user_format = EINA_FALSE;
1088
1089    _reload_format(obj);
1090 }
1091
1092 EOLIAN static Eina_Bool
1093 _elm_datetime_field_visible_get(const Eo *obj EINA_UNUSED, Elm_Datetime_Data *sd, Elm_Datetime_Field_Type fieldtype)
1094 {
1095    Datetime_Field *field;
1096
1097    if (fieldtype > ELM_DATETIME_AMPM) return EINA_FALSE;
1098
1099    field = sd->field_list + fieldtype;
1100
1101    return field->visible;
1102 }
1103
1104 EOLIAN static void
1105 _elm_datetime_field_visible_set(Eo *obj, Elm_Datetime_Data *sd, Elm_Datetime_Field_Type fieldtype, Eina_Bool visible)
1106 {
1107    char buf[BUFFER_SIZE];
1108    Datetime_Field *field;
1109
1110    if (fieldtype > ELM_DATETIME_AMPM) return;
1111
1112    field = sd->field_list + fieldtype;
1113    visible = !!visible;
1114    if (field->visible == visible) return;
1115
1116    field->visible = visible;
1117
1118    sd->freeze_sizing = EINA_TRUE;
1119    if (visible)
1120      {
1121         sd->enabled_field_count++;
1122
1123         if (!field->fmt_exist) return;
1124
1125         snprintf(buf, sizeof(buf), EDC_PART_FIELD_ENABLE_SIG_STR,
1126                  field->location);
1127         elm_layout_signal_emit(obj, buf, "elm");
1128
1129         ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd);
1130         edje_object_message_signal_process(wd->resize_obj);
1131
1132         snprintf(buf, sizeof(buf), EDC_PART_FIELD_STR, field->location);
1133         elm_layout_content_unset(obj, buf);
1134         elm_layout_content_set(obj, buf, field->item_obj);
1135      }
1136    else
1137      {
1138         sd->enabled_field_count--;
1139
1140         if (!field->fmt_exist) return;
1141
1142         snprintf(buf, sizeof(buf), EDC_PART_FIELD_DISABLE_SIG_STR,
1143                  field->location);
1144         elm_layout_signal_emit(obj, buf, "elm");
1145
1146         ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd);
1147         edje_object_message_signal_process(wd->resize_obj);
1148
1149         snprintf(buf, sizeof(buf), EDC_PART_FIELD_STR, field->location);
1150         evas_object_hide(elm_layout_content_unset(obj, buf));
1151      }
1152    sd->freeze_sizing = EINA_FALSE;
1153
1154    elm_layout_sizing_eval(obj);
1155
1156    if (!visible) return;
1157    if (!dt_mod || !dt_mod->field_value_display) return;
1158
1159    dt_mod->field_value_display(sd->mod_data, field->item_obj);
1160 }
1161
1162 EOLIAN static void
1163 _elm_datetime_field_limit_get(const Eo *obj EINA_UNUSED, Elm_Datetime_Data *sd, Elm_Datetime_Field_Type fieldtype, int *min, int *max)
1164 {
1165    Datetime_Field *field;
1166
1167    if (fieldtype >= ELM_DATETIME_AMPM) return;
1168
1169    field = sd->field_list + fieldtype;
1170    if (min) *min = field->min;
1171    if (max) *max = field->max;
1172 }
1173
1174 EOLIAN static void
1175 _elm_datetime_field_limit_set(Eo *obj, Elm_Datetime_Data *sd, Elm_Datetime_Field_Type fieldtype, int min, int max)
1176 {
1177    Datetime_Field *field;
1178    struct tm old_time;
1179
1180    if (fieldtype >= ELM_DATETIME_AMPM) return;
1181
1182    if (min > max) return;
1183
1184    old_time = sd->curr_time;
1185    field = sd->field_list + fieldtype;
1186    if (((min >= mapping[fieldtype].def_min) &&
1187         (min <= mapping[fieldtype].def_max)) ||
1188        (field->type == ELM_DATETIME_YEAR))
1189      field->min = min;
1190    if (((max >= mapping[fieldtype].def_min) &&
1191         (max <= mapping[fieldtype].def_max)) ||
1192        (field->type == ELM_DATETIME_YEAR))
1193      field->max = max;
1194
1195    _apply_field_limits(obj);
1196
1197    if (!_field_cmp(fieldtype, &old_time, &sd->curr_time))
1198      eo_do(obj, eo_event_callback_call(ELM_DATETIME_EVENT_CHANGED, NULL));
1199
1200 }
1201
1202 EOLIAN static Eina_Bool
1203 _elm_datetime_value_get(const Eo *obj EINA_UNUSED, Elm_Datetime_Data *sd, struct tm *currtime)
1204 {
1205    EINA_SAFETY_ON_NULL_RETURN_VAL(currtime, EINA_FALSE);
1206
1207    *currtime = sd->curr_time;
1208    return EINA_TRUE;
1209 }
1210
1211 EOLIAN static Eina_Bool
1212 _elm_datetime_value_set(Eo *obj, Elm_Datetime_Data *sd, const struct tm *newtime)
1213 {
1214    EINA_SAFETY_ON_NULL_RETURN_VAL(newtime, EINA_FALSE);
1215
1216    if (_date_cmp(&sd->curr_time, newtime)) return EINA_TRUE;
1217    sd->curr_time = *newtime;
1218    // apply default field restrictions for curr_time
1219    _apply_range_restrictions(&sd->curr_time);
1220    // validate the curr_time according to the min_limt and max_limt
1221    _validate_datetime_limits(&sd->curr_time, &sd->min_limit, EINA_FALSE);
1222    _validate_datetime_limits(&sd->max_limit, &sd->curr_time, EINA_TRUE);
1223    _apply_field_limits(obj);
1224
1225    eo_do(obj, eo_event_callback_call(ELM_DATETIME_EVENT_CHANGED, NULL));
1226
1227    return EINA_TRUE;
1228 }
1229
1230 EOLIAN static Eina_Bool
1231 _elm_datetime_value_min_get(const Eo *obj EINA_UNUSED, Elm_Datetime_Data *sd, struct tm *mintime)
1232 {
1233    EINA_SAFETY_ON_NULL_RETURN_VAL(mintime, EINA_FALSE);
1234
1235    *mintime = sd->min_limit;
1236    return EINA_TRUE;
1237 }
1238
1239 EOLIAN static Eina_Bool
1240 _elm_datetime_value_min_set(Eo *obj, Elm_Datetime_Data *sd, const struct tm *mintime)
1241 {
1242    struct tm old_time;
1243
1244    EINA_SAFETY_ON_NULL_RETURN_VAL(mintime, EINA_FALSE);
1245
1246    if (_date_cmp(&sd->min_limit, mintime)) return EINA_TRUE;
1247    sd->min_limit = *mintime;
1248    old_time = sd->curr_time;
1249    // apply default field restrictions for min_limit
1250    _apply_range_restrictions(&sd->min_limit);
1251    // validate curr_time and max_limt according to the min_limit
1252    _validate_datetime_limits(&sd->max_limit, &sd->min_limit, EINA_FALSE);
1253    _validate_datetime_limits(&sd->curr_time, &sd->min_limit, EINA_FALSE);
1254    _apply_field_limits(obj);
1255
1256    if (!_date_cmp(&old_time, &sd->curr_time))
1257      eo_do(obj, eo_event_callback_call(ELM_DATETIME_EVENT_CHANGED, NULL));
1258
1259    return EINA_TRUE;
1260 }
1261
1262 EOLIAN static Eina_Bool
1263 _elm_datetime_value_max_get(const Eo *obj EINA_UNUSED, Elm_Datetime_Data *sd, struct tm *maxtime)
1264 {
1265    EINA_SAFETY_ON_NULL_RETURN_VAL(maxtime, EINA_FALSE);
1266
1267    *maxtime = sd->max_limit;
1268    return EINA_TRUE;
1269 }
1270
1271 EOLIAN static Eina_Bool
1272 _elm_datetime_value_max_set(Eo *obj, Elm_Datetime_Data *sd, const struct tm *maxtime)
1273 {
1274    struct tm old_time;
1275
1276    EINA_SAFETY_ON_NULL_RETURN_VAL(maxtime, EINA_FALSE);
1277
1278    if (_date_cmp(&sd->max_limit, maxtime)) return EINA_TRUE;
1279    sd->max_limit = *maxtime;
1280    old_time = sd->curr_time;
1281    // apply default field restrictions for max_limit
1282    _apply_range_restrictions(&sd->max_limit);
1283    // validate curr_time and min_limt according to the max_limit
1284    _validate_datetime_limits(&sd->max_limit, &sd->min_limit, EINA_TRUE);
1285    _validate_datetime_limits(&sd->max_limit, &sd->curr_time, EINA_TRUE);
1286    _apply_field_limits(obj);
1287
1288    if (!_date_cmp(&old_time, &sd->curr_time))
1289      eo_do(obj, eo_event_callback_call(ELM_DATETIME_EVENT_CHANGED, NULL));
1290
1291    return EINA_TRUE;
1292 }
1293
1294 EOLIAN static void
1295 _elm_datetime_class_constructor(Eo_Class *klass)
1296 {
1297    evas_smart_legacy_type_register(MY_CLASS_NAME_LEGACY, klass);
1298 }
1299
1300 #include "elm_datetime.eo.c"