1 /**
2  * @file lv_calendar.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_calendar.h"
10 #include "../../../lvgl.h"
11 #if LV_USE_CALENDAR
12 
13 #include "../../../misc/lv_assert.h"
14 
15 /*********************
16  *      DEFINES
17  *********************/
18 #define LV_CALENDAR_CTRL_TODAY      LV_BTNMATRIX_CTRL_CUSTOM_1
19 #define LV_CALENDAR_CTRL_HIGHLIGHT  LV_BTNMATRIX_CTRL_CUSTOM_2
20 
21 #define MY_CLASS &lv_calendar_class
22 
23 /**********************
24  *      TYPEDEFS
25  **********************/
26 
27 /**********************
28  *  STATIC PROTOTYPES
29  **********************/
30 static void lv_calendar_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
31 static void draw_part_begin_event_cb(lv_event_t * e);
32 
33 static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day);
34 static uint8_t get_month_length(int32_t year, int32_t month);
35 static uint8_t is_leap_year(uint32_t year);
36 static void highlight_update(lv_obj_t * calendar);
37 
38 /**********************
39  *  STATIC VARIABLES
40  **********************/
41 const lv_obj_class_t lv_calendar_class = {
42     .constructor_cb = lv_calendar_constructor,
43     .width_def = (LV_DPI_DEF * 3) / 2,
44     .height_def = (LV_DPI_DEF * 3) / 2,
45     .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
46     .instance_size = sizeof(lv_calendar_t),
47     .base_class = &lv_obj_class
48 };
49 
50 static const char * day_names_def[7] = LV_CALENDAR_DEFAULT_DAY_NAMES;
51 
52 /**********************
53  *      MACROS
54  **********************/
55 
56 /**********************
57  *   GLOBAL FUNCTIONS
58  **********************/
59 
lv_calendar_create(lv_obj_t * parent)60 lv_obj_t * lv_calendar_create(lv_obj_t * parent)
61 {
62     LV_LOG_INFO("begin");
63     lv_obj_t * obj = lv_obj_class_create_obj(&lv_calendar_class, parent);
64     lv_obj_class_init_obj(obj);
65     return obj;
66 }
67 
68 /*=====================
69  * Setter functions
70  *====================*/
71 
lv_calendar_set_day_names(lv_obj_t * obj,const char * day_names[])72 void lv_calendar_set_day_names(lv_obj_t * obj, const char * day_names[])
73 {
74     LV_ASSERT_OBJ(obj, MY_CLASS);
75     lv_calendar_t * calendar = (lv_calendar_t *)obj;
76 
77     uint32_t i;
78     for(i = 0; i < 7; i++) {
79         calendar->map[i] = day_names[i];
80     }
81     lv_obj_invalidate(obj);
82 }
83 
lv_calendar_set_today_date(lv_obj_t * obj,uint32_t year,uint32_t month,uint32_t day)84 void lv_calendar_set_today_date(lv_obj_t * obj, uint32_t year, uint32_t month, uint32_t day)
85 {
86     LV_ASSERT_OBJ(obj, MY_CLASS);
87     lv_calendar_t * calendar = (lv_calendar_t *)obj;
88 
89     calendar->today.year         = year;
90     calendar->today.month        = month;
91     calendar->today.day          = day;
92 
93     highlight_update(obj);
94 }
95 
lv_calendar_set_highlighted_dates(lv_obj_t * obj,lv_calendar_date_t highlighted[],uint16_t date_num)96 void lv_calendar_set_highlighted_dates(lv_obj_t * obj, lv_calendar_date_t highlighted[], uint16_t date_num)
97 {
98     LV_ASSERT_NULL(highlighted);
99 
100     LV_ASSERT_OBJ(obj, MY_CLASS);
101     lv_calendar_t * calendar = (lv_calendar_t *)obj;
102 
103     calendar->highlighted_dates     = highlighted;
104     calendar->highlighted_dates_num = date_num;
105 
106     highlight_update(obj);
107 }
108 
lv_calendar_set_showed_date(lv_obj_t * obj,uint32_t year,uint32_t month)109 void lv_calendar_set_showed_date(lv_obj_t * obj, uint32_t year, uint32_t month)
110 {
111     LV_ASSERT_OBJ(obj, MY_CLASS);
112     lv_calendar_t * calendar = (lv_calendar_t *)obj;
113 
114     calendar->showed_date.year   = year;
115     calendar->showed_date.month  = month;
116     calendar->showed_date.day    = 1;
117 
118     lv_calendar_date_t d;
119     d.year = calendar->showed_date.year;
120     d.month = calendar->showed_date.month;
121     d.day = calendar->showed_date.day;
122 
123     uint8_t i;
124 
125     /*Remove the disabled state but revert it for day names*/
126     lv_btnmatrix_clear_btn_ctrl_all(calendar->btnm, LV_BTNMATRIX_CTRL_DISABLED);
127     for(i = 0; i < 7; i++) {
128         lv_btnmatrix_set_btn_ctrl(calendar->btnm, i, LV_BTNMATRIX_CTRL_DISABLED);
129     }
130 
131     uint8_t act_mo_len = get_month_length(d.year, d.month);
132     uint8_t day_first = get_day_of_week(d.year, d.month, 1);
133     uint8_t c;
134     for(i = day_first, c = 1; i < act_mo_len + day_first; i++, c++) {
135         lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c);
136     }
137 
138     uint8_t prev_mo_len = get_month_length(d.year, d.month - 1);
139     for(i = 0, c = prev_mo_len - day_first + 1; i < day_first; i++, c++) {
140         lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c);
141         lv_btnmatrix_set_btn_ctrl(calendar->btnm, i + 7, LV_BTNMATRIX_CTRL_DISABLED);
142     }
143 
144     for(i = day_first + act_mo_len, c = 1; i < 6 * 7; i++, c++) {
145         lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c);
146         lv_btnmatrix_set_btn_ctrl(calendar->btnm, i + 7, LV_BTNMATRIX_CTRL_DISABLED);
147     }
148 
149     highlight_update(obj);
150 
151     /*Reset the focused button if the days changes*/
152     if(lv_btnmatrix_get_selected_btn(calendar->btnm) != LV_BTNMATRIX_BTN_NONE) {
153         lv_btnmatrix_set_selected_btn(calendar->btnm, day_first + 7);
154     }
155 
156     lv_obj_invalidate(obj);
157 
158     /* The children of the calendar are probably headers.
159      * Notify them to let the headers updated to the new date*/
160     uint32_t child_cnt = lv_obj_get_child_cnt(obj);
161     for(i = 0; i < child_cnt; i++) {
162         lv_obj_t * child = lv_obj_get_child(obj, i);
163         if(child == calendar->btnm) continue;
164         lv_event_send(child, LV_EVENT_VALUE_CHANGED, obj);
165     }
166 }
167 
168 /*=====================
169  * Getter functions
170  *====================*/
171 
lv_calendar_get_btnmatrix(const lv_obj_t * obj)172 lv_obj_t * lv_calendar_get_btnmatrix(const lv_obj_t * obj)
173 {
174     LV_ASSERT_OBJ(obj, MY_CLASS);
175     const lv_calendar_t * calendar = (lv_calendar_t *)obj;
176     return calendar->btnm;
177 }
178 
lv_calendar_get_today_date(const lv_obj_t * obj)179 const lv_calendar_date_t * lv_calendar_get_today_date(const lv_obj_t * obj)
180 {
181     LV_ASSERT_OBJ(obj, MY_CLASS);
182     const lv_calendar_t * calendar = (lv_calendar_t *)obj;
183 
184     return &calendar->today;
185 }
186 
lv_calendar_get_showed_date(const lv_obj_t * obj)187 const lv_calendar_date_t * lv_calendar_get_showed_date(const lv_obj_t * obj)
188 {
189     LV_ASSERT_OBJ(obj, MY_CLASS);
190     const lv_calendar_t * calendar = (lv_calendar_t *)obj;
191 
192     return &calendar->showed_date;
193 }
194 
lv_calendar_get_highlighted_dates(const lv_obj_t * obj)195 lv_calendar_date_t * lv_calendar_get_highlighted_dates(const lv_obj_t * obj)
196 {
197     LV_ASSERT_OBJ(obj, MY_CLASS);
198     lv_calendar_t * calendar = (lv_calendar_t *)obj;
199 
200     return calendar->highlighted_dates;
201 }
202 
lv_calendar_get_highlighted_dates_num(const lv_obj_t * obj)203 uint16_t lv_calendar_get_highlighted_dates_num(const lv_obj_t * obj)
204 {
205     LV_ASSERT_OBJ(obj, MY_CLASS);
206     lv_calendar_t * calendar = (lv_calendar_t *)obj;
207 
208     return calendar->highlighted_dates_num;
209 }
210 
lv_calendar_get_pressed_date(const lv_obj_t * obj,lv_calendar_date_t * date)211 lv_res_t lv_calendar_get_pressed_date(const lv_obj_t * obj, lv_calendar_date_t * date)
212 {
213     LV_ASSERT_OBJ(obj, MY_CLASS);
214     lv_calendar_t * calendar = (lv_calendar_t *)obj;
215 
216     uint16_t d = lv_btnmatrix_get_selected_btn(calendar->btnm);
217     if(d == LV_BTNMATRIX_BTN_NONE) {
218         date->year = 0;
219         date->month = 0;
220         date->day = 0;
221         return LV_RES_INV;
222     }
223 
224     const char * txt = lv_btnmatrix_get_btn_text(calendar->btnm, lv_btnmatrix_get_selected_btn(calendar->btnm));
225 
226     if(txt[1] == 0) date->day = txt[0] - '0';
227     else date->day = (txt[0] - '0') * 10 + (txt[1] - '0');
228 
229     date->year = calendar->showed_date.year;
230     date->month = calendar->showed_date.month;
231 
232     return LV_RES_OK;
233 }
234 
235 
236 /**********************
237  *  STATIC FUNCTIONS
238  **********************/
239 
lv_calendar_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)240 static void lv_calendar_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
241 {
242     LV_UNUSED(class_p);
243     lv_calendar_t * calendar = (lv_calendar_t *)obj;
244 
245     /*Initialize the allocated 'ext'*/
246     calendar->today.year  = 2020;
247     calendar->today.month = 1;
248     calendar->today.day   = 1;
249 
250     calendar->showed_date.year  = 2020;
251     calendar->showed_date.month = 1;
252     calendar->showed_date.day   = 1;
253 
254     calendar->highlighted_dates      = NULL;
255     calendar->highlighted_dates_num  = 0;
256 
257     lv_memset_00(calendar->nums, sizeof(calendar->nums));
258     uint8_t i;
259     uint8_t j = 0;
260     for(i = 0; i < 8 * 7; i++) {
261         /*Every 8th string is "\n"*/
262         if(i != 0 && (i + 1) % 8 == 0) {
263             calendar->map[i] = "\n";
264         }
265         else if(i < 8) {
266             calendar->map[i] = day_names_def[i];
267         }
268         else {
269             calendar->nums[j][0] = 'x';
270             calendar->map[i] = calendar->nums[j];
271             j++;
272         }
273     }
274     calendar->map[8 * 7 - 1] = "";
275 
276     calendar->btnm = lv_btnmatrix_create(obj);
277     lv_btnmatrix_set_map(calendar->btnm, calendar->map);
278     lv_btnmatrix_set_btn_ctrl_all(calendar->btnm, LV_BTNMATRIX_CTRL_CLICK_TRIG | LV_BTNMATRIX_CTRL_NO_REPEAT);
279     lv_obj_add_event_cb(calendar->btnm, draw_part_begin_event_cb, LV_EVENT_DRAW_PART_BEGIN, NULL);
280     lv_obj_set_width(calendar->btnm, lv_pct(100));
281 
282     lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN);
283     lv_obj_set_flex_grow(calendar->btnm, 1);
284 
285     lv_calendar_set_showed_date(obj, calendar->showed_date.year, calendar->showed_date.month);
286     lv_calendar_set_today_date(obj, calendar->today.year, calendar->today.month, calendar->today.day);
287 
288     lv_obj_add_flag(calendar->btnm, LV_OBJ_FLAG_EVENT_BUBBLE);
289 }
290 
draw_part_begin_event_cb(lv_event_t * e)291 static void draw_part_begin_event_cb(lv_event_t * e)
292 {
293     lv_obj_t * obj = lv_event_get_target(e);
294     lv_obj_draw_part_dsc_t * dsc = lv_event_get_param(e);
295     if(dsc->part == LV_PART_ITEMS) {
296         /*Day name styles*/
297         if(dsc->id < 7) {
298             dsc->rect_dsc->bg_opa = LV_OPA_TRANSP;
299             dsc->rect_dsc->border_opa = LV_OPA_TRANSP;
300         }
301         else if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_BTNMATRIX_CTRL_DISABLED)) {
302             dsc->rect_dsc->bg_opa = LV_OPA_TRANSP;
303             dsc->rect_dsc->border_opa = LV_OPA_TRANSP;
304             dsc->label_dsc->color = lv_palette_main(LV_PALETTE_GREY);
305         }
306 
307         if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_CALENDAR_CTRL_HIGHLIGHT)) {
308             dsc->rect_dsc->bg_opa = LV_OPA_40;
309             dsc->rect_dsc->bg_color = lv_theme_get_color_primary(obj);
310             if(lv_btnmatrix_get_selected_btn(obj) == dsc->id) {
311                 dsc->rect_dsc->bg_opa = LV_OPA_70;
312             }
313         }
314 
315         if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_CALENDAR_CTRL_TODAY)) {
316             dsc->rect_dsc->border_opa = LV_OPA_COVER;
317             dsc->rect_dsc->border_color = lv_theme_get_color_primary(obj);
318             dsc->rect_dsc->border_width += 1;
319         }
320 
321     }
322 }
323 
324 /**
325  * Get the number of days in a month
326  * @param year a year
327  * @param month a month. The range is basically [1..12] but [-11..0] or [13..24] is also
328  *              supported to handle next/prev. year
329  * @return [28..31]
330  */
get_month_length(int32_t year,int32_t month)331 static uint8_t get_month_length(int32_t year, int32_t month)
332 {
333     month--;
334     if(month < 0) {
335         year--;             /*Already in the previous year (won't be less then -12 to skip a whole year)*/
336         month = 12 + month; /*`month` is negative, the result will be < 12*/
337     }
338     if(month >= 12) {
339         year++;
340         month -= 12;
341     }
342 
343     /*month == 1 is february*/
344     return (month == 1) ? (28 + is_leap_year(year)) : 31 - month % 7 % 2;
345 }
346 
347 /**
348  * Tells whether a year is leap year or not
349  * @param year a year
350  * @return 0: not leap year; 1: leap year
351  */
is_leap_year(uint32_t year)352 static uint8_t is_leap_year(uint32_t year)
353 {
354     return (year % 4) || ((year % 100 == 0) && (year % 400)) ? 0 : 1;
355 }
356 
357 /**
358  * Get the day of the week
359  * @param year a year
360  * @param month a  month [1..12]
361  * @param day a day [1..32]
362  * @return [0..6] which means [Sun..Sat] or [Mon..Sun] depending on LV_CALENDAR_WEEK_STARTS_MONDAY
363  */
get_day_of_week(uint32_t year,uint32_t month,uint32_t day)364 static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day)
365 {
366     uint32_t a = month < 3 ? 1 : 0;
367     uint32_t b = year - a;
368 
369 #if LV_CALENDAR_WEEK_STARTS_MONDAY
370     uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400) - 1) % 7;
371 #else
372     uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400)) % 7;
373 #endif
374 
375     return day_of_week  ;
376 }
377 
highlight_update(lv_obj_t * obj)378 static void highlight_update(lv_obj_t * obj)
379 {
380     lv_calendar_t * calendar = (lv_calendar_t *)obj;
381     uint16_t i;
382 
383     /*Clear all kind of selection*/
384     lv_btnmatrix_clear_btn_ctrl_all(calendar->btnm, LV_CALENDAR_CTRL_TODAY | LV_CALENDAR_CTRL_HIGHLIGHT);
385 
386     uint8_t day_first = get_day_of_week(calendar->showed_date.year, calendar->showed_date.month, 1);
387     if(calendar->highlighted_dates) {
388         for(i = 0; i < calendar->highlighted_dates_num; i++) {
389             if(calendar->highlighted_dates[i].year == calendar->showed_date.year &&
390                calendar->highlighted_dates[i].month == calendar->showed_date.month) {
391                 lv_btnmatrix_set_btn_ctrl(calendar->btnm, calendar->highlighted_dates[i].day - 1 + day_first + 7,
392                                           LV_CALENDAR_CTRL_HIGHLIGHT);
393             }
394         }
395     }
396 
397     if(calendar->showed_date.year == calendar->today.year && calendar->showed_date.month == calendar->today.month) {
398         lv_btnmatrix_set_btn_ctrl(calendar->btnm, calendar->today.day - 1 + day_first + 7, LV_CALENDAR_CTRL_TODAY);
399     }
400 }
401 
402 #endif  /*LV_USE_CALENDAR*/
403