1 
2 /**
3  * @file lv_keyboard.c
4  *
5  */
6 
7 /*********************
8  *      INCLUDES
9  *********************/
10 #include "lv_keyboard.h"
11 #if LV_USE_KEYBOARD
12 
13 #include "../../../widgets/lv_textarea.h"
14 #include "../../../misc/lv_assert.h"
15 
16 #include <stdlib.h>
17 
18 /*********************
19  *      DEFINES
20  *********************/
21 #define MY_CLASS    &lv_keyboard_class
22 #define LV_KB_BTN(width) LV_BTNMATRIX_CTRL_POPOVER | width
23 
24 /**********************
25  *      TYPEDEFS
26  **********************/
27 
28 /**********************
29  *  STATIC PROTOTYPES
30  **********************/
31 static void lv_keyboard_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
32 
33 static void lv_keyboard_update_map(lv_obj_t * obj);
34 
35 static void lv_keyboard_update_ctrl_map(lv_obj_t * obj);
36 
37 /**********************
38  *  STATIC VARIABLES
39  **********************/
40 const lv_obj_class_t lv_keyboard_class = {
41     .constructor_cb = lv_keyboard_constructor,
42     .width_def = LV_PCT(100),
43     .height_def = LV_PCT(50),
44     .instance_size = sizeof(lv_keyboard_t),
45     .editable = 1,
46     .base_class = &lv_btnmatrix_class
47 };
48 
49 static const char * const default_kb_map_lc[] = {"1#", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", LV_SYMBOL_BACKSPACE, "\n",
50                                                  "ABC", "a", "s", "d", "f", "g", "h", "j", "k", "l", LV_SYMBOL_NEW_LINE, "\n",
51                                                  "_", "-", "z", "x", "c", "v", "b", "n", "m", ".", ",", ":", "\n",
52                                                  LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, ""
53                                                 };
54 
55 static const lv_btnmatrix_ctrl_t default_kb_ctrl_lc_map[] = {
56     LV_KEYBOARD_CTRL_BTN_FLAGS | 5, LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_BTNMATRIX_CTRL_CHECKED | 7,
57     LV_KEYBOARD_CTRL_BTN_FLAGS | 6, LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_BTNMATRIX_CTRL_CHECKED | 7,
58     LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1),
59     LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 6, LV_BTNMATRIX_CTRL_CHECKED | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2
60 };
61 
62 static const char * const default_kb_map_uc[] = {"1#", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", LV_SYMBOL_BACKSPACE, "\n",
63                                                  "abc", "A", "S", "D", "F", "G", "H", "J", "K", "L", LV_SYMBOL_NEW_LINE, "\n",
64                                                  "_", "-", "Z", "X", "C", "V", "B", "N", "M", ".", ",", ":", "\n",
65                                                  LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, ""
66                                                 };
67 
68 static const lv_btnmatrix_ctrl_t default_kb_ctrl_uc_map[] = {
69     LV_KEYBOARD_CTRL_BTN_FLAGS | 5, LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_BTNMATRIX_CTRL_CHECKED | 7,
70     LV_KEYBOARD_CTRL_BTN_FLAGS | 6, LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_BTNMATRIX_CTRL_CHECKED | 7,
71     LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1),
72     LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 6, LV_BTNMATRIX_CTRL_CHECKED | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2
73 };
74 
75 static const char * const default_kb_map_spec[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", LV_SYMBOL_BACKSPACE, "\n",
76                                                    "abc", "+", "-", "/", "*", "=", "%", "!", "?", "#", "<", ">", "\n",
77                                                    "\\",  "@", "$", "(", ")", "{", "}", "[", "]", ";", "\"", "'", "\n",
78                                                    LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, ""
79                                                   };
80 
81 static const lv_btnmatrix_ctrl_t default_kb_ctrl_spec_map[] = {
82     LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | 2,
83     LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1),
84     LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1),
85     LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 6, LV_BTNMATRIX_CTRL_CHECKED | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2
86 };
87 
88 static const char * const default_kb_map_num[] = {"1", "2", "3", LV_SYMBOL_KEYBOARD, "\n",
89                                                   "4", "5", "6", LV_SYMBOL_OK, "\n",
90                                                   "7", "8", "9", LV_SYMBOL_BACKSPACE, "\n",
91                                                   "+/-", "0", ".", LV_SYMBOL_LEFT, LV_SYMBOL_RIGHT, ""
92                                                  };
93 
94 static const lv_btnmatrix_ctrl_t default_kb_ctrl_num_map[] = {
95     1, 1, 1, LV_KEYBOARD_CTRL_BTN_FLAGS | 2,
96     1, 1, 1, LV_KEYBOARD_CTRL_BTN_FLAGS | 2,
97     1, 1, 1, 2,
98     1, 1, 1, 1, 1
99 };
100 
101 static const char * * kb_map[9] = {
102     (const char * *)default_kb_map_lc,
103     (const char * *)default_kb_map_uc,
104     (const char * *)default_kb_map_spec,
105     (const char * *)default_kb_map_num,
106     (const char * *)default_kb_map_lc,
107     (const char * *)default_kb_map_lc,
108     (const char * *)default_kb_map_lc,
109     (const char * *)default_kb_map_lc,
110     (const char * *)NULL,
111 };
112 static const lv_btnmatrix_ctrl_t * kb_ctrl[9] = {
113     default_kb_ctrl_lc_map,
114     default_kb_ctrl_uc_map,
115     default_kb_ctrl_spec_map,
116     default_kb_ctrl_num_map,
117     default_kb_ctrl_lc_map,
118     default_kb_ctrl_lc_map,
119     default_kb_ctrl_lc_map,
120     default_kb_ctrl_lc_map,
121     NULL,
122 };
123 
124 /**********************
125  *      MACROS
126  **********************/
127 
128 /**********************
129  *   GLOBAL FUNCTIONS
130  **********************/
131 
132 /**
133  * Create a Keyboard object
134  * @param parent pointer to an object, it will be the parent of the new keyboard
135  * @return pointer to the created keyboard
136  */
lv_keyboard_create(lv_obj_t * parent)137 lv_obj_t * lv_keyboard_create(lv_obj_t * parent)
138 {
139     LV_LOG_INFO("begin");
140     lv_obj_t * obj = lv_obj_class_create_obj(&lv_keyboard_class, parent);
141     lv_obj_class_init_obj(obj);
142     return obj;
143 }
144 
145 /*=====================
146  * Setter functions
147  *====================*/
148 
149 /**
150  * Assign a Text Area to the Keyboard. The pressed characters will be put there.
151  * @param kb pointer to a Keyboard object
152  * @param ta pointer to a Text Area object to write there
153  */
lv_keyboard_set_textarea(lv_obj_t * obj,lv_obj_t * ta)154 void lv_keyboard_set_textarea(lv_obj_t * obj, lv_obj_t * ta)
155 {
156     if(ta) {
157         LV_ASSERT_OBJ(ta, &lv_textarea_class);
158     }
159 
160     LV_ASSERT_OBJ(obj, MY_CLASS);
161     lv_keyboard_t * keyboard = (lv_keyboard_t *)obj;
162 
163     /*Hide the cursor of the old Text area if cursor management is enabled*/
164     if(keyboard->ta) {
165         lv_obj_clear_state(obj, LV_STATE_FOCUSED);
166     }
167 
168     keyboard->ta = ta;
169 
170     /*Show the cursor of the new Text area if cursor management is enabled*/
171     if(keyboard->ta) {
172         lv_obj_add_flag(obj, LV_STATE_FOCUSED);
173     }
174 }
175 
176 /**
177  * Set a new a mode (text or number map)
178  * @param kb pointer to a Keyboard object
179  * @param mode the mode from 'lv_keyboard_mode_t'
180  */
lv_keyboard_set_mode(lv_obj_t * obj,lv_keyboard_mode_t mode)181 void lv_keyboard_set_mode(lv_obj_t * obj, lv_keyboard_mode_t mode)
182 {
183     LV_ASSERT_OBJ(obj, MY_CLASS);
184     lv_keyboard_t * keyboard = (lv_keyboard_t *)obj;
185     if(keyboard->mode == mode) return;
186 
187     keyboard->mode = mode;
188     lv_keyboard_update_map(obj);
189 }
190 
191 /**
192  * Show the button title in a popover when pressed.
193  * @param kb pointer to a Keyboard object
194  * @param en whether "popovers" mode is enabled
195  */
lv_keyboard_set_popovers(lv_obj_t * obj,bool en)196 void lv_keyboard_set_popovers(lv_obj_t * obj, bool en)
197 {
198     lv_keyboard_t * keyboard = (lv_keyboard_t *)obj;
199 
200     if(keyboard->popovers == en) {
201         return;
202     }
203 
204     keyboard->popovers = en;
205     lv_keyboard_update_ctrl_map(obj);
206 }
207 
208 /**
209  * Set a new map for the keyboard
210  * @param kb pointer to a Keyboard object
211  * @param mode keyboard map to alter 'lv_keyboard_mode_t'
212  * @param map pointer to a string array to describe the map.
213  *            See 'lv_btnmatrix_set_map()' for more info.
214  */
lv_keyboard_set_map(lv_obj_t * obj,lv_keyboard_mode_t mode,const char * map[],const lv_btnmatrix_ctrl_t ctrl_map[])215 void lv_keyboard_set_map(lv_obj_t * obj, lv_keyboard_mode_t mode, const char * map[],
216                          const lv_btnmatrix_ctrl_t ctrl_map[])
217 {
218     kb_map[mode] = map;
219     kb_ctrl[mode] = ctrl_map;
220     lv_keyboard_update_map(obj);
221 }
222 
223 /*=====================
224  * Getter functions
225  *====================*/
226 
227 /**
228  * Assign a Text Area to the Keyboard. The pressed characters will be put there.
229  * @param kb pointer to a Keyboard object
230  * @return pointer to the assigned Text Area object
231  */
lv_keyboard_get_textarea(const lv_obj_t * obj)232 lv_obj_t * lv_keyboard_get_textarea(const lv_obj_t * obj)
233 {
234     LV_ASSERT_OBJ(obj, MY_CLASS);
235     lv_keyboard_t * keyboard = (lv_keyboard_t *)obj;
236     return keyboard->ta;
237 }
238 
239 /**
240  * Set a new a mode (text or number map)
241  * @param kb pointer to a Keyboard object
242  * @return the current mode from 'lv_keyboard_mode_t'
243  */
lv_keyboard_get_mode(const lv_obj_t * obj)244 lv_keyboard_mode_t lv_keyboard_get_mode(const lv_obj_t * obj)
245 {
246     LV_ASSERT_OBJ(obj, MY_CLASS);
247     lv_keyboard_t * keyboard = (lv_keyboard_t *)obj;
248     return keyboard->mode;
249 }
250 
251 /**
252  * Tell whether "popovers" mode is enabled or not.
253  * @param kb pointer to a Keyboard object
254  * @return true: "popovers" mode is enabled; false: disabled
255  */
lv_btnmatrix_get_popovers(const lv_obj_t * obj)256 bool lv_btnmatrix_get_popovers(const lv_obj_t * obj)
257 {
258     lv_keyboard_t * keyboard = (lv_keyboard_t *)obj;
259     return keyboard->popovers;
260 }
261 
262 /*=====================
263  * Other functions
264  *====================*/
265 
266 /**
267  * Default keyboard event to add characters to the Text area and change the map.
268  * If a custom `event_cb` is added to the keyboard this function can be called from it to handle the
269  * button clicks
270  * @param kb pointer to a keyboard
271  * @param event the triggering event
272  */
lv_keyboard_def_event_cb(lv_event_t * e)273 void lv_keyboard_def_event_cb(lv_event_t * e)
274 {
275     lv_obj_t * obj = lv_event_get_target(e);
276 
277     LV_ASSERT_OBJ(obj, MY_CLASS);
278     lv_keyboard_t * keyboard = (lv_keyboard_t *)obj;
279     uint16_t btn_id   = lv_btnmatrix_get_selected_btn(obj);
280     if(btn_id == LV_BTNMATRIX_BTN_NONE) return;
281 
282     const char * txt = lv_btnmatrix_get_btn_text(obj, lv_btnmatrix_get_selected_btn(obj));
283     if(txt == NULL) return;
284 
285     if(strcmp(txt, "abc") == 0) {
286         keyboard->mode = LV_KEYBOARD_MODE_TEXT_LOWER;
287         lv_btnmatrix_set_map(obj, kb_map[LV_KEYBOARD_MODE_TEXT_LOWER]);
288         lv_keyboard_update_ctrl_map(obj);
289         return;
290     }
291     else if(strcmp(txt, "ABC") == 0) {
292         keyboard->mode = LV_KEYBOARD_MODE_TEXT_UPPER;
293         lv_btnmatrix_set_map(obj, kb_map[LV_KEYBOARD_MODE_TEXT_UPPER]);
294         lv_keyboard_update_ctrl_map(obj);
295         return;
296     }
297     else if(strcmp(txt, "1#") == 0) {
298         keyboard->mode = LV_KEYBOARD_MODE_SPECIAL;
299         lv_btnmatrix_set_map(obj, kb_map[LV_KEYBOARD_MODE_SPECIAL]);
300         lv_keyboard_update_ctrl_map(obj);
301         return;
302     }
303     else if(strcmp(txt, LV_SYMBOL_CLOSE) == 0 || strcmp(txt, LV_SYMBOL_KEYBOARD) == 0) {
304         lv_res_t res = lv_event_send(obj, LV_EVENT_CANCEL, NULL);
305         if(res != LV_RES_OK) return;
306 
307         if(keyboard->ta) {
308             res = lv_event_send(keyboard->ta, LV_EVENT_CANCEL, NULL);
309             if(res != LV_RES_OK) return;
310         }
311         return;
312     }
313     else if(strcmp(txt, LV_SYMBOL_OK) == 0) {
314         lv_res_t res = lv_event_send(obj, LV_EVENT_READY, NULL);
315         if(res != LV_RES_OK) return;
316 
317         if(keyboard->ta) {
318             res = lv_event_send(keyboard->ta, LV_EVENT_READY, NULL);
319             if(res != LV_RES_OK) return;
320         }
321         return;
322     }
323 
324     /*Add the characters to the text area if set*/
325     if(keyboard->ta == NULL) return;
326 
327     if(strcmp(txt, "Enter") == 0 || strcmp(txt, LV_SYMBOL_NEW_LINE) == 0) {
328         lv_textarea_add_char(keyboard->ta, '\n');
329         if(lv_textarea_get_one_line(keyboard->ta)) {
330             lv_res_t res = lv_event_send(keyboard->ta, LV_EVENT_READY, NULL);
331             if(res != LV_RES_OK) return;
332         }
333     }
334     else if(strcmp(txt, LV_SYMBOL_LEFT) == 0) {
335         lv_textarea_cursor_left(keyboard->ta);
336     }
337     else if(strcmp(txt, LV_SYMBOL_RIGHT) == 0) {
338         lv_textarea_cursor_right(keyboard->ta);
339     }
340     else if(strcmp(txt, LV_SYMBOL_BACKSPACE) == 0) {
341         lv_textarea_del_char(keyboard->ta);
342     }
343     else if(strcmp(txt, "+/-") == 0) {
344         uint16_t cur        = lv_textarea_get_cursor_pos(keyboard->ta);
345         const char * ta_txt = lv_textarea_get_text(keyboard->ta);
346         if(ta_txt[0] == '-') {
347             lv_textarea_set_cursor_pos(keyboard->ta, 1);
348             lv_textarea_del_char(keyboard->ta);
349             lv_textarea_add_char(keyboard->ta, '+');
350             lv_textarea_set_cursor_pos(keyboard->ta, cur);
351         }
352         else if(ta_txt[0] == '+') {
353             lv_textarea_set_cursor_pos(keyboard->ta, 1);
354             lv_textarea_del_char(keyboard->ta);
355             lv_textarea_add_char(keyboard->ta, '-');
356             lv_textarea_set_cursor_pos(keyboard->ta, cur);
357         }
358         else {
359             lv_textarea_set_cursor_pos(keyboard->ta, 0);
360             lv_textarea_add_char(keyboard->ta, '-');
361             lv_textarea_set_cursor_pos(keyboard->ta, cur + 1);
362         }
363     }
364     else {
365         lv_textarea_add_text(keyboard->ta, txt);
366     }
367 }
368 
369 /**********************
370  *   STATIC FUNCTIONS
371  **********************/
372 
lv_keyboard_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)373 static void lv_keyboard_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
374 {
375     LV_UNUSED(class_p);
376     lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICK_FOCUSABLE);
377 
378     lv_keyboard_t * keyboard = (lv_keyboard_t *)obj;
379     keyboard->ta         = NULL;
380     keyboard->mode       = LV_KEYBOARD_MODE_TEXT_LOWER;
381     keyboard->popovers   = 0;
382 
383     lv_obj_align(obj, LV_ALIGN_BOTTOM_MID, 0, 0);
384     lv_obj_add_event_cb(obj, lv_keyboard_def_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
385     lv_obj_set_style_base_dir(obj, LV_BASE_DIR_LTR, 0);
386 
387     lv_keyboard_update_map(obj);
388 }
389 
390 /**
391  * Update the key and control map for the current mode
392  * @param obj pointer to a keyboard object
393  */
lv_keyboard_update_map(lv_obj_t * obj)394 static void lv_keyboard_update_map(lv_obj_t * obj)
395 {
396     lv_keyboard_t * keyboard = (lv_keyboard_t *)obj;
397     lv_btnmatrix_set_map(obj, kb_map[keyboard->mode]);
398     lv_keyboard_update_ctrl_map(obj);
399 }
400 
401 /**
402  * Update the control map for the current mode
403  * @param obj pointer to a keyboard object
404  */
lv_keyboard_update_ctrl_map(lv_obj_t * obj)405 static void lv_keyboard_update_ctrl_map(lv_obj_t * obj)
406 {
407     lv_keyboard_t * keyboard = (lv_keyboard_t *)obj;
408 
409     if(keyboard->popovers) {
410         /*Apply the current control map (already includes LV_BTNMATRIX_CTRL_POPOVER flags)*/
411         lv_btnmatrix_set_ctrl_map(obj, kb_ctrl[keyboard->mode]);
412     }
413     else {
414         /*Make a copy of the current control map*/
415         lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj;
416         lv_btnmatrix_ctrl_t * ctrl_map = lv_mem_alloc(btnm->btn_cnt * sizeof(lv_btnmatrix_ctrl_t));
417         lv_memcpy(ctrl_map, kb_ctrl[keyboard->mode], sizeof(lv_btnmatrix_ctrl_t) * btnm->btn_cnt);
418 
419         /*Remove all LV_BTNMATRIX_CTRL_POPOVER flags*/
420         for(uint16_t i = 0; i < btnm->btn_cnt; i++) {
421             ctrl_map[i] &= (~LV_BTNMATRIX_CTRL_POPOVER);
422         }
423 
424         /*Apply new control map and clean up*/
425         lv_btnmatrix_set_ctrl_map(obj, ctrl_map);
426         lv_mem_free(ctrl_map);
427     }
428 }
429 
430 #endif  /*LV_USE_KEYBOARD*/
431