3c2ff39bfc1048ab714ac1fa575a3e67c367b4d3
[framework/uifw/elementary.git] / src / lib / elm_calendar.c
1 #ifdef HAVE_CONFIG_H
2 # include "elementary_config.h"
3 #endif
4
5 #ifdef HAVE_EVIL
6 # include <Evil.h>
7 #endif
8
9 #include <Elementary.h>
10 #include "elm_priv.h"
11
12 /**
13  * @defgroup Calendar
14  *
15  * A calendar is a widget that allows the user to select a date. It has
16  * support to adding check marks (holidays and checks by default). The calendar
17  * is displayed one month at a time.
18  *
19  * Weekday names and the function used to format month and year to
20  * be displayed can be set, giving more flexibility to this widget.
21  *
22  * Signals that you can add callbacks for are:
23  *
24  * changed - emitted when the user selects a day or changes the displayed
25  * month, what actually changes the selected day as well.
26  */
27
28 typedef enum _Day_Color // EINA_DEPRECATED
29 {
30   DAY_WEEKDAY = 0,
31   DAY_SATURDAY = 1,
32   DAY_SUNDAY = 2
33 } Day_Color;
34
35 typedef struct _Widget_Data Widget_Data;
36
37 struct _Widget_Data
38 {
39    Evas_Object *calendar;
40    Eina_List *marks;
41    double interval, first_interval;
42    int year_min, year_max, spin_speed;
43    int today_it, selected_it, first_day_it;
44    Ecore_Timer *spin, *update_timer;
45    char * (*format_func) (struct tm *stime);
46    const char *weekdays[7];
47    struct tm current_time, selected_time;
48    Day_Color day_color[42]; // EINA_DEPRECATED
49    Eina_Bool selection_enabled : 1;
50 };
51
52 struct _Elm_Calendar_Mark
53 {
54    Evas_Object *obj;
55    Eina_List *node;
56    struct tm mark_time;
57    const char *mark_type;
58    Elm_Calendar_Mark_Repeat repeat;
59 };
60
61 static const char *widtype = NULL;
62 static void _on_focus_hook(void *data, Evas_Object *obj);
63
64 static const char *_days_abbrev[] =
65 {
66    "Sun", "Mon", "Tue", "Wed",
67    "Thu", "Fri", "Sat"
68 };
69
70 static int _days_in_month[2][12] =
71 {
72    {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
73    {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
74 };
75
76 static Elm_Calendar_Mark *
77 _mark_new(Evas_Object *obj, const char *mark_type, struct tm *mark_time, Elm_Calendar_Mark_Repeat repeat)
78 {
79    Widget_Data *wd = elm_widget_data_get(obj);
80    Elm_Calendar_Mark *mark;
81
82    if (!wd) return NULL;
83    mark = calloc(1, sizeof(Elm_Calendar_Mark));
84    if (!mark) return NULL;
85    mark->obj = obj;
86    mark->mark_type = eina_stringshare_add(mark_type);
87    mark->mark_time = *mark_time;
88    mark->repeat = repeat;
89    return mark;
90 }
91
92 static inline void
93 _mark_free(Elm_Calendar_Mark *mark)
94 {
95    eina_stringshare_del(mark->mark_type);
96    free(mark);
97 }
98
99 static void
100 _sizing_eval(Evas_Object *obj)
101 {
102    Widget_Data *wd = elm_widget_data_get(obj);
103    Evas_Coord minw = -1, minh = -1;
104    if (!wd) return;
105    elm_coords_finger_size_adjust(8, &minw, 7, &minh);
106    edje_object_size_min_restricted_calc(wd->calendar, &minw, &minh, minw, minh);
107    evas_object_size_hint_min_set(obj, minw, minh);
108    evas_object_size_hint_max_set(obj, -1, -1);
109 }
110
111 static inline int
112 _maxdays_get(struct tm *time)
113 {
114    int month, year;
115
116    month = time->tm_mon;
117    year = time->tm_year + 1900;
118
119    return _days_in_month[((!(year % 4)) && 
120                           ((!(year % 400)) || 
121                               (year % 100)))]
122                         [month];
123 }
124
125 static inline void
126 _unselect(Widget_Data *wd, int selected)
127 {
128    char emission[32];
129    snprintf(emission, sizeof(emission), "cit_%i,unselected", selected);
130    edje_object_signal_emit(wd->calendar, emission, "elm");
131 }
132
133 static inline void
134 _select(Widget_Data *wd, int selected)
135 {
136    char emission[32];
137    snprintf(emission, sizeof(emission), "cit_%i,selected", selected);
138    edje_object_signal_emit(wd->calendar, emission, "elm");
139 }
140
141 static inline void
142 _not_today(Widget_Data *wd)
143 {
144    char emission[32];
145    snprintf(emission, sizeof(emission), "cit_%i,not_today", wd->today_it);
146    edje_object_signal_emit(wd->calendar, emission, "elm");
147    wd->today_it = -1;
148 }
149
150 static inline void
151 _today(Widget_Data *wd, int it)
152 {
153    char emission[32];
154    snprintf(emission, sizeof(emission), "cit_%i,today", it);
155    edje_object_signal_emit(wd->calendar, emission, "elm");
156    wd->today_it = it;
157 }
158
159 static char *
160 _format_month_year(struct tm *stime)
161 {
162    char buf[32];
163    if (!strftime(buf, sizeof(buf), "%B %Y", stime)) return NULL;
164    return strdup(buf);
165 }
166
167 static inline void
168 _cit_mark(Evas_Object *cal, int cit, const char *mtype)
169 {
170    char sign[64];
171    snprintf(sign, sizeof(sign), "cit_%i,%s", cit, mtype);
172    edje_object_signal_emit(cal, sign, "elm");
173 }
174
175 static inline int
176 _weekday_get(int first_week_day, int day)
177 {
178    return (day + first_week_day - 1) % 7;
179 }
180
181 // EINA_DEPRECATED
182 static void
183 _text_day_color_update(Widget_Data *wd, int pos)
184 {
185    char emission[32];
186   
187    switch (wd->day_color[pos])
188     {
189     case DAY_WEEKDAY:
190       snprintf(emission, sizeof(emission), "cit_%i,weekday", pos);
191       break;
192     case DAY_SATURDAY:
193       snprintf(emission, sizeof(emission), "cit_%i,saturday", pos);
194       break;
195     case DAY_SUNDAY:
196       snprintf(emission, sizeof(emission), "cit_%i,sunday", pos);
197       break;
198     default:
199       return;
200     }
201   
202    edje_object_signal_emit(wd->calendar, emission, "elm");
203 }
204
205 // EINA_DEPRECATED
206 static void
207 _text_day_color_set(Widget_Data *wd, Day_Color col, int pos)
208 {
209    if ((pos < 0) || (pos >= 42)) return;
210    if (wd->day_color[pos] == col) return;
211    wd->day_color[pos] = col;
212    _text_day_color_update(wd, pos);
213 }
214
215 static void
216 _populate(Evas_Object *obj)
217 {
218    int maxdays, day, mon, year, i;
219    Elm_Calendar_Mark *mark;
220    char part[12], day_s[3];
221    struct tm first_day;
222    Eina_List *l;
223    char *buf;
224    Eina_Bool last_row = EINA_TRUE;
225    Widget_Data *wd = elm_widget_data_get(obj);
226
227    if (!wd) return;
228
229    if (wd->today_it > 0) _not_today(wd);
230
231    maxdays = _maxdays_get(&wd->selected_time);
232    mon = wd->selected_time.tm_mon;
233    year = wd->selected_time.tm_year;
234
235    /* Set selected month */
236    buf = wd->format_func(&wd->selected_time);
237    if (buf)
238      {
239        edje_object_part_text_set(wd->calendar, "month_text", buf);
240        free(buf);
241      }
242    else
243      edje_object_part_text_set(wd->calendar, "month_text", "");
244
245    /* Set days */
246    day = 0;
247    first_day = wd->selected_time;
248    first_day.tm_mday = 1;
249    mktime(&first_day);
250
251    // Layout of the calendar is changed for removing the unfilled last row.
252    wd->first_day_it = first_day.tm_wday;
253   
254   if ((35 - wd->first_day_it) > (maxdays - 1)) last_row = EINA_FALSE;
255   
256   if (!last_row)
257     {
258       char emission[32];
259       
260       for (i = 0; i < 5; i++)
261         {
262           snprintf(emission, sizeof(emission), "cseph_%i,row_hide", i);
263           edje_object_signal_emit(wd->calendar, emission, "elm");
264         }
265       snprintf(emission, sizeof(emission), "cseph_%i,row_invisible", 5);
266       edje_object_signal_emit(wd->calendar, emission, "elm");
267       for (i = 0; i < 35; i++)
268         {
269           snprintf(emission, sizeof(emission), "cit_%i,cell_expanded", i);
270           edje_object_signal_emit(wd->calendar, emission, "elm");
271         }
272       for (i = 35; i < 42; i++)
273         {
274           snprintf(emission, sizeof(emission), "cit_%i,cell_invisible", i);
275           edje_object_signal_emit(wd->calendar, emission, "elm");
276         }
277     }
278   else
279     {
280       char emission[32];
281       
282       for (i = 0; i < 6; i++)
283         {
284           snprintf(emission, sizeof(emission), "cseph_%i,row_show", i);
285           edje_object_signal_emit(wd->calendar, emission, "elm");
286         }
287       for (i = 0; i < 42; i++)
288         {
289           snprintf(emission, sizeof(emission), "cit_%i,cell_default", i);
290           edje_object_signal_emit(wd->calendar, emission, "elm");
291         }
292     }
293   
294    for (i = 0; i < 42; i++)
295      {
296         _text_day_color_update(wd, i); // EINA_DEPRECATED
297         if ((!day) && (i == first_day.tm_wday)) day = 1;
298
299         if ((day == wd->current_time.tm_mday)
300               && (mon == wd->current_time.tm_mon)
301               && (year == wd->current_time.tm_year))
302           _today(wd, i);
303
304         if (day == wd->selected_time.tm_mday)
305           {
306              if ((wd->selected_it > -1) && (wd->selected_it != i))
307                _unselect(wd, wd->selected_it);
308
309              if (wd->selection_enabled) _select(wd, i);
310
311              wd->selected_it = i;
312           }
313
314         if ((day) && (day <= maxdays))
315           snprintf(day_s, sizeof(day_s), "%i", day++);
316         else
317           day_s[0] = 0;
318
319         snprintf(part, sizeof(part), "cit_%i.text", i);
320         edje_object_part_text_set(wd->calendar, part, day_s);
321         /* Clear previous marks */
322         _cit_mark(wd->calendar, i, "clear");
323      }
324
325    /* Set marks */
326    EINA_LIST_FOREACH(wd->marks, l, mark)
327      {
328         struct tm *mtime = &mark->mark_time;
329         int mon = wd->selected_time.tm_mon;
330         int year = wd->selected_time.tm_year;
331         int mday_it = mtime->tm_mday + wd->first_day_it - 1;
332
333         switch (mark->repeat)
334           {
335            case ELM_CALENDAR_UNIQUE:
336               if ((mtime->tm_mon == mon) && (mtime->tm_year == year))
337                 _cit_mark(wd->calendar, mday_it, mark->mark_type);
338               break;
339            case ELM_CALENDAR_DAILY:
340               if (((mtime->tm_year == year) && (mtime->tm_mon < mon)) ||
341                     (mtime->tm_year < year))
342                 day = 1;
343               else if ((mtime->tm_year == year) && (mtime->tm_mon == mon))
344                 day = mtime->tm_mday;
345               else
346                 break;
347               for (; day <= maxdays; day++)
348                 _cit_mark(wd->calendar, day + wd->first_day_it - 1,
349                       mark->mark_type);
350               break;
351            case ELM_CALENDAR_WEEKLY:
352               if (((mtime->tm_year == year) && (mtime->tm_mon < mon)) ||
353                     (mtime->tm_year < year))
354                 day = 1;
355               else if ((mtime->tm_year == year) && (mtime->tm_mon == mon))
356                 day = mtime->tm_mday;
357               else
358                 break;
359               for (; day <= maxdays; day++)
360                 if (mtime->tm_wday == _weekday_get(wd->first_day_it, day))
361                   _cit_mark(wd->calendar, day + wd->first_day_it - 1,
362                         mark->mark_type);
363               break;
364            case ELM_CALENDAR_MONTHLY:
365               if (((mtime->tm_year < year) ||
366                        ((mtime->tm_year == year) && (mtime->tm_mon <= mon))) &&
367                     (mtime->tm_mday <= maxdays))
368                 _cit_mark(wd->calendar, mday_it, mark->mark_type);
369               break;
370            case ELM_CALENDAR_ANNUALLY:
371               if ((mtime->tm_year <= year) && (mtime->tm_mon == mon) &&
372                     (mtime->tm_mday <= maxdays))
373                 _cit_mark(wd->calendar, mday_it, mark->mark_type);
374               break;
375           }
376      }
377 }
378
379 static void
380 _set_headers(Evas_Object *obj)
381 {
382    static char part[] = "ch_0.text";
383    int i;
384    Widget_Data *wd = elm_widget_data_get(obj);
385    if (!wd) return;
386
387    for (i = 0; i < 7; i++)
388      {
389         part[3] = i + '0';
390         edje_object_part_text_set(wd->calendar, part, wd->weekdays[i]);
391      }
392 }
393
394 static void
395 _del_hook(Evas_Object *obj)
396 {
397    int i;
398    Elm_Calendar_Mark *mark;
399    Widget_Data *wd = elm_widget_data_get(obj);
400
401    if (!wd) return;
402
403    if (wd->spin) ecore_timer_del(wd->spin);
404    if (wd->update_timer) ecore_timer_del(wd->update_timer);
405
406    if (wd->marks)
407      {
408        EINA_LIST_FREE(wd->marks, mark)
409          {
410            _mark_free(mark);
411          }
412      }
413
414    for (i = 0; i < 7; i++)
415      eina_stringshare_del(wd->weekdays[i]);
416
417    free(wd);
418 }
419
420 static void
421 _on_focus_hook(void *data __UNUSED__, Evas_Object *obj)
422 {
423    Widget_Data *wd = elm_widget_data_get(obj);
424    if (!wd) return;
425    if (elm_widget_focus_get(obj))
426      {
427        edje_object_signal_emit(wd->calendar, "elm,action,focus", "elm");
428        evas_object_focus_set(wd->calendar, EINA_TRUE);
429      }
430    else
431      {
432        edje_object_signal_emit(wd->calendar, "elm,action,unfocus", "elm");
433        evas_object_focus_set(wd->calendar, EINA_FALSE);
434      }
435 }
436
437 static void
438 _theme_hook(Evas_Object *obj)
439 {
440    Widget_Data *wd = elm_widget_data_get(obj);
441    if (!wd) return;
442    _elm_theme_object_set(obj, wd->calendar, "calendar", "base",
443                          elm_widget_style_get(obj));
444    _set_headers(obj);
445    _populate(obj);
446    edje_object_message_signal_process(wd->calendar);
447    edje_object_scale_set(wd->calendar,
448                          elm_widget_scale_get(obj) * _elm_config->scale);
449    _sizing_eval(obj);
450 }
451
452 static void
453 _signal_emit_hook(Evas_Object *obj, const char *emission, const char *source)
454 {
455    Widget_Data *wd = elm_widget_data_get(obj);
456    if (!wd) return;
457    edje_object_signal_emit(wd->calendar, emission, source);
458 }
459
460 static void
461 _signal_callback_add_hook(Evas_Object *obj, const char *emission, const char *source, void (*func_cb) (void *data, Evas_Object *o, const char *emission, const char *source), void *data)
462 {
463    Widget_Data *wd = elm_widget_data_get(obj);
464    if (!wd) return;
465    edje_object_signal_callback_add(wd->calendar, emission,
466                                    source, func_cb, data);
467 }
468
469 static void
470 _signal_callback_del_hook(Evas_Object *obj, const char *emission, const char *source, void (*func_cb) (void *data, Evas_Object *o, const char *emission, const char *source), void *data)
471 {
472    Widget_Data *wd = elm_widget_data_get(obj);
473    if (!wd) return;
474    edje_object_signal_callback_del_full(wd->calendar, emission, source, func_cb,
475                                         data);
476 }
477
478 /* Set correct tm_wday and tm_yday after other fields changes*/
479 static inline void
480 _fix_selected_time(Widget_Data *wd)
481 {
482    mktime(&wd->selected_time);
483 }
484
485 static Eina_Bool
486 _update_month(Evas_Object *obj, int delta)
487 {
488    struct tm time_check;
489    int maxdays;
490    Widget_Data *wd = elm_widget_data_get(obj);
491    if (!wd) return EINA_FALSE;
492
493    /* check if it's a valid time. for 32 bits, year greater than 2037 is not */
494    time_check = wd->selected_time;
495    time_check.tm_mon += delta;
496    if (mktime(&time_check) == -1)
497      return EINA_FALSE;
498
499    wd->selected_time.tm_mon += delta;
500    if (wd->selected_time.tm_mon < 0)
501      {
502         if (wd->selected_time.tm_year == wd->year_min)
503           {
504              wd->selected_time.tm_mon++;
505              return EINA_FALSE;
506           }
507         wd->selected_time.tm_mon = 11;
508         wd->selected_time.tm_year--;
509      }
510    else if (wd->selected_time.tm_mon > 11)
511      {
512         if (wd->selected_time.tm_year == wd->year_max)
513           {
514              wd->selected_time.tm_mon--;
515              return EINA_FALSE;
516           }
517         wd->selected_time.tm_mon = 0;
518         wd->selected_time.tm_year++;
519      }
520
521    maxdays = _maxdays_get(&wd->selected_time);
522    if (wd->selected_time.tm_mday > maxdays)
523      wd->selected_time.tm_mday = maxdays;
524
525    _fix_selected_time(wd);
526    evas_object_smart_callback_call(obj, "changed", NULL);
527
528    return EINA_TRUE;
529 }
530
531 static Eina_Bool
532 _spin_value(void *data)
533 {
534    Widget_Data *wd = elm_widget_data_get(data);
535    if (!wd) return ECORE_CALLBACK_CANCEL;
536    if (_update_month(data, wd->spin_speed)) _populate(data);
537    wd->interval = wd->interval / 1.05;
538    ecore_timer_interval_set(wd->spin, wd->interval);
539    return ECORE_CALLBACK_RENEW;
540 }
541
542 static void
543 _button_inc_start(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
544 {
545    Widget_Data *wd = elm_widget_data_get(data);
546    if (!wd) return;
547    wd->interval = wd->first_interval;
548    wd->spin_speed = 1;
549    if (wd->spin) ecore_timer_del(wd->spin);
550    wd->spin = ecore_timer_add(wd->interval, _spin_value, data);
551    _spin_value(data);
552 }
553
554 static void
555 _button_dec_start(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
556 {
557    Widget_Data *wd = elm_widget_data_get(data);
558    if (!wd) return;
559    wd->interval = wd->first_interval;
560    wd->spin_speed = -1;
561    if (wd->spin) ecore_timer_del(wd->spin);
562    wd->spin = ecore_timer_add(wd->interval, _spin_value, data);
563    _spin_value(data);
564 }
565
566 static void
567 _button_stop(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source __UNUSED__)
568 {
569    Widget_Data *wd = elm_widget_data_get(data);
570    if (!wd) return;
571    wd->interval = wd->first_interval;
572    if (wd->spin) ecore_timer_del(wd->spin);
573    wd->spin = NULL;
574 }
575
576 static int
577 _get_item_day(Evas_Object *obj, int selected_it)
578 {
579    int day;
580    Widget_Data *wd = elm_widget_data_get(obj);
581    if (!wd) return 0;
582
583    day = selected_it - wd->first_day_it + 1;
584    if ((day < 0) || (day > _maxdays_get(&wd->selected_time)))
585      return 0;
586
587    return day;
588 }
589
590 static void
591 _update_sel_it(Evas_Object *obj, int sel_it)
592 {
593    int day;
594    Widget_Data *wd = elm_widget_data_get(obj);
595    if ((!wd) || (!wd->selection_enabled))
596       return;
597
598    day = _get_item_day(obj, sel_it);
599    if (!day)
600      return;
601
602    _unselect(wd, wd->selected_it);
603
604    wd->selected_it = sel_it;
605    wd->selected_time.tm_mday = day;
606    _select(wd, wd->selected_it);
607    _fix_selected_time(wd);
608    evas_object_smart_callback_call(obj, "changed", NULL);
609 }
610
611 static void
612 _day_selected(void *data, Evas_Object *obj __UNUSED__, const char *emission __UNUSED__, const char *source)
613 {
614    int sel_it;
615    Widget_Data *wd = elm_widget_data_get(data);
616    if ((!wd) || (!wd->selection_enabled))
617       return;
618    sel_it = atoi(source);
619
620    _update_sel_it(data, sel_it);
621 }
622
623 static inline int
624 _time_to_next_day(struct tm *t)
625 {
626   return ((((24 - t->tm_hour) * 60) - t->tm_min) * 60) - t->tm_sec;
627 }
628
629 static Eina_Bool
630 _update_cur_date(void *data)
631 {
632    time_t current_time;
633    int t, day;
634    Widget_Data *wd = elm_widget_data_get(data);
635    if (!wd) return ECORE_CALLBACK_RENEW;
636
637    if (wd->today_it > 0) _not_today(wd);
638
639    current_time = time(NULL);
640    localtime_r(&current_time, &wd->current_time);
641    t = _time_to_next_day(&wd->current_time);
642    ecore_timer_interval_set(wd->update_timer, t);
643
644    if ((wd->current_time.tm_mon != wd->selected_time.tm_mon) ||
645          (wd->current_time.tm_year!= wd->selected_time.tm_year))
646      return ECORE_CALLBACK_RENEW;
647
648    day = wd->current_time.tm_mday + wd->first_day_it - 1;
649    _today(wd, day);
650
651    return ECORE_CALLBACK_RENEW;
652 }
653
654 static Eina_Bool
655 _event_hook(Evas_Object *obj, Evas_Object *src __UNUSED__, Evas_Callback_Type type, void *event_info)
656 {
657    if (type != EVAS_CALLBACK_KEY_DOWN) return EINA_FALSE;
658    Evas_Event_Key_Down *ev = event_info;
659    Widget_Data *wd = elm_widget_data_get(obj);
660
661    if (!wd) return EINA_FALSE;
662    if (elm_widget_disabled_get(obj)) return EINA_FALSE;
663    if (!wd->selection_enabled) return EINA_FALSE;
664
665    if ((!strcmp(ev->keyname, "Left")) ||
666        (!strcmp(ev->keyname, "KP_Left")))
667      {
668         _update_sel_it(obj, wd->selected_it-1);
669      }
670    else if ((!strcmp(ev->keyname, "Right")) ||
671             (!strcmp(ev->keyname, "KP_Right")))
672      {
673         _update_sel_it(obj, wd->selected_it+1);
674      }
675    else if ((!strcmp(ev->keyname, "Up"))  ||
676             (!strcmp(ev->keyname, "KP_Up")))
677      {
678         _update_sel_it(obj, wd->selected_it-7);
679      }
680    else if ((!strcmp(ev->keyname, "Down")) ||
681             (!strcmp(ev->keyname, "KP_Down")))
682      {
683         _update_sel_it(obj, wd->selected_it+7);
684      }
685    else if ((!strcmp(ev->keyname, "Prior")) ||
686             (!strcmp(ev->keyname, "KP_Prior")))
687      {
688         if (_update_month(obj, -1)) _populate(obj);
689      }
690    else if ((!strcmp(ev->keyname, "Next")) ||
691             (!strcmp(ev->keyname, "KP_Next")))
692      {
693         if (_update_month(obj, 1)) _populate(obj);
694      }
695    else return EINA_FALSE;
696
697    return EINA_TRUE;
698 }
699
700 /**
701  * Add a new calendar to the parent
702  *
703  * @param parent The parent object
704  * @return The new object or NULL if it cannot be created
705  *
706  * @ingroup Calendar
707  */
708 EAPI Evas_Object *
709 elm_calendar_add(Evas_Object *parent)
710 {
711    time_t current_time;
712    Evas_Object *obj;
713    Widget_Data *wd;
714    int i, t;
715    Evas *e;
716
717    EINA_SAFETY_ON_NULL_RETURN_VAL(parent, NULL);
718
719    wd = ELM_NEW(Widget_Data);
720    e = evas_object_evas_get(parent);
721    if (!e) return NULL;
722    obj = elm_widget_add(e);
723    ELM_SET_WIDTYPE(widtype, "calendar");
724    elm_widget_type_set(obj, "calendar");
725    elm_widget_sub_object_add(parent, obj);
726    elm_widget_on_focus_hook_set(obj, _on_focus_hook, NULL);
727    elm_widget_data_set(obj, wd);
728    elm_widget_del_hook_set(obj, _del_hook);
729    elm_widget_theme_hook_set(obj, _theme_hook);
730    elm_widget_signal_emit_hook_set(obj, _signal_emit_hook);
731    elm_widget_signal_callback_add_hook_set(obj, _signal_callback_add_hook);
732    elm_widget_signal_callback_del_hook_set(obj, _signal_callback_del_hook);
733    elm_widget_can_focus_set(obj, EINA_TRUE);
734    elm_widget_event_hook_set(obj, _event_hook);
735
736    wd->first_interval = 0.85;
737    wd->year_min = 2;
738    wd->year_max = -1;
739    wd->today_it = -1;
740    wd->selected_it = -1;
741    wd->first_day_it = -1;
742    wd->selection_enabled = EINA_TRUE;
743    wd->format_func = _format_month_year;
744    wd->marks = NULL;
745
746    wd->calendar = edje_object_add(e);
747    _elm_theme_object_set(obj, wd->calendar, "calendar", "base", "default");
748    elm_widget_resize_object_set(obj, wd->calendar);
749
750    edje_object_signal_callback_add(wd->calendar, "elm,action,increment,start",
751                                    "*", _button_inc_start, obj);
752    edje_object_signal_callback_add(wd->calendar, "elm,action,decrement,start",
753                                    "*", _button_dec_start, obj);
754    edje_object_signal_callback_add(wd->calendar, "elm,action,stop",
755                                    "*", _button_stop, obj);
756    edje_object_signal_callback_add(wd->calendar, "elm,action,selected",
757                                    "*", _day_selected, obj);
758
759    for (i = 0; i < 7; i++)
760      wd->weekdays[i] = eina_stringshare_add(_days_abbrev[i]);
761
762    current_time = time(NULL);
763    localtime_r(&current_time, &wd->selected_time);
764    wd->current_time = wd->selected_time;
765    t = _time_to_next_day(&wd->current_time);
766    wd->update_timer = ecore_timer_add(t, _update_cur_date, obj);
767
768    _set_headers(obj);
769    _populate(obj);
770    _sizing_eval(obj);
771    return obj;
772 }
773
774 /**
775  * Set weekdays names to display in the calendar.
776  *
777  * By default, the following abbreviations are displayed:
778  * "Sun, Mon, Tue, Wed, Thu, Fri, Sat"
779  * The first string should be related to Sunday, the second to Monday...
780  *
781  * The usage should be like this:
782  * @code
783  *   const char *weekdays[] =
784  *   {
785  *      "Sunday", "Monday", "Tuesday", "Wednesday",
786  *      "Thursday", "Friday", "Saturday"
787  *   };
788  *   elm_calendar_weekdays_names_set(calendar, weekdays);
789  * @endcode
790  *
791  * @param obj The calendar object
792  * @param weedays Array of seven strings to be used as weekday names.
793  * Warning: it must have 7 elements, or it will access invalid memory.
794  * The strings must be NULL terminated ('@\0').
795  *
796  * @ingroup Calendar
797  */
798 EAPI void
799 elm_calendar_weekdays_names_set(Evas_Object *obj, const char *weekdays[])
800 {
801    int i;
802    ELM_CHECK_WIDTYPE(obj, widtype);
803    Widget_Data *wd = elm_widget_data_get(obj);
804    if (!wd) return;
805
806    EINA_SAFETY_ON_NULL_RETURN(weekdays);
807
808    for (i = 0; i < 7; i++)
809      {
810         eina_stringshare_replace(&wd->weekdays[i], weekdays[i]);
811      }
812    _set_headers(obj);
813 }
814
815 /**
816  * Get weekdays names displayed in the calendar.
817  *
818  * By default, the following abbreviations are displayed:
819  * "Sun, Mon, Tue, Wed, Thu, Fri, Sat"
820  * The first string is related to Sunday, the second to Monday...
821  *
822  * @param obj The calendar object
823  * @return Array of seven strings to used as weekday names.
824  *
825  * @ingroup Calendar
826  */
827 EAPI const char **
828 elm_calendar_weekdays_names_get(const Evas_Object *obj)
829 {
830    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
831    Widget_Data *wd = elm_widget_data_get(obj);
832    if (!wd) return NULL;
833    return wd->weekdays;
834 }
835
836 /**
837  * Set the interval for the calendar
838  *
839  * The interval value is decreased while the user increments or decrements
840  * the calendar value. The next interval value is the previous interval / 1.05,
841  * so it speed up a bit. Default value is 0.85 seconds.
842  *
843  * @param obj The calendar object
844  * @param interval The interval value in seconds
845  *
846  * @ingroup Calendar
847  */
848 EAPI void
849 elm_calendar_interval_set(Evas_Object *obj, double interval)
850 {
851    ELM_CHECK_WIDTYPE(obj, widtype);
852    Widget_Data *wd = elm_widget_data_get(obj);
853    if (!wd) return;
854    wd->first_interval = interval;
855 }
856
857 /**
858  * Get the interval of the calendar
859  *
860  * The interval value is decreased while the user increments or decrements
861  * the calendar value. The next interval value is the previous interval / 1.05,
862  * so it speed up a bit. Default value is 0.85 seconds.
863  *
864  * @param obj The calendar object
865  * @return The value of the first interval in seconds
866  *
867  * @ingroup Calendar
868  */
869 EAPI double
870 elm_calendar_interval_get(const Evas_Object *obj)
871 {
872    ELM_CHECK_WIDTYPE(obj, widtype) 0.0;
873    Widget_Data *wd = elm_widget_data_get(obj);
874    if (!wd) return 0.0;
875    return wd->first_interval;
876 }
877
878 /**
879  * Set the minimum and maximum values for the year
880  *
881  * Maximum must be greater than minimum, except if you don't wan't to set
882  * maximum year.
883  * Default values are 1902 and -1.
884  *
885  * If the maximum year is a negative value, it will be limited depending of the
886  * platform architecture (2037 for 32 bits);
887  *
888  * @param obj The calendar object
889  * @param min The minimum year, greater than 1901;
890  * @param max The maximum year;
891  *
892  * @ingroup Calendar
893  */
894 EAPI void
895 elm_calendar_min_max_year_set(Evas_Object *obj, int min, int max)
896 {
897    ELM_CHECK_WIDTYPE(obj, widtype);
898    Widget_Data *wd = elm_widget_data_get(obj);
899    if (!wd) return;
900    min -= 1900;
901    max -= 1900;
902    if ((wd->year_min == min) && (wd->year_max == max)) return;
903    wd->year_min = min > 2 ? min : 2;
904    wd->year_max = max;
905    if (wd->selected_time.tm_year > wd->year_max)
906      wd->selected_time.tm_year = wd->year_max;
907    if (wd->selected_time.tm_year < wd->year_min)
908      wd->selected_time.tm_year = wd->year_min;
909    _fix_selected_time(wd);
910    _populate(obj);
911 }
912
913 /**
914  * Get the minimum and maximum values for the year
915  *
916  * Default values are 1902 and -1.
917  *
918  * If the maximum year is a negative value, it will be limited depending of the
919  * platform architecture (2037 for 32 bits);
920  *
921  * @param obj The calendar object
922  * @param min The minimum year
923  * @param max The maximum year
924  *
925  * @ingroup Calendar
926  */
927 EAPI void
928 elm_calendar_min_max_year_get(const Evas_Object *obj, int *min, int *max)
929 {
930    ELM_CHECK_WIDTYPE(obj, widtype);
931    Widget_Data *wd = elm_widget_data_get(obj);
932    if (!wd) return;
933    if (min) *min = wd->year_min + 1900;
934    if (max) *max = wd->year_max + 1900;
935 }
936
937 /**
938  * Enable or disable day selection
939  *
940  * Enabled by default. If disabled, the user can select months, but not days.
941  * It should be used if you won't need such selection for the widget usage.
942  *
943  * @param obj The calendar object
944  * @param enabled Boolean to enable (true) or disable (false) day selection
945  *
946  * @ingroup Calendar
947  */
948 EAPI void
949 elm_calendar_day_selection_enabled_set(Evas_Object *obj, Eina_Bool enabled)
950 {
951    ELM_CHECK_WIDTYPE(obj, widtype);
952    Widget_Data *wd = elm_widget_data_get(obj);
953    if (!wd) return;
954    wd->selection_enabled = enabled;
955    if (enabled)
956      _select(wd, wd->selected_it);
957    else
958      _unselect(wd, wd->selected_it);
959 }
960
961 /**
962  * Get day selection state
963  *
964  * Enabled by default. If disabled, the user can select months, but not days.
965  * It should be used if you won't need such selection for the widget usage.
966  *
967  * @param obj The calendar object
968  * @return True if day selection is enabled, or false otherwise. It will
969  * return false if it can't get widget data.
970  *
971  * @ingroup Calendar
972  */
973 EAPI Eina_Bool
974 elm_calendar_day_selection_enabled_get(const Evas_Object *obj)
975 {
976    ELM_CHECK_WIDTYPE(obj, widtype) EINA_FALSE;
977    Widget_Data *wd = elm_widget_data_get(obj);
978    if (!wd) return EINA_FALSE;
979    return wd->selection_enabled;
980 }
981
982 /**
983  * Set selected time
984  *
985  * Set the time selected, changing the displayed month if needed.
986  * Selected time changes when the user changes the month or select a day.
987  *
988  * @param obj The calendar object
989  * @param selected_time A tm struct to represent the selected date
990  *
991  * @ingroup Calendar
992  */
993 EAPI void
994 elm_calendar_selected_time_set(Evas_Object *obj, struct tm *selected_time)
995 {
996    ELM_CHECK_WIDTYPE(obj, widtype);
997    Widget_Data *wd = elm_widget_data_get(obj);
998    if (!wd) return;
999
1000    EINA_SAFETY_ON_NULL_RETURN(selected_time);
1001    wd->selected_time = *selected_time;
1002    _populate(obj);
1003    return;
1004 }
1005
1006 /**
1007  * Get selected time
1008  *
1009  * Get the time selected by the user.
1010  * Selected time changes when the user changes the month or select a day.
1011  *
1012  * @param obj The calendar object
1013  * @param selected_time A tm struct to represent the selected date
1014  * @return It will return false if it can't get widget data, or true otherwise
1015  *
1016  * @ingroup Calendar
1017  */
1018 EAPI Eina_Bool
1019 elm_calendar_selected_time_get(const Evas_Object *obj, struct tm *selected_time)
1020 {
1021    ELM_CHECK_WIDTYPE(obj, widtype) EINA_FALSE;
1022    Widget_Data *wd = elm_widget_data_get(obj);
1023    if (!wd) return EINA_FALSE;
1024    EINA_SAFETY_ON_NULL_RETURN_VAL(selected_time, EINA_FALSE);
1025    *selected_time = wd->selected_time;
1026    return EINA_TRUE;
1027 }
1028
1029 /**
1030  * Set a function to format the string that will be used to display
1031  * month - year
1032  *
1033  * By default it uses strftime with "%B %Y" format string.
1034  * It should allocate the memory that will be used by the string,
1035  * that will be freed by the widget after usage.
1036  * A pointer to the string and a pointer to the time struct will be provided.
1037  *
1038  * Example:
1039  * @code
1040  * static char *
1041  * _format_month_year(struct tm *stime)
1042  * {
1043  *    char buf[32];
1044  *    if (!strftime(buf, sizeof(buf), "%B %Y", stime)) return NULL;
1045  *    return strdup(buf);
1046  * }
1047  * elm_calendar_format_function_set(calendar, _format_month_year);
1048  * @endcode
1049  *
1050  * @param obj The calendar object
1051  * @param format_function Function to set the month-year string given
1052  * the selected date
1053  *
1054  * @ingroup Calendar
1055  */
1056 EAPI void
1057 elm_calendar_format_function_set(Evas_Object *obj, char * (*format_function) (struct tm *stime))
1058 {
1059    ELM_CHECK_WIDTYPE(obj, widtype);
1060    Widget_Data *wd = elm_widget_data_get(obj);
1061    if (!wd) return;
1062    wd->format_func = format_function;
1063 }
1064
1065 /**
1066  * Add a new mark to the calendar
1067  *
1068  * Add a mark that will be drawn in the calendar respecting the insertion time
1069  * and periodicity. It will emit the type as signal to the widget theme.
1070  * By default, it supports "holiday" and "checked", but it can be extended.
1071  *
1072  * It won't immediately update the calendar, drawing the marks. For this, call
1073  * elm_calendar_marks_draw().
1074  *
1075  * Example
1076  * @code
1077  * struct tm selected_time;
1078  * time_t current_time;
1079  *
1080  * current_time = time(NULL) + 5 * 84600;
1081  * localtime_r(&current_time, &selected_time);
1082  * elm_calendar_mark_add(cal, "holiday", selected_time, ELM_CALENDAR_ANNUALLY);
1083  *
1084  * current_time = time(NULL) + 1 * 84600;
1085  * localtime_r(&current_time, &selected_time);
1086  * elm_calendar_mark_add(cal, "checked", selected_time, ELM_CALENDAR_UNIQUE);
1087  *
1088  * elm_calendar_marks_draw(cal);
1089  * @endcode
1090  *
1091  * @param obj The calendar object
1092  * @param mark_type A string used to define the type of mark. It will be
1093  * emitted to the theme, that should display a related modification on these
1094  * days representation.
1095  * @param mark_time A time struct to represent the date of inclusion of the
1096  * mark. For marks that repeats it will just be displayed after the inclusion
1097  * date in the calendar.
1098  * @param repeat Repeat the event following this periodicity. Can be a unique
1099  * mark (that don't repeat), daily, weekly, monthly or annually.
1100  *
1101  * @return The created mark or NULL upon failure
1102  *
1103  * @ingroup Calendar
1104  */
1105 EAPI Elm_Calendar_Mark *
1106 elm_calendar_mark_add(Evas_Object *obj, const char *mark_type, struct tm *mark_time, Elm_Calendar_Mark_Repeat repeat)
1107 {
1108    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
1109    Widget_Data *wd = elm_widget_data_get(obj);
1110    Elm_Calendar_Mark *mark;
1111    if (!wd) return NULL;
1112
1113    mark = _mark_new(obj, mark_type, mark_time, repeat);
1114    wd->marks = eina_list_append(wd->marks, mark);
1115    mark->node = eina_list_last(wd->marks);
1116    return mark;
1117 }
1118
1119 /**
1120  * Delete mark from the calendar.
1121  *
1122  * @param mark The mark to delete
1123  *
1124  * @ingroup Calendar
1125  */
1126 EAPI void
1127 elm_calendar_mark_del(Elm_Calendar_Mark *mark)
1128 {
1129    Evas_Object *obj;
1130    Widget_Data *wd;
1131
1132    EINA_SAFETY_ON_NULL_RETURN(mark);
1133
1134    obj = mark->obj;
1135    wd = elm_widget_data_get(obj);
1136    if (!wd) return;
1137
1138    wd->marks = eina_list_remove_list(wd->marks, mark->node);
1139    _mark_free(mark);
1140 }
1141
1142 /**
1143  * Remove all the marks from the calendar
1144  *
1145  * @param obj The calendar object
1146  *
1147  * @ingroup Calendar
1148  */
1149 EAPI void
1150 elm_calendar_marks_clear(Evas_Object *obj)
1151 {
1152    ELM_CHECK_WIDTYPE(obj, widtype);
1153    Widget_Data *wd = elm_widget_data_get(obj);
1154    Elm_Calendar_Mark *mark;
1155
1156    if (!wd) return;
1157    EINA_LIST_FREE(wd->marks, mark)
1158       _mark_free(mark);
1159 }
1160
1161 /**
1162  * Returns a list of all the calendar marks.
1163  *
1164  * @param obj The calendar object
1165  * @return An Eina_List* of the calendar marks, or NULL on failure
1166  *
1167  * @ingroup Calendar
1168  */
1169 EAPI const Eina_List *
1170 elm_calendar_marks_get(const Evas_Object *obj)
1171 {
1172    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
1173    Widget_Data *wd = elm_widget_data_get(obj);
1174    if (!wd) return NULL;
1175    return wd->marks;
1176 }
1177
1178 /**
1179  * Draw calendar marks.
1180  *
1181  * Should be used after adding, removing or clearing marks.
1182  * It will go through the entire marks list updating the calendar
1183  * (not a cheap function). So if lots of marks will be added,
1184  * add all the marks and then call this function.
1185  *
1186  * When the month is changed marks will be drawed.
1187  *
1188  * @param obj The calendar object
1189  *
1190  * @ingroup Calendar
1191  */
1192 EAPI void
1193 elm_calendar_marks_draw(Evas_Object *obj)
1194 {
1195    ELM_CHECK_WIDTYPE(obj, widtype);
1196    Widget_Data *wd = elm_widget_data_get(obj);
1197    if (!wd) return;
1198    _populate(obj);
1199 }
1200
1201 /**
1202  * Set a text color to the saturday color.
1203  * 
1204  * Deprecated. use elm_calendar_mark_add() instead like:
1205  * 
1206  * @code
1207  * struct tm t = { 0, 0, 12, 6, 0, 0, 5, 5, -1 };
1208  * elm_calendar_mark_add(obj, "sat", &t, ELM_CALENDAR_WEEKLY);
1209  * @endcode
1210  * 
1211  * @param obj The calendar object
1212  * @param pos The text position
1213  *
1214  * @ingroup Calendar
1215  */
1216 EINA_DEPRECATED EAPI void
1217 elm_calendar_text_saturday_color_set(Evas_Object *obj, int pos)
1218 {
1219   ELM_CHECK_WIDTYPE(obj, widtype);
1220   Widget_Data *wd = elm_widget_data_get(obj);
1221   if (!wd) return;
1222   _text_day_color_set(wd, DAY_SATURDAY, pos);
1223 }
1224
1225 /**
1226  * Set a text color to the sunday color.
1227  *
1228  * Deprecated. use elm_calendar_mark_add() instead like:
1229  * 
1230  * @code
1231  * struct tm t = { 0, 0, 12, 7, 0, 0, 6, 6, -1 };
1232  * elm_calendar_mark_add(obj, "sun", &t, ELM_CALENDAR_WEEKLY);
1233  * @endcode
1234  * 
1235  * @param obj The calendar object
1236  * @param pos The text position
1237  *
1238  * @ingroup Calendar
1239  */
1240 EINA_DEPRECATED EAPI void
1241 elm_calendar_text_sunday_color_set(Evas_Object *obj, int pos)
1242 {
1243   ELM_CHECK_WIDTYPE(obj, widtype);
1244   Widget_Data *wd = elm_widget_data_get(obj);
1245   if (!wd) return;
1246   _text_day_color_set(wd, DAY_SUNDAY, pos);
1247 }
1248
1249 /**
1250  * Set a text color to the weekday color.
1251  *
1252  * Deprecated. use elm_calendar_mark_add() instead like:
1253  * 
1254  * @code
1255  * struct tm t = { 0, 0, 12, 1, 0, 0, 0, 0, -1 };
1256  * 
1257  * elm_calendar_mark_add(obj, "week", &t, ELM_CALENDAR_WEEKLY); // monday
1258  * t.tm_tm_mday++; t.tm_wday++; t.tm_yday++;
1259  * elm_calendar_mark_add(obj, "week", &t, ELM_CALENDAR_WEEKLY); // tuesday
1260  * t.tm_tm_mday++; t.tm_wday++; t.tm_yday++;
1261  * elm_calendar_mark_add(obj, "week", &t, ELM_CALENDAR_WEEKLY); // wednesday
1262  * t.tm_tm_mday++; t.tm_wday++; t.tm_yday++;
1263  * elm_calendar_mark_add(obj, "week", &t, ELM_CALENDAR_WEEKLY); // thursday
1264  * t.tm_tm_mday++; t.tm_wday++; t.tm_yday++;
1265  * elm_calendar_mark_add(obj, "week", &t, ELM_CALENDAR_WEEKLY); // friday
1266  * @endcode
1267  * 
1268  * @param obj The calendar object
1269  * @param pos The text position
1270  *
1271  * @ingroup Calendar
1272  */
1273 EINA_DEPRECATED EAPI void
1274 elm_calendar_text_weekday_color_set(Evas_Object *obj, int pos)
1275 {
1276   ELM_CHECK_WIDTYPE(obj, widtype);
1277   Widget_Data *wd = elm_widget_data_get(obj);
1278   if (!wd) return;
1279   _text_day_color_set(wd, DAY_WEEKDAY, pos);
1280 }