1 /**
2  * @file lv_calendar.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_calendar_private.h"
10 #include "../../draw/lv_draw_private.h"
11 #include "../../core/lv_obj_class_private.h"
12 #include "../../../lvgl.h"
13 #if LV_USE_CALENDAR
14 
15 #include "../../misc/lv_assert.h"
16 
17 /*********************
18  *      DEFINES
19  *********************/
20 #define LV_CALENDAR_CTRL_TODAY      LV_BUTTONMATRIX_CTRL_CUSTOM_1
21 #define LV_CALENDAR_CTRL_HIGHLIGHT  LV_BUTTONMATRIX_CTRL_CUSTOM_2
22 
23 #define MY_CLASS (&lv_calendar_class)
24 
25 /**********************
26  *      TYPEDEFS
27  **********************/
28 
29 /**********************
30  *  STATIC PROTOTYPES
31  **********************/
32 static void lv_calendar_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
33 static void draw_task_added_event_cb(lv_event_t * e);
34 
35 static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day);
36 static uint8_t get_month_length(int32_t year, int32_t month);
37 static uint8_t is_leap_year(uint32_t year);
38 static void highlight_update(lv_obj_t * calendar);
39 
40 #if LV_USE_CALENDAR_CHINESE
41 static lv_calendar_date_t gregorian_get_last_month_time(lv_calendar_date_t * time);
42 static lv_calendar_date_t gregorian_get_next_month_time(lv_calendar_date_t * time);
43 static void chinese_calendar_set_day_name(lv_obj_t * calendar, uint8_t index, uint8_t day,
44                                           lv_calendar_date_t * gregorian_time);
45 #endif
46 
47 /**********************
48  *  STATIC VARIABLES
49  **********************/
50 
51 const lv_obj_class_t lv_calendar_class = {
52     .constructor_cb = lv_calendar_constructor,
53     .width_def = (LV_DPI_DEF * 3) / 2,
54     .height_def = (LV_DPI_DEF * 3) / 2,
55     .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
56     .instance_size = sizeof(lv_calendar_t),
57     .base_class = &lv_obj_class,
58     .name = "calendar",
59 };
60 
61 static const char * day_names_def[7] = LV_CALENDAR_DEFAULT_DAY_NAMES;
62 
63 /**********************
64  *      MACROS
65  **********************/
66 
67 /**********************
68  *   GLOBAL FUNCTIONS
69  **********************/
70 
lv_calendar_create(lv_obj_t * parent)71 lv_obj_t * lv_calendar_create(lv_obj_t * parent)
72 {
73     LV_LOG_INFO("begin");
74     lv_obj_t * obj = lv_obj_class_create_obj(&lv_calendar_class, parent);
75     lv_obj_class_init_obj(obj);
76     return obj;
77 }
78 
79 /*=====================
80  * Setter functions
81  *====================*/
82 
lv_calendar_set_day_names(lv_obj_t * obj,const char * day_names[])83 void lv_calendar_set_day_names(lv_obj_t * obj, const char * day_names[])
84 {
85     LV_ASSERT_OBJ(obj, MY_CLASS);
86     lv_calendar_t * calendar = (lv_calendar_t *)obj;
87 
88     uint32_t i;
89     for(i = 0; i < 7; i++) {
90         calendar->map[i] = day_names[i];
91     }
92     lv_obj_invalidate(obj);
93 }
94 
lv_calendar_set_today_date(lv_obj_t * obj,uint32_t year,uint32_t month,uint32_t day)95 void lv_calendar_set_today_date(lv_obj_t * obj, uint32_t year, uint32_t month, uint32_t day)
96 {
97     LV_ASSERT_OBJ(obj, MY_CLASS);
98     lv_calendar_t * calendar = (lv_calendar_t *)obj;
99 
100     calendar->today.year         = year;
101     calendar->today.month        = month;
102     calendar->today.day          = day;
103 
104     highlight_update(obj);
105 }
106 
lv_calendar_set_highlighted_dates(lv_obj_t * obj,lv_calendar_date_t highlighted[],size_t date_num)107 void lv_calendar_set_highlighted_dates(lv_obj_t * obj, lv_calendar_date_t highlighted[], size_t date_num)
108 {
109     LV_ASSERT_NULL(highlighted);
110 
111     LV_ASSERT_OBJ(obj, MY_CLASS);
112     lv_calendar_t * calendar = (lv_calendar_t *)obj;
113 
114     calendar->highlighted_dates     = highlighted;
115     calendar->highlighted_dates_num = date_num;
116 
117     highlight_update(obj);
118 }
119 
lv_calendar_set_month_shown(lv_obj_t * obj,uint32_t year,uint32_t month)120 void lv_calendar_set_month_shown(lv_obj_t * obj, uint32_t year, uint32_t month)
121 {
122     LV_ASSERT_OBJ(obj, MY_CLASS);
123     lv_calendar_t * calendar = (lv_calendar_t *)obj;
124 
125     calendar->showed_date.year   = year;
126     calendar->showed_date.month  = month;
127     calendar->showed_date.day    = 1;
128 
129     lv_calendar_date_t d;
130     d.year = calendar->showed_date.year;
131     d.month = calendar->showed_date.month;
132     d.day = calendar->showed_date.day;
133 
134     uint32_t i;
135 
136     /*Remove the disabled state but revert it for day names*/
137     lv_buttonmatrix_clear_button_ctrl_all(calendar->btnm, LV_BUTTONMATRIX_CTRL_DISABLED);
138     for(i = 0; i < 7; i++) {
139         lv_buttonmatrix_set_button_ctrl(calendar->btnm, i, LV_BUTTONMATRIX_CTRL_DISABLED);
140     }
141 
142     uint8_t act_mo_len = get_month_length(d.year, d.month);
143     uint8_t day_first = get_day_of_week(d.year, d.month, 1);
144     uint8_t c;
145 
146 #if LV_USE_CALENDAR_CHINESE
147     lv_calendar_date_t gregorian_time;
148     gregorian_time = d;
149 #endif
150 
151     for(i = day_first, c = 1; i < act_mo_len + day_first; i++, c++) {
152 #if LV_USE_CALENDAR_CHINESE
153         if(calendar->use_chinese_calendar) {
154             gregorian_time.day = c;
155             chinese_calendar_set_day_name(obj, i, c, &gregorian_time);
156         }
157         else
158 #endif
159             lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c);
160     }
161 
162     uint8_t prev_mo_len = get_month_length(d.year, d.month - 1);
163 
164 #if LV_USE_CALENDAR_CHINESE
165     gregorian_time = gregorian_get_last_month_time(&d);
166 #endif
167 
168     for(i = 0, c = prev_mo_len - day_first + 1; i < day_first; i++, c++) {
169 #if LV_USE_CALENDAR_CHINESE
170         if(calendar->use_chinese_calendar) {
171             gregorian_time.day = c;
172             chinese_calendar_set_day_name(obj, i, c, &gregorian_time);
173         }
174         else
175 #endif
176             lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c);
177         lv_buttonmatrix_set_button_ctrl(calendar->btnm, i + 7, LV_BUTTONMATRIX_CTRL_DISABLED);
178     }
179 
180 #if LV_USE_CALENDAR_CHINESE
181     gregorian_time = gregorian_get_next_month_time(&d);
182 #endif
183 
184     for(i = day_first + act_mo_len, c = 1; i < 6 * 7; i++, c++) {
185 #if LV_USE_CALENDAR_CHINESE
186         if(calendar->use_chinese_calendar) {
187             gregorian_time.day = c;
188             chinese_calendar_set_day_name(obj, i, c, &gregorian_time);
189         }
190         else
191 #endif
192             lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c);
193         lv_buttonmatrix_set_button_ctrl(calendar->btnm, i + 7, LV_BUTTONMATRIX_CTRL_DISABLED);
194     }
195 
196     highlight_update(obj);
197 
198     /*Reset the focused button if the days changes*/
199     if(lv_buttonmatrix_get_selected_button(calendar->btnm) != LV_BUTTONMATRIX_BUTTON_NONE) {
200         lv_buttonmatrix_set_selected_button(calendar->btnm, day_first + 7);
201     }
202 
203     lv_obj_invalidate(obj);
204 
205     /* The children of the calendar are probably headers.
206      * Notify them to let the headers updated to the new date*/
207     uint32_t child_cnt = lv_obj_get_child_count(obj);
208     for(i = 0; i < child_cnt; i++) {
209         lv_obj_t * child = lv_obj_get_child(obj, i);
210         if(child == calendar->btnm) continue;
211         lv_obj_send_event(child, LV_EVENT_VALUE_CHANGED, obj);
212     }
213 }
214 
215 /*=====================
216  * Getter functions
217  *====================*/
218 
lv_calendar_get_btnmatrix(const lv_obj_t * obj)219 lv_obj_t * lv_calendar_get_btnmatrix(const lv_obj_t * obj)
220 {
221     LV_ASSERT_OBJ(obj, MY_CLASS);
222     const lv_calendar_t * calendar = (lv_calendar_t *)obj;
223     return calendar->btnm;
224 }
225 
lv_calendar_get_today_date(const lv_obj_t * obj)226 const lv_calendar_date_t * lv_calendar_get_today_date(const lv_obj_t * obj)
227 {
228     LV_ASSERT_OBJ(obj, MY_CLASS);
229     const lv_calendar_t * calendar = (lv_calendar_t *)obj;
230 
231     return &calendar->today;
232 }
233 
lv_calendar_get_showed_date(const lv_obj_t * obj)234 const lv_calendar_date_t * lv_calendar_get_showed_date(const lv_obj_t * obj)
235 {
236     LV_ASSERT_OBJ(obj, MY_CLASS);
237     const lv_calendar_t * calendar = (lv_calendar_t *)obj;
238 
239     return &calendar->showed_date;
240 }
241 
lv_calendar_get_highlighted_dates(const lv_obj_t * obj)242 lv_calendar_date_t * lv_calendar_get_highlighted_dates(const lv_obj_t * obj)
243 {
244     LV_ASSERT_OBJ(obj, MY_CLASS);
245     lv_calendar_t * calendar = (lv_calendar_t *)obj;
246 
247     return calendar->highlighted_dates;
248 }
249 
lv_calendar_get_highlighted_dates_num(const lv_obj_t * obj)250 size_t lv_calendar_get_highlighted_dates_num(const lv_obj_t * obj)
251 {
252     LV_ASSERT_OBJ(obj, MY_CLASS);
253     lv_calendar_t * calendar = (lv_calendar_t *)obj;
254 
255     return calendar->highlighted_dates_num;
256 }
257 
lv_calendar_get_pressed_date(const lv_obj_t * obj,lv_calendar_date_t * date)258 lv_result_t lv_calendar_get_pressed_date(const lv_obj_t * obj, lv_calendar_date_t * date)
259 {
260     LV_ASSERT_OBJ(obj, MY_CLASS);
261     lv_calendar_t * calendar = (lv_calendar_t *)obj;
262 
263     uint32_t d = lv_buttonmatrix_get_selected_button(calendar->btnm);
264     if(d == LV_BUTTONMATRIX_BUTTON_NONE) {
265         date->year = 0;
266         date->month = 0;
267         date->day = 0;
268         return LV_RESULT_INVALID;
269     }
270 
271     const char * txt = lv_buttonmatrix_get_button_text(calendar->btnm, lv_buttonmatrix_get_selected_button(calendar->btnm));
272 
273     if(txt[1] == 0) date->day = txt[0] - '0';
274     else date->day = (txt[0] - '0') * 10 + (txt[1] - '0');
275 
276     date->year = calendar->showed_date.year;
277     date->month = calendar->showed_date.month;
278 
279     return LV_RESULT_OK;
280 }
281 
282 /**********************
283  *  STATIC FUNCTIONS
284  **********************/
285 
lv_calendar_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)286 static void lv_calendar_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
287 {
288     LV_UNUSED(class_p);
289     lv_calendar_t * calendar = (lv_calendar_t *)obj;
290 
291     /*Initialize the allocated 'ext'*/
292 
293     calendar->today.year  = 2024;
294     calendar->today.month = 1;
295     calendar->today.day   = 1;
296 
297     calendar->showed_date.year  = 2024;
298     calendar->showed_date.month = 1;
299     calendar->showed_date.day   = 1;
300 
301     calendar->highlighted_dates      = NULL;
302     calendar->highlighted_dates_num  = 0;
303 
304     lv_memzero(calendar->nums, sizeof(calendar->nums));
305     uint8_t i;
306     uint8_t j = 0;
307     for(i = 0; i < 8 * 7; i++) {
308         /*Every 8th string is "\n"*/
309         if(i != 0 && (i + 1) % 8 == 0) {
310             calendar->map[i] = "\n";
311         }
312         else if(i < 7) {
313             calendar->map[i] = day_names_def[i];
314         }
315         else {
316             calendar->nums[j][0] = 'x';
317             calendar->map[i] = calendar->nums[j];
318             j++;
319         }
320     }
321     calendar->map[8 * 7 - 1] = "";
322 
323     calendar->btnm = lv_buttonmatrix_create(obj);
324     lv_buttonmatrix_set_map(calendar->btnm, calendar->map);
325     lv_buttonmatrix_set_button_ctrl_all(calendar->btnm, LV_BUTTONMATRIX_CTRL_CLICK_TRIG | LV_BUTTONMATRIX_CTRL_NO_REPEAT);
326     lv_obj_add_event_cb(calendar->btnm, draw_task_added_event_cb, LV_EVENT_DRAW_TASK_ADDED, NULL);
327     lv_obj_set_width(calendar->btnm, lv_pct(100));
328     lv_obj_add_flag(calendar->btnm, LV_OBJ_FLAG_EVENT_BUBBLE | LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS);
329 
330     lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN);
331     lv_obj_set_flex_grow(calendar->btnm, 1);
332 
333     lv_obj_set_style_text_align(obj, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
334 
335     lv_calendar_set_month_shown(obj, calendar->showed_date.year, calendar->showed_date.month);
336     lv_calendar_set_today_date(obj, calendar->today.year, calendar->today.month, calendar->today.day);
337 }
338 
draw_task_added_event_cb(lv_event_t * e)339 static void draw_task_added_event_cb(lv_event_t * e)
340 {
341     lv_obj_t * obj = lv_event_get_current_target(e);
342     lv_draw_task_t * draw_task = lv_event_get_param(e);
343     if(((lv_draw_dsc_base_t *)draw_task->draw_dsc)->part != LV_PART_ITEMS) return;
344 
345     lv_draw_fill_dsc_t * fill_draw_dsc = lv_draw_task_get_fill_dsc(draw_task);
346     lv_draw_border_dsc_t * border_draw_dsc = lv_draw_task_get_border_dsc(draw_task);
347 
348     if(!fill_draw_dsc && !border_draw_dsc) {
349         return;
350     }
351 
352     int32_t id = ((lv_draw_dsc_base_t *)draw_task->draw_dsc)->id1;
353 
354     /*Day name styles*/
355     if(id < 7) {
356         if(fill_draw_dsc) fill_draw_dsc->opa = LV_OPA_TRANSP;
357         if(border_draw_dsc) border_draw_dsc->opa = LV_OPA_TRANSP;
358     }
359     else if(lv_buttonmatrix_has_button_ctrl(obj, id, LV_BUTTONMATRIX_CTRL_DISABLED)) {
360         if(fill_draw_dsc) fill_draw_dsc->opa = LV_OPA_TRANSP;
361         if(border_draw_dsc) border_draw_dsc->opa = LV_OPA_TRANSP;
362     }
363 
364     if(lv_buttonmatrix_has_button_ctrl(obj, id, LV_CALENDAR_CTRL_HIGHLIGHT)) {
365         if(border_draw_dsc) border_draw_dsc->color = lv_theme_get_color_primary(obj);
366         if(fill_draw_dsc) fill_draw_dsc->opa = LV_OPA_40;
367         if(fill_draw_dsc) fill_draw_dsc->color = lv_theme_get_color_primary(obj);
368         if(lv_buttonmatrix_get_selected_button(obj) == (uint32_t)id) {
369             if(fill_draw_dsc) fill_draw_dsc->opa = LV_OPA_70;
370         }
371     }
372 
373     if(lv_buttonmatrix_has_button_ctrl(obj, id, LV_CALENDAR_CTRL_TODAY)) {
374         if(border_draw_dsc) border_draw_dsc->opa = LV_OPA_COVER;
375         if(border_draw_dsc) border_draw_dsc->color = lv_theme_get_color_primary(obj);
376         if(border_draw_dsc) border_draw_dsc->width += 1;
377     }
378 }
379 
380 /**
381  * Get the number of days in a month
382  * @param year a year
383  * @param month a month. The range is basically [1..12] but [-11..0] or [13..24] is also
384  *              supported to handle next/prev. year
385  * @return [28..31]
386  */
get_month_length(int32_t year,int32_t month)387 static uint8_t get_month_length(int32_t year, int32_t month)
388 {
389     month--;
390     if(month < 0) {
391         year--;             /*Already in the previous year (won't be less than -12 to skip a whole year)*/
392         month = 12 + month; /*`month` is negative, the result will be < 12*/
393     }
394     if(month >= 12) {
395         year++;
396         month -= 12;
397     }
398 
399     /*month == 1 is february*/
400     return (month == 1) ? (28 + is_leap_year(year)) : 31 - month % 7 % 2;
401 }
402 
403 /**
404  * Tells whether a year is leap year or not
405  * @param year a year
406  * @return 0: not leap year; 1: leap year
407  */
is_leap_year(uint32_t year)408 static uint8_t is_leap_year(uint32_t year)
409 {
410     return (year % 4) || ((year % 100 == 0) && (year % 400)) ? 0 : 1;
411 }
412 
413 /**
414  * Get the day of the week
415  * @param year a year
416  * @param month a  month [1..12]
417  * @param day a day [1..32]
418  * @return [0..6] which means [Sun..Sat] or [Mon..Sun] depending on LV_CALENDAR_WEEK_STARTS_MONDAY
419  */
get_day_of_week(uint32_t year,uint32_t month,uint32_t day)420 static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day)
421 {
422     uint32_t a = month < 3 ? 1 : 0;
423     uint32_t b = year - a;
424 
425 #if LV_CALENDAR_WEEK_STARTS_MONDAY
426     uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400) - 1) % 7;
427 #else
428     uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400)) % 7;
429 #endif
430 
431     return day_of_week  ;
432 }
433 
highlight_update(lv_obj_t * obj)434 static void highlight_update(lv_obj_t * obj)
435 {
436     lv_calendar_t * calendar = (lv_calendar_t *)obj;
437     uint32_t i;
438 
439     /*Clear all kind of selection*/
440     lv_buttonmatrix_clear_button_ctrl_all(calendar->btnm, LV_CALENDAR_CTRL_TODAY | LV_CALENDAR_CTRL_HIGHLIGHT);
441 
442     uint8_t day_first = get_day_of_week(calendar->showed_date.year, calendar->showed_date.month, 1);
443     if(calendar->highlighted_dates) {
444         for(i = 0; i < calendar->highlighted_dates_num; i++) {
445             if(calendar->highlighted_dates[i].year == calendar->showed_date.year &&
446                calendar->highlighted_dates[i].month == calendar->showed_date.month) {
447                 lv_buttonmatrix_set_button_ctrl(calendar->btnm, calendar->highlighted_dates[i].day - 1 + day_first + 7,
448                                                 LV_CALENDAR_CTRL_HIGHLIGHT);
449             }
450         }
451     }
452 
453     if(calendar->showed_date.year == calendar->today.year && calendar->showed_date.month == calendar->today.month) {
454         lv_buttonmatrix_set_button_ctrl(calendar->btnm, calendar->today.day - 1 + day_first + 7, LV_CALENDAR_CTRL_TODAY);
455     }
456 }
457 
458 #if LV_USE_CALENDAR_CHINESE
459 
gregorian_get_last_month_time(lv_calendar_date_t * time)460 static lv_calendar_date_t gregorian_get_last_month_time(lv_calendar_date_t * time)
461 {
462     lv_calendar_date_t last_month_time;
463     if(time->month == 1) {
464         last_month_time.month = 12;
465         last_month_time.year = time->year - 1;
466     }
467     else {
468         last_month_time.month = time->month - 1;
469         last_month_time.year = time->year;
470     }
471     return last_month_time;
472 }
473 
gregorian_get_next_month_time(lv_calendar_date_t * time)474 static lv_calendar_date_t gregorian_get_next_month_time(lv_calendar_date_t * time)
475 {
476     lv_calendar_date_t next_month_time;
477     if(time->month == 12) {
478         next_month_time.month = 1;
479         next_month_time.year = time->year + 1;
480     }
481     else {
482         next_month_time.month = time->month + 1;
483         next_month_time.year = time->year;
484     }
485     return next_month_time;
486 }
487 
chinese_calendar_set_day_name(lv_obj_t * obj,uint8_t index,uint8_t day,lv_calendar_date_t * gregorian_time)488 static void chinese_calendar_set_day_name(lv_obj_t * obj, uint8_t index, uint8_t day,
489                                           lv_calendar_date_t * gregorian_time)
490 {
491     lv_calendar_t * calendar = (lv_calendar_t *)obj;
492     const char * day_name = lv_calendar_get_day_name(gregorian_time);
493     if(day_name != NULL)
494         lv_snprintf(calendar->nums[index], sizeof(calendar->nums[0]),
495                     "%d\n%s",
496                     day,
497                     day_name);
498     else
499         lv_snprintf(calendar->nums[index], sizeof(calendar->nums[0]), "%d", day);
500 }
501 #endif
502 
503 #endif  /*LV_USE_CALENDAR*/
504