Merge branch 'master' into gsoc-dir-split
[kugel-rb.git] / apps / recorder / keyboard.c
blob8dea52799fdc398ba2c840e3c5f29020c7858a2d
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 by Björn Stenberg
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
21 #include "kernel.h"
22 #include "system.h"
23 #include "string-extra.h"
24 #include "font.h"
25 #include "screens.h"
26 #include "talk.h"
27 #include "settings.h"
28 #include "misc.h"
29 #include "rbunicode.h"
30 #include "buttonbar.h"
31 #include "logf.h"
32 #include "hangul.h"
33 #include "action.h"
34 #include "icon.h"
35 #include "pcmbuf.h"
36 #include "lang.h"
37 #include "keyboard.h"
38 #include "viewport.h"
39 #include "file.h"
40 #include "splash.h"
42 #ifndef O_BINARY
43 #define O_BINARY 0
44 #endif
47 #define DEFAULT_MARGIN 6
48 #define KBD_BUF_SIZE 500
50 #ifdef HAVE_TOUCHSCREEN
51 #define MIN_GRID_SIZE 16
52 #define GRID_SIZE(s, x) \
53 ((s) == SCREEN_MAIN && MIN_GRID_SIZE > (x) ? MIN_GRID_SIZE: (x))
54 #endif
56 #if (CONFIG_KEYPAD == IRIVER_H100_PAD) \
57 || (CONFIG_KEYPAD == IRIVER_H300_PAD) \
58 || (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
59 || (CONFIG_KEYPAD == IPOD_3G_PAD) \
60 || (CONFIG_KEYPAD == IPOD_4G_PAD) \
61 || (CONFIG_KEYPAD == IRIVER_H10_PAD) \
62 || (CONFIG_KEYPAD == GIGABEAT_PAD) \
63 || (CONFIG_KEYPAD == GIGABEAT_S_PAD) \
64 || (CONFIG_KEYPAD == MROBE100_PAD) \
65 || (CONFIG_KEYPAD == SANSA_E200_PAD) \
66 || (CONFIG_KEYPAD == PHILIPS_HDD1630_PAD) \
67 || (CONFIG_KEYPAD == PHILIPS_SA9200_PAD) \
68 || (CONFIG_KEYPAD == PBELL_VIBE500_PAD)
69 /* certain key combos toggle input mode between keyboard input and Morse input */
70 #define KBD_TOGGLE_INPUT
71 #endif
73 #define CHANGED_PICKER 1
74 #define CHANGED_CURSOR 2
75 #define CHANGED_TEXT 3
77 struct keyboard_parameters
79 unsigned short kbd_buf[KBD_BUF_SIZE];
80 unsigned short max_line_len;
81 int default_lines;
82 int last_k;
83 int last_i;
84 int font_w;
85 int font_h;
86 int text_w;
87 int curfont;
88 int main_y;
89 #ifdef HAVE_MORSE_INPUT
90 int old_main_y;
91 #endif
92 int max_chars;
93 int max_chars_text;
94 int lines;
95 int pages;
96 int keyboard_margin;
97 int curpos;
98 int leftpos;
99 int page;
100 int x;
101 int y;
102 bool line_edit;
103 #ifdef HAVE_TOUCHSCREEN
104 bool show_buttons;
105 #endif
108 struct edit_state
110 char* text;
111 int buflen;
112 int len_utf8;
113 int editpos; /* Edit position on all screens */
114 bool cur_blink; /* Cursor on/off flag */
115 bool hangul;
116 unsigned short hlead, hvowel, htail;
117 #ifdef HAVE_MORSE_INPUT
118 bool morse_mode;
119 bool morse_reading;
120 unsigned char morse_code;
121 int morse_tick;
122 #endif
123 int changed;
126 static struct keyboard_parameters kbd_param[NB_SCREENS];
127 static bool kbd_loaded = false;
129 #ifdef HAVE_MORSE_INPUT
130 /* FIXME: We should put this to a configuration file. */
131 static const char *morse_alphabets =
132 "abcdefghijklmnopqrstuvwxyz1234567890,.?-@ ";
133 static const unsigned char morse_codes[] = {
134 0x05,0x18,0x1a,0x0c,0x02,0x12,0x0e,0x10,0x04,0x17,0x0d,0x14,0x07,
135 0x06,0x0f,0x16,0x1d,0x0a,0x08,0x03,0x09,0x11,0x0b,0x19,0x1b,0x1c,
136 0x2f,0x27,0x23,0x21,0x20,0x30,0x38,0x3c,0x3e,0x3f,
137 0x73,0x55,0x4c,0x61,0x5a,0x80 };
138 #endif
140 /* Loads a custom keyboard into memory
141 call with NULL to reset keyboard */
142 int load_kbd(unsigned char* filename)
144 int fd, l;
145 int i, line_len, max_line_len;
146 unsigned char buf[4];
147 unsigned short *pbuf;
149 if (filename == NULL)
151 kbd_loaded = false;
152 return 0;
155 fd = open_utf8(filename, O_RDONLY|O_BINARY);
156 if (fd < 0)
157 return 1;
159 pbuf = kbd_param[0].kbd_buf;
160 line_len = 0;
161 max_line_len = 1;
162 i = 1;
163 while (read(fd, buf, 1) == 1 && i < KBD_BUF_SIZE-1)
165 /* check how many bytes to read for this character */
166 static const unsigned char sizes[4] = { 0x80, 0xe0, 0xf0, 0xf5 };
167 size_t count;
168 unsigned short ch;
170 for (count = 0; count < ARRAYLEN(sizes); count++)
172 if (buf[0] < sizes[count])
173 break;
176 if (count >= ARRAYLEN(sizes))
177 continue; /* Invalid size. */
179 if (read(fd, &buf[1], count) != (ssize_t)count)
181 close(fd);
182 kbd_loaded = false;
183 return 1;
186 utf8decode(buf, &ch);
187 if (ch != 0xFEFF && ch != '\r') /* skip BOM & carriage returns */
189 i++;
190 if (ch == '\n')
192 if (max_line_len < line_len)
193 max_line_len = line_len;
194 *pbuf = line_len;
195 pbuf += line_len + 1;
196 line_len = 0;
198 else
199 pbuf[++line_len] = ch;
203 close(fd);
204 kbd_loaded = true;
206 if (max_line_len < line_len)
207 max_line_len = line_len;
208 if (i == 1 || line_len != 0) /* ignore last empty line */
210 *pbuf = line_len;
211 pbuf += line_len + 1;
213 *pbuf = 0xFEFF; /* mark end of characters */
214 i++;
215 FOR_NB_SCREENS(l)
217 struct keyboard_parameters *pm = &kbd_param[l];
218 #if NB_SCREENS > 1
219 if (l > 0)
220 memcpy(pm->kbd_buf, kbd_param[0].kbd_buf, i*sizeof(unsigned short));
221 #endif
222 /* initialize parameters */
223 pm->x = pm->y = pm->page = 0;
224 pm->default_lines = 0;
225 pm->max_line_len = max_line_len;
228 return 0;
231 /* helper function to spell a char */
232 static void kbd_spellchar(unsigned short c)
234 unsigned char tmp[5];
235 /* store char to pass to talk_spell */
236 unsigned char* utf8 = utf8encode(c, tmp);
237 *utf8 = 0;
239 if (c == ' ')
240 talk_id(VOICE_BLANK, false);
241 else
242 talk_spell(tmp, false);
245 static void kbd_inschar(struct edit_state *state, unsigned short ch)
247 int i, j, len;
248 unsigned char tmp[4];
249 unsigned char* utf8;
251 len = strlen(state->text);
252 utf8 = utf8encode(ch, tmp);
253 j = (long)utf8 - (long)tmp;
255 if (len + j < state->buflen)
257 i = utf8seek(state->text, state->editpos);
258 utf8 = state->text + i;
259 memmove(utf8 + j, utf8, len - i + 1);
260 memcpy(utf8, tmp, j);
261 state->editpos++;
262 state->changed = CHANGED_TEXT;
266 static void kbd_delchar(struct edit_state *state)
268 int i, j, len;
269 unsigned char* utf8;
271 if (state->editpos > 0)
273 state->editpos--;
274 len = strlen(state->text);
275 i = utf8seek(state->text, state->editpos);
276 utf8 = state->text + i;
277 j = utf8seek(utf8, 1);
278 memmove(utf8, utf8 + j, len - i - j + 1);
279 state->changed = CHANGED_TEXT;
283 /* Lookup k value based on state of param (pm) */
284 static unsigned short get_kbd_ch(struct keyboard_parameters *pm, int x, int y)
286 int i = 0, k = pm->page*pm->lines + y, n;
287 unsigned short *pbuf;
288 if (k >= pm->last_k)
290 i = pm->last_i;
291 k -= pm->last_k;
293 for (pbuf = &pm->kbd_buf[i]; (i = *pbuf) != 0xFEFF; pbuf += i + 1)
295 n = i ? (i + pm->max_chars - 1) / pm->max_chars : 1;
296 if (k < n) break;
297 k -= n;
299 if (y == 0 && i != 0xFEFF)
301 pm->last_k = pm->page*pm->lines - k;
302 pm->last_i = pbuf - pm->kbd_buf;
304 k = k * pm->max_chars + x;
305 return (*pbuf != 0xFEFF && k < *pbuf)? pbuf[k+1]: ' ';
308 static void kbd_calc_params(struct keyboard_parameters *pm,
309 struct screen *sc, struct edit_state *state);
310 static void kbd_draw_picker(struct keyboard_parameters *pm,
311 struct screen *sc, struct edit_state *state);
312 static void kbd_draw_edit_line(struct keyboard_parameters *pm,
313 struct screen *sc, struct edit_state *state);
314 #ifdef HAVE_TOUCHSCREEN
315 static void kbd_draw_buttons(struct keyboard_parameters *pm, struct screen *sc);
316 static int keyboard_touchscreen(struct keyboard_parameters *pm,
317 struct screen *sc, struct edit_state *state);
318 #endif
319 static void kbd_insert_selected(struct keyboard_parameters *pm,
320 struct edit_state *state);
321 static void kbd_backspace(struct edit_state *state);
322 static void kbd_move_cursor(struct edit_state *state, int dir);
323 static void kbd_move_picker_horizontal(struct keyboard_parameters *pm,
324 struct edit_state *state, int dir);
325 static void kbd_move_picker_vertical(struct keyboard_parameters *pm,
326 struct edit_state *state, int dir);
328 int kbd_input(char* text, int buflen)
330 bool done = false;
331 #ifdef CPU_ARM
332 /* This seems to keep the sizes for ARM way down */
333 struct keyboard_parameters * volatile param = kbd_param;
334 #else
335 struct keyboard_parameters * const param = kbd_param;
336 #endif
337 struct edit_state state;
338 int l; /* screen loop variable */
339 unsigned short ch;
340 int ret = 0; /* assume success */
341 FOR_NB_SCREENS(l)
343 viewportmanager_theme_enable(l, false, NULL);
346 #ifdef HAVE_BUTTONBAR
347 struct gui_buttonbar buttonbar;
348 bool buttonbar_config = global_settings.buttonbar;
350 global_settings.buttonbar = true;
351 gui_buttonbar_init(&buttonbar);
352 gui_buttonbar_set_display(&buttonbar, &screens[SCREEN_MAIN]);
353 #endif
355 /* initialize state */
356 state.text = text;
357 state.buflen = buflen;
358 /* Initial edit position is after last character */
359 state.editpos = utf8length(state.text);
360 state.cur_blink = true;
361 #ifdef HAVE_MORSE_INPUT
362 state.morse_mode = global_settings.morse_input;
363 state.morse_reading = false;
364 #endif
365 state.hangul = false;
366 state.changed = 0;
368 if (!kbd_loaded)
370 /* Copy default keyboard to buffer */
371 FOR_NB_SCREENS(l)
373 struct keyboard_parameters *pm = &param[l];
374 unsigned short *pbuf;
375 const unsigned char *p;
376 int len = 0;
378 #if LCD_WIDTH >= 160 && LCD_HEIGHT >= 96
379 struct screen *sc = &screens[l];
381 if (sc->getwidth() >= 160 && sc->getheight() >= 96)
383 p = "ABCDEFG abcdefg !?\" @#$%+'\n"
384 "HIJKLMN hijklmn 789 &_()-`\n"
385 "OPQRSTU opqrstu 456 §|{}/<\n"
386 "VWXYZ., vwxyz.,0123 ~=[]*>\n"
387 "ÀÁÂÃÄÅÆ ÌÍÎÏ ÈÉÊË ¢£¤¥¦§©®\n"
388 "àáâãäåæ ìíîï èéêë «»°ºª¹²³\n"
389 "ÓÒÔÕÖØ ÇÐÞÝß ÙÚÛÜ ¯±×÷¡¿µ·\n"
390 "òóôõöø çðþýÿ ùúûü ¼½¾¬¶¨:;";
392 pm->default_lines = 8;
393 pm->max_line_len = 26;
395 else
396 #endif /* LCD_WIDTH >= 160 && LCD_HEIGHT >= 96 */
398 p = "ABCDEFG !?\" @#$%+'\n"
399 "HIJKLMN 789 &_()-`\n"
400 "OPQRSTU 456 §|{}/<\n"
401 "VWXYZ.,0123 ~=[]*>\n"
403 "abcdefg ¢£¤¥¦§©®¬\n"
404 "hijklmn «»°ºª¹²³¶\n"
405 "opqrstu ¯±×÷¡¿µ·¨\n"
406 "vwxyz., :;¼½¾ \n"
408 "ÀÁÂÃÄÅÆ ÌÍÎÏ ÈÉÊË\n"
409 "àáâãäåæ ìíîï èéêë\n"
410 "ÓÒÔÕÖØ ÇÐÞÝß ÙÚÛÜ\n"
411 "òóôõöø çðþýÿ ùúûü";
413 pm->default_lines = 4;
414 pm->max_line_len = 18;
417 pbuf = pm->kbd_buf;
418 while (*p)
420 p = utf8decode(p, &pbuf[len+1]);
421 if (pbuf[len+1] == '\n')
423 *pbuf = len;
424 pbuf += len+1;
425 len = 0;
427 else
428 len++;
430 *pbuf = len;
431 pbuf[len+1] = 0xFEFF; /* mark end of characters */
433 /* initialize parameters */
434 pm->x = pm->y = pm->page = 0;
436 kbd_loaded = true;
439 FOR_NB_SCREENS(l)
441 struct keyboard_parameters *pm = &param[l];
442 struct screen *sc = &screens[l];
443 kbd_calc_params(pm, sc, &state);
446 if (global_settings.talk_menu) /* voice UI? */
447 talk_spell(state.text, true); /* spell initial text */
449 while (!done)
451 /* These declarations are assigned to the screen on which the key
452 action occurred - pointers save a lot of space over array notation
453 when accessing the same array element countless times */
454 int button;
455 #if NB_SCREENS > 1
456 int button_screen;
457 #else
458 const int button_screen = 0;
459 #endif
460 struct keyboard_parameters *pm;
461 struct screen *sc;
463 state.len_utf8 = utf8length(state.text);
465 FOR_NB_SCREENS(l)
467 /* declare scoped pointers inside screen loops - hide the
468 declarations from previous block level */
469 struct keyboard_parameters *pm = &param[l];
470 struct screen *sc = &screens[l];
471 sc->clear_display();
472 kbd_draw_picker(pm, sc, &state);
473 kbd_draw_edit_line(pm, sc, &state);
474 #ifdef HAVE_TOUCHSCREEN
475 if (pm->show_buttons)
476 kbd_draw_buttons(pm, sc);
477 #endif
480 #ifdef HAVE_BUTTONBAR
481 /* draw the button bar */
482 gui_buttonbar_set(&buttonbar, "Shift", "OK", "Del");
483 gui_buttonbar_draw(&buttonbar);
484 #endif
486 FOR_NB_SCREENS(l)
487 screens[l].update();
489 state.cur_blink = !state.cur_blink;
491 button = get_action(
492 #ifdef HAVE_MORSE_INPUT
493 state.morse_mode? CONTEXT_MORSE_INPUT:
494 #endif
495 CONTEXT_KEYBOARD, HZ/2);
496 #if NB_SCREENS > 1
497 button_screen = (get_action_statuscode(NULL) & ACTION_REMOTE) ? 1 : 0;
498 #endif
499 pm = &param[button_screen];
500 sc = &screens[button_screen];
501 #ifdef HAVE_TOUCHSCREEN
502 if (button == ACTION_TOUCHSCREEN)
503 button = keyboard_touchscreen(pm, sc, &state);
504 #endif
506 /* Remap some buttons to allow to move
507 * cursor in line edit mode and morse mode. */
508 if (pm->line_edit
509 #ifdef HAVE_MORSE_INPUT
510 || state.morse_mode
511 #endif /* HAVE_MORSE_INPUT */
514 if (button == ACTION_KBD_LEFT)
515 button = ACTION_KBD_CURSOR_LEFT;
516 if (button == ACTION_KBD_RIGHT)
517 button = ACTION_KBD_CURSOR_RIGHT;
520 switch ( button )
522 case ACTION_KBD_DONE:
523 /* accepts what was entered and continues */
524 ret = 0;
525 done = true;
526 break;
528 case ACTION_KBD_ABORT:
529 ret = -1;
530 done = true;
531 break;
533 case ACTION_KBD_PAGE_FLIP:
534 #ifdef HAVE_MORSE_INPUT
535 if (state.morse_mode)
536 break;
537 #endif
538 if (++pm->page >= pm->pages)
539 pm->page = 0;
541 state.changed = CHANGED_PICKER;
542 break;
544 case ACTION_KBD_RIGHT:
545 kbd_move_picker_horizontal(pm, &state, 1);
546 break;
548 case ACTION_KBD_LEFT:
549 kbd_move_picker_horizontal(pm, &state, -1);
550 break;
552 case ACTION_KBD_DOWN:
553 kbd_move_picker_vertical(pm, &state, 1);
554 break;
556 case ACTION_KBD_UP:
557 kbd_move_picker_vertical(pm, &state, -1);
558 break;
560 #ifdef HAVE_MORSE_INPUT
561 #ifdef KBD_TOGGLE_INPUT
562 case ACTION_KBD_MORSE_INPUT:
563 state.morse_mode = !state.morse_mode;
564 state.changed = CHANGED_PICKER;
566 FOR_NB_SCREENS(l)
568 struct keyboard_parameters *pm = &param[l];
569 int y = pm->main_y;
570 pm->main_y = pm->old_main_y;
571 pm->old_main_y = y;
573 break;
574 #endif /* KBD_TOGGLE_INPUT */
576 case ACTION_KBD_MORSE_SELECT:
577 if (state.morse_mode && state.morse_reading)
579 state.morse_code <<= 1;
580 if ((current_tick - state.morse_tick) > HZ/5)
581 state.morse_code |= 0x01;
583 break;
584 #endif /* HAVE_MORSE_INPUT */
586 case ACTION_KBD_SELECT:
587 /* select doubles as backspace in line_edit */
588 if (pm->line_edit)
589 kbd_backspace(&state);
590 else
591 #ifdef HAVE_MORSE_INPUT
592 if (state.morse_mode)
594 state.morse_tick = current_tick;
596 if (!state.morse_reading)
598 state.morse_reading = true;
599 state.morse_code = 1;
602 else
603 #endif /* HAVE_MORSE_INPUT */
604 kbd_insert_selected(pm, &state);
605 break;
607 case ACTION_KBD_BACKSPACE:
608 kbd_backspace(&state);
609 break;
611 case ACTION_KBD_CURSOR_RIGHT:
612 kbd_move_cursor(&state, 1);
613 break;
615 case ACTION_KBD_CURSOR_LEFT:
616 kbd_move_cursor(&state, -1);
617 break;
619 case ACTION_NONE:
620 #ifdef HAVE_MORSE_INPUT
621 if (state.morse_reading)
623 int j;
624 logf("Morse: 0x%02x", state.morse_code);
625 state.morse_reading = false;
627 for (j = 0; morse_alphabets[j] != '\0'; j++)
629 if (morse_codes[j] == state.morse_code)
630 break ;
633 if (morse_alphabets[j] == '\0')
635 logf("Morse code not found");
636 break ;
639 /* turn off hangul input */
640 state.hangul = false;
641 kbd_inschar(&state, morse_alphabets[j]);
643 #endif /* HAVE_MORSE_INPUT */
644 break;
646 default:
647 if (default_event_handler(button) == SYS_USB_CONNECTED)
649 FOR_NB_SCREENS(l)
650 screens[l].setfont(FONT_SYSFIXED);
652 break;
654 } /* end switch */
656 if (button != ACTION_NONE)
658 state.cur_blink = true;
660 if (global_settings.talk_menu) /* voice UI? */
662 if (state.changed == CHANGED_PICKER)
664 if (pm->line_edit)
666 talk_id(VOICE_EDIT, false);
668 else
669 #ifdef HAVE_MORSE_INPUT
670 /* FIXME: We should talk something like Morse mode.. */
671 if (!state.morse_mode)
672 #endif
674 ch = get_kbd_ch(pm, pm->x, pm->y);
675 kbd_spellchar(ch);
678 else if (state.changed == CHANGED_CURSOR)
680 int c = utf8seek(state.text, state.editpos);
681 kbd_spellchar(state.text[c]);
683 else if (state.changed == CHANGED_TEXT)
684 talk_spell(state.text, false); /* speak revised text */
686 state.changed = 0;
689 #ifdef HAVE_BUTTONBAR
690 global_settings.buttonbar = buttonbar_config;
691 #endif
693 if (ret < 0)
694 splash(HZ/2, ID2P(LANG_CANCEL));
696 #if defined(HAVE_MORSE_INPUT) && defined(KBD_TOGGLE_INPUT)
697 if (global_settings.morse_input != state.morse_mode)
699 global_settings.morse_input = state.morse_mode;
700 settings_save();
702 #endif /* HAVE_MORSE_INPUT && KBD_TOGGLE_INPUT */
704 FOR_NB_SCREENS(l)
706 screens[l].setfont(FONT_UI);
707 viewportmanager_theme_undo(l, false);
709 return ret;
712 static void kbd_calc_params(struct keyboard_parameters *pm,
713 struct screen *sc, struct edit_state *state)
715 struct font* font;
716 const unsigned char *p;
717 unsigned short ch, *pbuf;
718 int icon_w, sc_w, sc_h, w;
719 int i, total_lines;
720 #ifdef HAVE_TOUCHSCREEN
721 int button_h = 0;
722 bool flippage_button = false;
723 pm->show_buttons = (sc->screen_type == SCREEN_MAIN &&
724 (touchscreen_get_mode() == TOUCHSCREEN_POINT));
725 #endif
727 pm->curfont = pm->default_lines ? FONT_SYSFIXED : FONT_UI;
728 font = font_get(pm->curfont);
729 pm->font_h = font->height;
731 /* check if FONT_UI fits the screen */
732 if (2*pm->font_h + 3 + BUTTONBAR_HEIGHT > sc->getheight())
734 pm->curfont = FONT_SYSFIXED;
735 font = font_get(FONT_SYSFIXED);
736 pm->font_h = font->height;
738 #ifdef HAVE_TOUCHSCREEN
739 pm->font_h = GRID_SIZE(sc->screen_type, pm->font_h);
740 #endif
742 /* find max width of keyboard glyphs.
743 * since we're going to be adding spaces,
744 * max width is at least their width */
745 pm->font_w = font_get_width(font, ' ');
746 for (pbuf = pm->kbd_buf; *pbuf != 0xFEFF; pbuf += i)
748 for (i = 0; ++i <= *pbuf; )
750 w = font_get_width(font, pbuf[i]);
751 if (pm->font_w < w)
752 pm->font_w = w;
756 /* Find max width for text string */
757 pm->text_w = pm->font_w;
758 p = state->text;
759 while (*p)
761 p = utf8decode(p, &ch);
762 w = font_get_width(font, ch);
763 if (pm->text_w < w)
764 pm->text_w = w;
767 #ifdef HAVE_TOUCHSCREEN
768 pm->font_w = GRID_SIZE(sc->screen_type, pm->font_w);
769 #endif
770 /* calculate how many characters to put in a row. */
771 icon_w = get_icon_width(sc->screen_type);
772 sc_w = sc->getwidth();
773 if (pm->font_w < sc_w / pm->max_line_len)
774 pm->font_w = sc_w / pm->max_line_len;
775 pm->max_chars = sc_w / pm->font_w;
776 pm->max_chars_text = (sc_w - icon_w * 2 - 2) / pm->text_w;
777 if (pm->max_chars_text < 3 && icon_w > pm->text_w)
778 pm->max_chars_text = sc_w / pm->text_w - 2;
780 /* calculate pm->pages and pm->lines */
781 sc_h = sc->getheight();
782 #ifdef HAVE_TOUCHSCREEN
783 /* add space for buttons */
784 if (pm->show_buttons)
786 /* reserve place for OK/Del/Cancel buttons, use ui font for them */
787 button_h = GRID_SIZE(sc->screen_type, sc->getcharheight());
788 sc_h -= MAX(MIN_GRID_SIZE*2, button_h);
790 recalc_param:
791 #endif
792 pm->lines = (sc_h - BUTTONBAR_HEIGHT) / pm->font_h - 1;
794 if (pm->default_lines && pm->lines > pm->default_lines)
795 pm->lines = pm->default_lines;
797 pm->keyboard_margin = sc_h - BUTTONBAR_HEIGHT
798 - (pm->lines+1)*pm->font_h;
800 if (pm->keyboard_margin < 3 && pm->lines > 1)
802 pm->lines--;
803 pm->keyboard_margin += pm->font_h;
806 if (pm->keyboard_margin > DEFAULT_MARGIN)
807 pm->keyboard_margin = DEFAULT_MARGIN;
809 total_lines = 0;
810 for (pbuf = pm->kbd_buf; (i = *pbuf) != 0xFEFF; pbuf += i + 1)
811 total_lines += (i ? (i + pm->max_chars - 1) / pm->max_chars : 1);
813 pm->pages = (total_lines + pm->lines - 1) / pm->lines;
814 pm->lines = (total_lines + pm->pages - 1) / pm->pages;
815 #ifdef HAVE_TOUCHSCREEN
816 if (pm->pages > 1 && pm->show_buttons && !flippage_button)
818 /* add space for flip page button */
819 sc_h -= button_h;
820 flippage_button = true;
821 goto recalc_param;
823 #endif
824 if (pm->page >= pm->pages)
825 pm->x = pm->y = pm->page = 0;
827 pm->main_y = pm->font_h*pm->lines + pm->keyboard_margin;
828 pm->keyboard_margin -= pm->keyboard_margin/2;
829 #ifdef HAVE_TOUCHSCREEN
830 /* flip page button is put between piker and edit line */
831 if (flippage_button)
832 pm->main_y += button_h;
833 #endif
835 #ifdef HAVE_MORSE_INPUT
836 pm->old_main_y = sc_h - pm->font_h - BUTTONBAR_HEIGHT;
837 if (state->morse_mode)
839 int y = pm->main_y;
840 pm->main_y = pm->old_main_y;
841 pm->old_main_y = y;
843 #endif
846 static void kbd_draw_picker(struct keyboard_parameters *pm,
847 struct screen *sc, struct edit_state *state)
849 char outline[8];
850 #ifdef HAVE_MORSE_INPUT
851 if (state->morse_mode)
853 const int w = 6, h = 8; /* sysfixed font width, height */
854 int i, j, x, y;
855 int sc_w = sc->getwidth(), sc_h = pm->main_y - pm->keyboard_margin - 1;
857 /* Draw morse code screen with sysfont */
858 sc->setfont(FONT_SYSFIXED);
859 x = 0;
860 y = 0;
861 outline[1] = '\0';
863 /* Draw morse code table with code descriptions. */
864 for (i = 0; morse_alphabets[i] != '\0'; i++)
866 int morse_code;
868 outline[0] = morse_alphabets[i];
869 sc->putsxy(x, y, outline);
871 morse_code = morse_codes[i];
872 for (j = 0; morse_code > 0x01; morse_code >>= 1)
873 j++;
875 x += w + 3 + j*4;
876 morse_code = morse_codes[i];
877 for (; morse_code > 0x01; morse_code >>= 1)
879 x -= 4;
880 if (morse_code & 0x01)
881 sc->fillrect(x, y + 2, 3, 4);
882 else
883 sc->fillrect(x, y + 3, 1, 2);
886 x += w*5 - 3;
887 if (x + w*6 >= sc_w)
889 x = 0;
890 y += h;
891 if (y + h >= sc_h)
892 break;
896 else
897 #else
898 (void) state;
899 #endif /* HAVE_MORSE_INPUT */
901 /* draw page */
902 int i, j;
903 int w, h;
904 unsigned short ch;
905 unsigned char *utf8;
907 sc->setfont(pm->curfont);
909 for (j = 0; j < pm->lines; j++)
911 for (i = 0; i < pm->max_chars; i++)
913 ch = get_kbd_ch(pm, i, j);
914 utf8 = utf8encode(ch, outline);
915 *utf8 = 0;
917 sc->getstringsize(outline, &w, &h);
918 sc->putsxy(i*pm->font_w + (pm->font_w-w) / 2,
919 j*pm->font_h + (pm->font_h-h) / 2, outline);
923 if (!pm->line_edit)
925 /* highlight the key that has focus */
926 sc->set_drawmode(DRMODE_COMPLEMENT);
927 sc->fillrect(pm->font_w*pm->x, pm->font_h*pm->y,
928 pm->font_w, pm->font_h);
929 sc->set_drawmode(DRMODE_SOLID);
934 static void kbd_draw_edit_line(struct keyboard_parameters *pm,
935 struct screen *sc, struct edit_state *state)
937 char outline[8];
938 unsigned char *utf8;
939 int i = 0, j = 0, icon_w, w;
940 int sc_w = sc->getwidth();
941 int y = pm->main_y - pm->keyboard_margin;
942 int text_margin = (sc_w - pm->text_w * pm->max_chars_text) / 2;
944 /* Clear text area one pixel above separator line so any overdraw
945 doesn't collide */
946 screen_clear_area(sc, 0, y - 1, sc_w, pm->font_h + 6);
948 sc->hline(0, sc_w - 1, y);
950 /* write out the text */
951 sc->setfont(pm->curfont);
953 pm->leftpos = MAX(0, MIN(state->len_utf8, state->editpos + 2)
954 - pm->max_chars_text);
955 pm->curpos = state->editpos - pm->leftpos;
956 utf8 = state->text + utf8seek(state->text, pm->leftpos);
958 while (*utf8 && i < pm->max_chars_text)
960 j = utf8seek(utf8, 1);
961 strlcpy(outline, utf8, j+1);
962 sc->getstringsize(outline, &w, NULL);
963 sc->putsxy(text_margin + i*pm->text_w + (pm->text_w-w)/2,
964 pm->main_y, outline);
965 utf8 += j;
966 i++;
969 icon_w = get_icon_width(sc->screen_type);
970 if (pm->leftpos > 0)
972 /* Draw nicer bitmap arrow if room, else settle for "<". */
973 if (text_margin >= icon_w)
975 screen_put_icon_with_offset(sc, 0, 0,
976 (text_margin - icon_w) / 2,
977 pm->main_y, Icon_Reverse_Cursor);
979 else
981 sc->getstringsize("<", &w, NULL);
982 sc->putsxy(text_margin - w, pm->main_y, "<");
986 if (state->len_utf8 - pm->leftpos > pm->max_chars_text)
988 /* Draw nicer bitmap arrow if room, else settle for ">". */
989 if (text_margin >= icon_w)
991 screen_put_icon_with_offset(sc, 0, 0,
992 sc_w - (text_margin + icon_w) / 2,
993 pm->main_y, Icon_Cursor);
995 else
997 sc->putsxy(sc_w - text_margin, pm->main_y, ">");
1001 /* cursor */
1002 i = text_margin + pm->curpos * pm->text_w;
1004 if (state->cur_blink)
1005 sc->vline(i, pm->main_y, pm->main_y + pm->font_h - 1);
1007 if (state->hangul) /* draw underbar */
1008 sc->hline(i - pm->text_w, i, pm->main_y + pm->font_h - 1);
1010 if (pm->line_edit)
1012 sc->set_drawmode(DRMODE_COMPLEMENT);
1013 sc->fillrect(0, y + 2, sc_w, pm->font_h + 2);
1014 sc->set_drawmode(DRMODE_SOLID);
1018 #ifdef HAVE_TOUCHSCREEN
1019 static void kbd_draw_buttons(struct keyboard_parameters *pm, struct screen *sc)
1021 struct viewport vp;
1022 int button_h, text_h, text_y;
1023 int sc_w = sc->getwidth(), sc_h = sc->getheight();
1024 viewport_set_defaults(&vp, sc->screen_type);
1025 vp.flags |= VP_FLAG_ALIGN_CENTER;
1026 sc->set_viewport(&vp);
1027 text_h = sc->getcharheight();
1028 button_h = GRID_SIZE(sc->screen_type, text_h);
1029 text_y = (button_h - text_h) / 2 + 1;
1030 vp.x = 0;
1031 vp.y = 0;
1032 vp.width = sc_w;
1033 vp.height = button_h;
1034 /* draw buttons */
1035 if (pm->pages > 1)
1037 /* button to flip page. */
1038 vp.y = pm->lines*pm->font_h;
1039 sc->hline(0, sc_w - 1, 0);
1040 sc->putsxy(0, text_y, ">");
1042 /* OK/Del/Cancel buttons */
1043 button_h = MAX(MIN_GRID_SIZE*2, button_h);
1044 text_y = (button_h - text_h) / 2 + 1;
1045 vp.y = sc_h - button_h - 1;
1046 vp.height = button_h;
1047 sc->hline(0, sc_w - 1, 0);
1048 vp.width = sc_w/3;
1049 sc->putsxy(0, text_y, str(LANG_KBD_OK));
1050 vp.x += vp.width;
1051 sc->vline(0, 0, button_h);
1052 sc->putsxy(0, text_y, str(LANG_KBD_DELETE));
1053 vp.x += vp.width;
1054 sc->vline(0, 0, button_h);
1055 sc->putsxy(0, text_y, str(LANG_KBD_CANCEL));
1056 sc->set_viewport(NULL);
1059 static int keyboard_touchscreen(struct keyboard_parameters *pm,
1060 struct screen *sc, struct edit_state *state)
1062 short x, y;
1063 const int button = action_get_touchscreen_press(&x, &y);
1064 const int sc_w = sc->getwidth(), sc_h = sc->getheight();
1065 int button_h = MAX(MIN_GRID_SIZE*2, sc->getcharheight());
1066 #ifdef HAVE_MORSE_INPUT
1067 if (state->morse_mode && y < pm->main_y - pm->keyboard_margin)
1069 /* don't return ACTION_NONE since it has effect in morse mode. */
1070 return button == BUTTON_TOUCHSCREEN? ACTION_KBD_SELECT:
1071 button & BUTTON_REL? ACTION_KBD_MORSE_SELECT: ACTION_STD_OK;
1073 #else
1074 (void) state;
1075 #endif
1076 if (x < 0 || y < 0)
1077 return ACTION_NONE;
1078 if (y < pm->lines*pm->font_h)
1080 if (x/pm->font_w < pm->max_chars)
1082 /* picker area */
1083 state->changed = CHANGED_PICKER;
1084 pm->x = x/pm->font_w;
1085 pm->y = y/pm->font_h;
1086 pm->line_edit = false;
1087 if (button == BUTTON_REL)
1088 return ACTION_KBD_SELECT;
1091 else if (y < pm->main_y - pm->keyboard_margin)
1093 /* button to flip page */
1094 if (button == BUTTON_REL)
1095 return ACTION_KBD_PAGE_FLIP;
1097 else if (y < sc_h - button_h)
1099 /* edit line */
1100 if (button & (BUTTON_REPEAT|BUTTON_REL))
1102 if (x < sc_w/2)
1103 return ACTION_KBD_CURSOR_LEFT;
1104 else
1105 return ACTION_KBD_CURSOR_RIGHT;
1108 else
1110 /* OK/Del/Cancel button */
1111 if (button == BUTTON_REL)
1113 if (x < sc_w/3)
1114 return ACTION_KBD_DONE;
1115 else if (x < (sc_w/3) * 2)
1116 return ACTION_KBD_BACKSPACE;
1117 else
1118 return ACTION_KBD_ABORT;
1121 return ACTION_NONE;
1123 #endif
1125 /* inserts the selected char */
1126 static void kbd_insert_selected(struct keyboard_parameters *pm,
1127 struct edit_state *state)
1129 /* find input char */
1130 unsigned short ch = get_kbd_ch(pm, pm->x, pm->y);
1132 /* check for hangul input */
1133 if (ch >= 0x3131 && ch <= 0x3163)
1135 unsigned short tmp;
1137 if (!state->hangul)
1139 state->hlead = state->hvowel = state->htail = 0;
1140 state->hangul = true;
1143 if (!state->hvowel)
1145 state->hvowel = ch;
1147 else if (!state->htail)
1149 state->htail = ch;
1151 else
1153 /* previous hangul complete */
1154 /* check whether tail is actually lead of next char */
1155 tmp = hangul_join(state->htail, ch, 0);
1157 if (tmp != 0xfffd)
1159 tmp = hangul_join(state->hlead, state->hvowel, 0);
1160 kbd_delchar(state);
1161 kbd_inschar(state, tmp);
1162 /* insert dummy char */
1163 kbd_inschar(state, ' ');
1164 state->hlead = state->htail;
1165 state->hvowel = ch;
1166 state->htail = 0;
1168 else
1170 state->hvowel = state->htail = 0;
1171 state->hlead = ch;
1175 /* combine into hangul */
1176 tmp = hangul_join(state->hlead, state->hvowel, state->htail);
1178 if (tmp != 0xfffd)
1180 kbd_delchar(state);
1181 ch = tmp;
1183 else
1185 state->hvowel = state->htail = 0;
1186 state->hlead = ch;
1189 else
1191 state->hangul = false;
1194 /* insert char */
1195 kbd_inschar(state, ch);
1198 static void kbd_backspace(struct edit_state *state)
1200 unsigned short ch;
1201 if (state->hangul)
1203 if (state->htail)
1204 state->htail = 0;
1205 else if (state->hvowel)
1206 state->hvowel = 0;
1207 else
1208 state->hangul = false;
1211 kbd_delchar(state);
1213 if (state->hangul)
1215 if (state->hvowel)
1216 ch = hangul_join(state->hlead, state->hvowel, state->htail);
1217 else
1218 ch = state->hlead;
1219 kbd_inschar(state, ch);
1223 static void kbd_move_cursor(struct edit_state *state, int dir)
1225 state->hangul = false;
1226 state->editpos += dir;
1228 if (state->editpos >= 0 && state->editpos <= state->len_utf8)
1230 state->changed = CHANGED_CURSOR;
1232 else
1234 state->editpos -= dir;
1235 #if CONFIG_CODEC == SWCODEC
1236 if (global_settings.talk_menu)
1237 pcmbuf_beep(1000, 150, 1500);
1238 #endif
1242 static void kbd_move_picker_horizontal(struct keyboard_parameters *pm,
1243 struct edit_state *state, int dir)
1245 state->changed = CHANGED_PICKER;
1247 pm->x += dir;
1248 if (pm->x < 0)
1250 if (--pm->page < 0)
1251 pm->page = pm->pages - 1;
1252 pm->x = pm->max_chars - 1;
1254 else if (pm->x >= pm->max_chars)
1256 if (++pm->page >= pm->pages)
1257 pm->page = 0;
1258 pm->x = 0;
1262 static void kbd_move_picker_vertical(struct keyboard_parameters *pm,
1263 struct edit_state *state, int dir)
1265 state->changed = CHANGED_PICKER;
1267 #ifdef HAVE_MORSE_INPUT
1268 if (state->morse_mode)
1270 pm->line_edit = !pm->line_edit;
1271 return;
1273 #endif /* HAVE_MORSE_INPUT */
1275 pm->y += dir;
1276 if (pm->line_edit)
1278 pm->y = (dir > 0 ? 0 : pm->lines - 1);
1279 pm->line_edit = false;
1281 else if (pm->y < 0 || pm->y >= pm->lines)
1283 pm->line_edit = true;