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     uint32_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  *  STATIC FUNCTIONS
237  **********************/
238 
lv_calendar_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)239 static void lv_calendar_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
240 {
241     LV_UNUSED(class_p);
242     lv_calendar_t * calendar = (lv_calendar_t *)obj;
243 
244     /*Initialize the allocated 'ext'*/
245     calendar->today.year  = 2020;
246     calendar->today.month = 1;
247     calendar->today.day   = 1;
248 
249     calendar->showed_date.year  = 2020;
250     calendar->showed_date.month = 1;
251     calendar->showed_date.day   = 1;
252 
253     calendar->highlighted_dates      = NULL;
254     calendar->highlighted_dates_num  = 0;
255 
256     lv_memset_00(calendar->nums, sizeof(calendar->nums));
257     uint8_t i;
258     uint8_t j = 0;
259     for(i = 0; i < 8 * 7; i++) {
260         /*Every 8th string is "\n"*/
261         if(i != 0 && (i + 1) % 8 == 0) {
262             calendar->map[i] = "\n";
263         }
264         else if(i < 7) {
265             calendar->map[i] = day_names_def[i];
266         }
267         else {
268             calendar->nums[j][0] = 'x';
269             calendar->map[i] = calendar->nums[j];
270             j++;
271         }
272     }
273     calendar->map[8 * 7 - 1] = "";
274 
275     calendar->btnm = lv_btnmatrix_create(obj);
276     lv_btnmatrix_set_map(calendar->btnm, calendar->map);
277     lv_btnmatrix_set_btn_ctrl_all(calendar->btnm, LV_BTNMATRIX_CTRL_CLICK_TRIG | LV_BTNMATRIX_CTRL_NO_REPEAT);
278     lv_obj_add_event_cb(calendar->btnm, draw_part_begin_event_cb, LV_EVENT_DRAW_PART_BEGIN, NULL);
279     lv_obj_set_width(calendar->btnm, lv_pct(100));
280 
281     lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN);
282     lv_obj_set_flex_grow(calendar->btnm, 1);
283 
284     lv_calendar_set_showed_date(obj, calendar->showed_date.year, calendar->showed_date.month);
285     lv_calendar_set_today_date(obj, calendar->today.year, calendar->today.month, calendar->today.day);
286 
287     lv_obj_add_flag(calendar->btnm, LV_OBJ_FLAG_EVENT_BUBBLE);
288 }
289 
draw_part_begin_event_cb(lv_event_t * e)290 static void draw_part_begin_event_cb(lv_event_t * e)
291 {
292     lv_obj_t * obj = lv_event_get_target(e);
293     lv_obj_draw_part_dsc_t * dsc = lv_event_get_param(e);
294     if(dsc->part == LV_PART_ITEMS) {
295         /*Day name styles*/
296         if(dsc->id < 7) {
297             dsc->rect_dsc->bg_opa = LV_OPA_TRANSP;
298             dsc->rect_dsc->border_opa = LV_OPA_TRANSP;
299         }
300         else if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_BTNMATRIX_CTRL_DISABLED)) {
301             dsc->rect_dsc->bg_opa = LV_OPA_TRANSP;
302             dsc->rect_dsc->border_opa = LV_OPA_TRANSP;
303             dsc->label_dsc->color = lv_palette_main(LV_PALETTE_GREY);
304         }
305 
306         if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_CALENDAR_CTRL_HIGHLIGHT)) {
307             dsc->rect_dsc->bg_opa = LV_OPA_40;
308             dsc->rect_dsc->bg_color = lv_theme_get_color_primary(obj);
309             if(lv_btnmatrix_get_selected_btn(obj) == dsc->id) {
310                 dsc->rect_dsc->bg_opa = LV_OPA_70;
311             }
312         }
313 
314         if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_CALENDAR_CTRL_TODAY)) {
315             dsc->rect_dsc->border_opa = LV_OPA_COVER;
316             dsc->rect_dsc->border_color = lv_theme_get_color_primary(obj);
317             dsc->rect_dsc->border_width += 1;
318         }
319 
320     }
321 }
322 
323 /**
324  * Get the number of days in a month
325  * @param year a year
326  * @param month a month. The range is basically [1..12] but [-11..0] or [13..24] is also
327  *              supported to handle next/prev. year
328  * @return [28..31]
329  */
get_month_length(int32_t year,int32_t month)330 static uint8_t get_month_length(int32_t year, int32_t month)
331 {
332     month--;
333     if(month < 0) {
334         year--;             /*Already in the previous year (won't be less then -12 to skip a whole year)*/
335         month = 12 + month; /*`month` is negative, the result will be < 12*/
336     }
337     if(month >= 12) {
338         year++;
339         month -= 12;
340     }
341 
342     /*month == 1 is february*/
343     return (month == 1) ? (28 + is_leap_year(year)) : 31 - month % 7 % 2;
344 }
345 
346 /**
347  * Tells whether a year is leap year or not
348  * @param year a year
349  * @return 0: not leap year; 1: leap year
350  */
is_leap_year(uint32_t year)351 static uint8_t is_leap_year(uint32_t year)
352 {
353     return (year % 4) || ((year % 100 == 0) && (year % 400)) ? 0 : 1;
354 }
355 
356 /**
357  * Get the day of the week
358  * @param year a year
359  * @param month a  month [1..12]
360  * @param day a day [1..32]
361  * @return [0..6] which means [Sun..Sat] or [Mon..Sun] depending on LV_CALENDAR_WEEK_STARTS_MONDAY
362  */
get_day_of_week(uint32_t year,uint32_t month,uint32_t day)363 static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day)
364 {
365     uint32_t a = month < 3 ? 1 : 0;
366     uint32_t b = year - a;
367 
368 #if LV_CALENDAR_WEEK_STARTS_MONDAY
369     uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400) - 1) % 7;
370 #else
371     uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400)) % 7;
372 #endif
373 
374     return day_of_week  ;
375 }
376 
highlight_update(lv_obj_t * obj)377 static void highlight_update(lv_obj_t * obj)
378 {
379     lv_calendar_t * calendar = (lv_calendar_t *)obj;
380     uint16_t i;
381 
382     /*Clear all kind of selection*/
383     lv_btnmatrix_clear_btn_ctrl_all(calendar->btnm, LV_CALENDAR_CTRL_TODAY | LV_CALENDAR_CTRL_HIGHLIGHT);
384 
385     uint8_t day_first = get_day_of_week(calendar->showed_date.year, calendar->showed_date.month, 1);
386     if(calendar->highlighted_dates) {
387         for(i = 0; i < calendar->highlighted_dates_num; i++) {
388             if(calendar->highlighted_dates[i].year == calendar->showed_date.year &&
389                calendar->highlighted_dates[i].month == calendar->showed_date.month) {
390                 lv_btnmatrix_set_btn_ctrl(calendar->btnm, calendar->highlighted_dates[i].day - 1 + day_first + 7,
391                                           LV_CALENDAR_CTRL_HIGHLIGHT);
392             }
393         }
394     }
395 
396     if(calendar->showed_date.year == calendar->today.year && calendar->showed_date.month == calendar->today.month) {
397         lv_btnmatrix_set_btn_ctrl(calendar->btnm, calendar->today.day - 1 + day_first + 7, LV_CALENDAR_CTRL_TODAY);
398     }
399 }
400 
401 #endif  /*LV_USE_CALENDAR*/
402