1 
2 /**
3  * @file lv_kb.c
4  *
5  */
6 
7 /*********************
8  *      INCLUDES
9  *********************/
10 #include "lv_keyboard.h"
11 #if LV_USE_KEYBOARD != 0
12 
13 #include "../lv_misc/lv_debug.h"
14 #include "../lv_themes/lv_theme.h"
15 #include "lv_textarea.h"
16 
17 /*********************
18  *      DEFINES
19  *********************/
20 #define LV_OBJX_NAME "lv_keyboard"
21 
22 /**********************
23  *      TYPEDEFS
24  **********************/
25 
26 /**********************
27  *  STATIC PROTOTYPES
28  **********************/
29 static lv_res_t lv_keyboard_signal(lv_obj_t * kb, lv_signal_t sign, void * param);
30 static void lv_keyboard_update_map(lv_obj_t * kb);
31 
32 /**********************
33  *  STATIC VARIABLES
34  **********************/
35 static lv_signal_cb_t ancestor_signal;
36 /* clang-format off */
37 static const char * const default_kb_map_lc[] = {"1#", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", LV_SYMBOL_BACKSPACE, "\n",
38                                                  "ABC", "a", "s", "d", "f", "g", "h", "j", "k", "l", LV_SYMBOL_NEW_LINE, "\n",
39                                                  "_", "-", "z", "x", "c", "v", "b", "n", "m", ".", ",", ":", "\n",
40                                                  LV_SYMBOL_CLOSE, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, ""
41                                                 };
42 
43 static const lv_btnmatrix_ctrl_t default_kb_ctrl_lc_map[] = {
44     LV_KEYBOARD_CTRL_BTN_FLAGS | 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 7,
45     LV_KEYBOARD_CTRL_BTN_FLAGS | 6, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7,
46     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
47     LV_KEYBOARD_CTRL_BTN_FLAGS | 2, 2, 6, 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2
48 };
49 
50 static const char * const default_kb_map_uc[] = {"1#", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", LV_SYMBOL_BACKSPACE, "\n",
51                                                  "abc", "A", "S", "D", "F", "G", "H", "J", "K", "L", LV_SYMBOL_NEW_LINE, "\n",
52                                                  "_", "-", "Z", "X", "C", "V", "B", "N", "M", ".", ",", ":", "\n",
53                                                  LV_SYMBOL_CLOSE, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, ""
54                                                 };
55 
56 static const lv_btnmatrix_ctrl_t default_kb_ctrl_uc_map[] = {
57     LV_KEYBOARD_CTRL_BTN_FLAGS | 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 7,
58     LV_KEYBOARD_CTRL_BTN_FLAGS | 6, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7,
59     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
60     LV_KEYBOARD_CTRL_BTN_FLAGS | 2, 2, 6, 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2
61 };
62 
63 static const char * const default_kb_map_spec[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", LV_SYMBOL_BACKSPACE, "\n",
64                                                    "abc", "+", "-", "/", "*", "=", "%", "!", "?", "#", "<", ">", "\n",
65                                                    "\\",  "@", "$", "(", ")", "{", "}", "[", "]", ";", "\"", "'", "\n",
66                                                    LV_SYMBOL_CLOSE, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, ""
67                                                   };
68 
69 static const lv_btnmatrix_ctrl_t default_kb_ctrl_spec_map[] = {
70     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
71     LV_KEYBOARD_CTRL_BTN_FLAGS | 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
72     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
73     LV_KEYBOARD_CTRL_BTN_FLAGS | 2, 2, 6, 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2
74 };
75 
76 static const char * const default_kb_map_num[] = {"1", "2", "3", LV_SYMBOL_CLOSE, "\n",
77                                                   "4", "5", "6", LV_SYMBOL_OK, "\n",
78                                                   "7", "8", "9", LV_SYMBOL_BACKSPACE, "\n",
79                                                   "+/-", "0", ".", LV_SYMBOL_LEFT, LV_SYMBOL_RIGHT, ""
80                                                  };
81 
82 static const lv_btnmatrix_ctrl_t default_kb_ctrl_num_map[] = {
83     1, 1, 1, LV_KEYBOARD_CTRL_BTN_FLAGS | 2,
84     1, 1, 1, LV_KEYBOARD_CTRL_BTN_FLAGS | 2,
85     1, 1, 1, 2,
86     1, 1, 1, 1, 1
87 };
88 /* clang-format on */
89 
90 static const char * * kb_map[4] = {
91     (const char * *)default_kb_map_lc,
92     (const char * *)default_kb_map_uc,
93     (const char * *)default_kb_map_spec,
94     (const char * *)default_kb_map_num
95 };
96 static const lv_btnmatrix_ctrl_t * kb_ctrl[4] = {
97     default_kb_ctrl_lc_map,
98     default_kb_ctrl_uc_map,
99     default_kb_ctrl_spec_map,
100     default_kb_ctrl_num_map
101 };
102 
103 /**********************
104  *      MACROS
105  **********************/
106 
107 /**********************
108  *   GLOBAL FUNCTIONS
109  **********************/
110 
111 /**
112  * Create a keyboard objects
113  * @param par pointer to an object, it will be the parent of the new keyboard
114  * @param copy pointer to a keyboard object, if not NULL then the new object will be copied from it
115  * @return pointer to the created keyboard
116  */
lv_keyboard_create(lv_obj_t * par,const lv_obj_t * copy)117 lv_obj_t * lv_keyboard_create(lv_obj_t * par, const lv_obj_t * copy)
118 {
119     LV_LOG_TRACE("keyboard create started");
120 
121     /*Create the ancestor of keyboard*/
122     lv_obj_t * kb = lv_btnmatrix_create(par, copy);
123     LV_ASSERT_MEM(kb);
124     if(kb == NULL) return NULL;
125 
126     if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(kb);
127 
128     /*Allocate the keyboard type specific extended data*/
129     lv_keyboard_ext_t * ext = lv_obj_allocate_ext_attr(kb, sizeof(lv_keyboard_ext_t));
130     LV_ASSERT_MEM(ext);
131     if(ext == NULL) {
132         lv_obj_del(kb);
133         return NULL;
134     }
135 
136     /*Initialize the allocated 'ext' */
137     ext->ta         = NULL;
138     ext->mode       = LV_KEYBOARD_MODE_TEXT_LOWER;
139     ext->cursor_mng = 0;
140 
141     /*The signal and design functions are not copied so set them here*/
142     lv_obj_set_signal_cb(kb, lv_keyboard_signal);
143 
144     /*Init the new keyboard keyboard*/
145     if(copy == NULL) {
146         /* Set a size which fits into the parent.
147          * Don't use `par` directly because if the window is created on a page it is moved to the
148          * scrollable so the parent has changed */
149         lv_obj_set_size(kb, lv_obj_get_width_fit(lv_obj_get_parent(kb)),
150                         lv_obj_get_height_fit(lv_obj_get_parent(kb)) / 2);
151         lv_obj_align(kb, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
152         lv_obj_set_event_cb(kb, lv_keyboard_def_event_cb);
153         lv_obj_set_base_dir(kb, LV_BIDI_DIR_LTR);
154         lv_obj_add_protect(kb, LV_PROTECT_CLICK_FOCUS);
155 
156         lv_btnmatrix_set_map(kb, kb_map[ext->mode]);
157         lv_btnmatrix_set_ctrl_map(kb, kb_ctrl[ext->mode]);
158 
159         lv_theme_apply(kb, LV_THEME_KEYBOARD);
160     }
161     /*Copy an existing keyboard*/
162     else {
163         lv_keyboard_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
164         ext->ta                = copy_ext->ta;
165         ext->mode              = copy_ext->mode;
166         ext->cursor_mng        = copy_ext->cursor_mng;
167 
168         lv_btnmatrix_set_map(kb, kb_map[ext->mode]);
169         lv_btnmatrix_set_ctrl_map(kb, kb_ctrl[ext->mode]);
170 
171         /*Refresh the style with new signal function*/
172         //        lv_obj_refresh_style(new_kb);
173     }
174 
175     LV_LOG_INFO("keyboard created");
176 
177     return kb;
178 }
179 
180 /*=====================
181  * Setter functions
182  *====================*/
183 
184 /**
185  * Assign a Text Area to the Keyboard. The pressed characters will be put there.
186  * @param kb pointer to a Keyboard object
187  * @param ta pointer to a Text Area object to write there
188  */
lv_keyboard_set_textarea(lv_obj_t * kb,lv_obj_t * ta)189 void lv_keyboard_set_textarea(lv_obj_t * kb, lv_obj_t * ta)
190 {
191     LV_ASSERT_OBJ(kb, LV_OBJX_NAME);
192     if(ta) {
193         LV_ASSERT_OBJ(ta, "lv_textarea");
194     }
195 
196     lv_keyboard_ext_t * ext = lv_obj_get_ext_attr(kb);
197 
198     /*Hide the cursor of the old Text area if cursor management is enabled*/
199     if(ext->ta && ext->cursor_mng) {
200         lv_textarea_set_cursor_hidden(ext->ta, true);
201     }
202 
203     ext->ta = ta;
204 
205     /*Show the cursor of the new Text area if cursor management is enabled*/
206     if(ext->ta && ext->cursor_mng) {
207         lv_textarea_set_cursor_hidden(ext->ta, false);
208     }
209 }
210 
211 /**
212  * Set a new a mode (text or number map)
213  * @param kb pointer to a Keyboard object
214  * @param mode the mode from 'lv_keyboard_mode_t'
215  */
lv_keyboard_set_mode(lv_obj_t * kb,lv_keyboard_mode_t mode)216 void lv_keyboard_set_mode(lv_obj_t * kb, lv_keyboard_mode_t mode)
217 {
218     LV_ASSERT_OBJ(kb, LV_OBJX_NAME);
219 
220     lv_keyboard_ext_t * ext = lv_obj_get_ext_attr(kb);
221     if(ext->mode == mode) return;
222 
223     ext->mode = mode;
224     lv_btnmatrix_set_map(kb, kb_map[mode]);
225     lv_btnmatrix_set_ctrl_map(kb, kb_ctrl[mode]);
226 }
227 
228 /**
229  * Automatically hide or show the cursor of Text Area
230  * @param kb pointer to a Keyboard object
231  * @param en true: show cursor on the current text area, false: hide cursor
232  */
lv_keyboard_set_cursor_manage(lv_obj_t * kb,bool en)233 void lv_keyboard_set_cursor_manage(lv_obj_t * kb, bool en)
234 {
235     LV_ASSERT_OBJ(kb, LV_OBJX_NAME);
236 
237     lv_keyboard_ext_t * ext = lv_obj_get_ext_attr(kb);
238     if(ext->cursor_mng == en) return;
239 
240     ext->cursor_mng = en == false ? 0 : 1;
241 
242     if(ext->ta) {
243 
244         if(ext->cursor_mng) {
245             lv_textarea_set_cursor_hidden(ext->ta, false);
246         }
247         else {
248             lv_textarea_set_cursor_hidden(ext->ta, true);
249         }
250     }
251 }
252 
253 /**
254  * Set a new map for the keyboard
255  * @param kb pointer to a Keyboard object
256  * @param mode keyboard map to alter 'lv_keyboard_mode_t'
257  * @param map pointer to a string array to describe the map.
258  *            See 'lv_btnmatrix_set_map()' for more info.
259  */
lv_keyboard_set_map(lv_obj_t * kb,lv_keyboard_mode_t mode,const char * map[])260 void lv_keyboard_set_map(lv_obj_t * kb, lv_keyboard_mode_t mode, const char * map[])
261 {
262     kb_map[mode] = map;
263     lv_keyboard_update_map(kb);
264 }
265 
266 /**
267  * Set the button control map (hidden, disabled etc.) for the keyboard. The
268  * control map array will be copied and so may be deallocated after this
269  * function returns.
270  * @param kb pointer to a keyboard object
271  * @param mode keyboard ctrl map to alter 'lv_keyboard_mode_t'
272  * @param ctrl_map pointer to an array of `lv_btn_ctrl_t` control bytes.
273  *                 See: `lv_btnmatrix_set_ctrl_map` for more details.
274  */
lv_keyboard_set_ctrl_map(lv_obj_t * kb,lv_keyboard_mode_t mode,const lv_btnmatrix_ctrl_t ctrl_map[])275 void lv_keyboard_set_ctrl_map(lv_obj_t * kb, lv_keyboard_mode_t mode, const lv_btnmatrix_ctrl_t ctrl_map[])
276 {
277     kb_ctrl[mode] = ctrl_map;
278     lv_keyboard_update_map(kb);
279 }
280 
281 /*=====================
282  * Getter functions
283  *====================*/
284 
285 /**
286  * Assign a Text Area to the Keyboard. The pressed characters will be put there.
287  * @param kb pointer to a Keyboard object
288  * @return pointer to the assigned Text Area object
289  */
lv_keyboard_get_textarea(const lv_obj_t * kb)290 lv_obj_t * lv_keyboard_get_textarea(const lv_obj_t * kb)
291 {
292     LV_ASSERT_OBJ(kb, LV_OBJX_NAME);
293 
294     lv_keyboard_ext_t * ext = lv_obj_get_ext_attr(kb);
295     return ext->ta;
296 }
297 
298 /**
299  * Set a new a mode (text or number map)
300  * @param kb pointer to a Keyboard object
301  * @return the current mode from 'lv_keyboard_mode_t'
302  */
lv_keyboard_get_mode(const lv_obj_t * kb)303 lv_keyboard_mode_t lv_keyboard_get_mode(const lv_obj_t * kb)
304 {
305     LV_ASSERT_OBJ(kb, LV_OBJX_NAME);
306 
307     lv_keyboard_ext_t * ext = lv_obj_get_ext_attr(kb);
308     return ext->mode;
309 }
310 
311 /**
312  * Get the current cursor manage mode.
313  * @param kb pointer to a Keyboard object
314  * @return true: show cursor on the current text area, false: hide cursor
315  */
lv_keyboard_get_cursor_manage(const lv_obj_t * kb)316 bool lv_keyboard_get_cursor_manage(const lv_obj_t * kb)
317 {
318     LV_ASSERT_OBJ(kb, LV_OBJX_NAME);
319 
320     lv_keyboard_ext_t * ext = lv_obj_get_ext_attr(kb);
321     return ext->cursor_mng == 0 ? false : true;
322 }
323 
324 /*=====================
325  * Other functions
326  *====================*/
327 
328 /**
329  * Default keyboard event to add characters to the Text area and change the map.
330  * If a custom `event_cb` is added to the keyboard this function be called from it to handle the
331  * button clicks
332  * @param kb pointer to a  keyboard
333  * @param event the triggering event
334  */
lv_keyboard_def_event_cb(lv_obj_t * kb,lv_event_t event)335 void lv_keyboard_def_event_cb(lv_obj_t * kb, lv_event_t event)
336 {
337     LV_ASSERT_OBJ(kb, LV_OBJX_NAME);
338 
339     if(event != LV_EVENT_VALUE_CHANGED) return;
340 
341     lv_keyboard_ext_t * ext = lv_obj_get_ext_attr(kb);
342     uint16_t btn_id   = lv_btnmatrix_get_active_btn(kb);
343     if(btn_id == LV_BTNMATRIX_BTN_NONE) return;
344     if(lv_btnmatrix_get_btn_ctrl(kb, btn_id, LV_BTNMATRIX_CTRL_HIDDEN | LV_BTNMATRIX_CTRL_DISABLED)) return;
345     if(lv_btnmatrix_get_btn_ctrl(kb, btn_id, LV_BTNMATRIX_CTRL_NO_REPEAT) && event == LV_EVENT_LONG_PRESSED_REPEAT) return;
346 
347     const char * txt = lv_btnmatrix_get_active_btn_text(kb);
348     if(txt == NULL) return;
349 
350     /*Do the corresponding action according to the text of the button*/
351     if(strcmp(txt, "abc") == 0) {
352         ext->mode = LV_KEYBOARD_MODE_TEXT_LOWER;
353         lv_btnmatrix_set_map(kb, kb_map[LV_KEYBOARD_MODE_TEXT_LOWER]);
354         lv_btnmatrix_set_ctrl_map(kb, kb_ctrl[LV_KEYBOARD_MODE_TEXT_LOWER]);
355         return;
356     }
357     else if(strcmp(txt, "ABC") == 0) {
358         ext->mode = LV_KEYBOARD_MODE_TEXT_UPPER;
359         lv_btnmatrix_set_map(kb, kb_map[LV_KEYBOARD_MODE_TEXT_UPPER]);
360         lv_btnmatrix_set_ctrl_map(kb, kb_ctrl[LV_KEYBOARD_MODE_TEXT_UPPER]);
361         return;
362     }
363     else if(strcmp(txt, "1#") == 0) {
364         ext->mode = LV_KEYBOARD_MODE_SPECIAL;
365         lv_btnmatrix_set_map(kb, kb_map[LV_KEYBOARD_MODE_SPECIAL]);
366         lv_btnmatrix_set_ctrl_map(kb, kb_ctrl[LV_KEYBOARD_MODE_SPECIAL]);
367         return;
368     }
369     else if(strcmp(txt, LV_SYMBOL_CLOSE) == 0) {
370         if(kb->event_cb != lv_keyboard_def_event_cb) {
371             lv_res_t res = lv_event_send(kb, LV_EVENT_CANCEL, NULL);
372             if(res != LV_RES_OK) return;
373         }
374         else {
375             lv_keyboard_set_textarea(kb, NULL); /*De-assign the text area  to hide it cursor if needed*/
376             lv_obj_del(kb);
377             return;
378         }
379         return;
380     }
381     else if(strcmp(txt, LV_SYMBOL_OK) == 0) {
382         if(kb->event_cb != lv_keyboard_def_event_cb) {
383             lv_res_t res = lv_event_send(kb, LV_EVENT_APPLY, NULL);
384             if(res != LV_RES_OK) return;
385         }
386         else {
387             lv_keyboard_set_textarea(kb, NULL); /*De-assign the text area to hide it cursor if needed*/
388         }
389         return;
390     }
391 
392     /*Add the characters to the text area if set*/
393     if(ext->ta == NULL) return;
394 
395     if(strcmp(txt, "Enter") == 0 || strcmp(txt, LV_SYMBOL_NEW_LINE) == 0)
396         lv_textarea_add_char(ext->ta, '\n');
397     else if(strcmp(txt, LV_SYMBOL_LEFT) == 0)
398         lv_textarea_cursor_left(ext->ta);
399     else if(strcmp(txt, LV_SYMBOL_RIGHT) == 0)
400         lv_textarea_cursor_right(ext->ta);
401     else if(strcmp(txt, LV_SYMBOL_BACKSPACE) == 0)
402         lv_textarea_del_char(ext->ta);
403     else if(strcmp(txt, "+/-") == 0) {
404         uint16_t cur        = lv_textarea_get_cursor_pos(ext->ta);
405         const char * ta_txt = lv_textarea_get_text(ext->ta);
406         if(ta_txt[0] == '-') {
407             lv_textarea_set_cursor_pos(ext->ta, 1);
408             lv_textarea_del_char(ext->ta);
409             lv_textarea_add_char(ext->ta, '+');
410             lv_textarea_set_cursor_pos(ext->ta, cur);
411         }
412         else if(ta_txt[0] == '+') {
413             lv_textarea_set_cursor_pos(ext->ta, 1);
414             lv_textarea_del_char(ext->ta);
415             lv_textarea_add_char(ext->ta, '-');
416             lv_textarea_set_cursor_pos(ext->ta, cur);
417         }
418         else {
419             lv_textarea_set_cursor_pos(ext->ta, 0);
420             lv_textarea_add_char(ext->ta, '-');
421             lv_textarea_set_cursor_pos(ext->ta, cur + 1);
422         }
423     }
424     else {
425         lv_textarea_add_text(ext->ta, txt);
426     }
427 }
428 
429 /**********************
430  *   STATIC FUNCTIONS
431  **********************/
432 
433 /**
434  * Signal function of the keyboard
435  * @param kb pointer to a keyboard object
436  * @param sign a signal type from lv_signal_t enum
437  * @param param pointer to a signal specific variable
438  * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
439  */
lv_keyboard_signal(lv_obj_t * kb,lv_signal_t sign,void * param)440 static lv_res_t lv_keyboard_signal(lv_obj_t * kb, lv_signal_t sign, void * param)
441 {
442     lv_res_t res;
443 
444     /* Include the ancient signal function */
445     res = ancestor_signal(kb, sign, param);
446     if(res != LV_RES_OK) return res;
447     if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME);
448 
449     if(sign == LV_SIGNAL_CLEANUP) {
450         /*Nothing to cleanup. (No dynamically allocated memory in 'ext')*/
451     }
452     else if(sign == LV_SIGNAL_FOCUS) {
453         lv_keyboard_ext_t * ext = lv_obj_get_ext_attr(kb);
454         /*Show the cursor of the Text area if cursor management is enabled*/
455         if(ext->ta && ext->cursor_mng) {
456             lv_textarea_set_cursor_hidden(ext->ta, false);
457         }
458     }
459     else if(sign == LV_SIGNAL_DEFOCUS) {
460         lv_keyboard_ext_t * ext = lv_obj_get_ext_attr(kb);
461         /*Show the cursor of the Text area if cursor management is enabled*/
462         if(ext->ta && ext->cursor_mng) {
463             lv_textarea_set_cursor_hidden(ext->ta, true);
464         }
465     }
466 
467     return res;
468 }
469 
470 /**
471  * Update the key map for the current mode
472  * @param kb pointer to a keyboard object
473  */
lv_keyboard_update_map(lv_obj_t * kb)474 static void lv_keyboard_update_map(lv_obj_t * kb)
475 {
476     lv_keyboard_ext_t * ext = lv_obj_get_ext_attr(kb);
477     lv_btnmatrix_set_map(kb, kb_map[ext->mode]);
478     lv_btnmatrix_set_ctrl_map(kb, kb_ctrl[ext->mode]);
479 }
480 
481 #endif
482