From 75c3d0b9f10ea30b827052b7a6f894734e705c4f Mon Sep 17 00:00:00 2001 From: Frank Dischner Date: Wed, 29 Mar 2006 16:21:42 +0000 Subject: [PATCH] Add support for loadable vkeyboard layouts git-svn-id: svn://svn.rockbox.org/rockbox/trunk@9347 a1c6a512-1295-4272-9138-f99709370657 --- apps/filetree.c | 9 + apps/keyboard.h | 4 + apps/lang/english.lang | 11 + apps/recorder/icons.c | 1 + apps/recorder/icons.h | 1 + apps/recorder/keyboard.c | 649 +++++++++++++++++++++++++++------------ apps/settings.c | 34 ++ apps/settings.h | 4 + apps/tree.c | 1 + apps/tree.h | 1 + firmware/SOURCES | 1 + firmware/export/hangul.h | 23 ++ firmware/hangul.c | 101 ++++++ 13 files changed, 644 insertions(+), 196 deletions(-) create mode 100644 firmware/export/hangul.h create mode 100644 firmware/hangul.c diff --git a/apps/filetree.c b/apps/filetree.c index bbe56e119c..7b4f5e7ed8 100644 --- a/apps/filetree.c +++ b/apps/filetree.c @@ -41,6 +41,9 @@ #include "dircache.h" #include "splash.h" #include "yesno.h" +#ifdef HAVE_LCD_BITMAP +#include "keyboard.h" +#endif #ifndef SIMULATOR static int boot_size = 0; @@ -462,6 +465,12 @@ int ft_enter(struct tree_context* c) font_load(buf); set_file(buf, (char *)global_settings.font_file, MAX_FILENAME); break; + + case TREE_ATTR_KBD: + if (!load_kbd(buf)) + gui_syncsplash(HZ, true, str(LANG_KEYBOARD_LOADED)); + set_file(buf, (char *)global_settings.kbd_file, MAX_FILENAME); + break; #endif #ifndef SIMULATOR diff --git a/apps/keyboard.h b/apps/keyboard.h index fa1e11e0ce..3ba2822e36 100644 --- a/apps/keyboard.h +++ b/apps/keyboard.h @@ -21,4 +21,8 @@ int kbd_input(char* buffer, int buflen); +#ifdef HAVE_LCD_BITMAP +int load_kbd(unsigned char* filename); +#endif + #endif diff --git a/apps/lang/english.lang b/apps/lang/english.lang index c239b9cdd3..e591b46b9f 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -3887,3 +3887,14 @@ eng: "Precut" voice: "Pre-cut" new: +id: LANG_KEYBOARD_LOADED +desc: shown when a keyboard has been loaded from the dir browser +eng: "New Keyboard" +voice: "" +new: + +id: VOICE_EXT_KBD +desc: spoken only, for file extension +eng: "" +voice: "keyboard" +new: diff --git a/apps/recorder/icons.c b/apps/recorder/icons.c index 6a0291ae5e..4b6df725f9 100644 --- a/apps/recorder/icons.c +++ b/apps/recorder/icons.c @@ -49,6 +49,7 @@ const unsigned char bitmap_icons_6x8[][6] = { 0xff, 0x81, 0xaf, 0xaa, 0x8c, 0xf8 }, /* Bookmark file */ { 0x77, 0x55, 0x55, 0x55, 0x55, 0x77 }, /* Queued Item */ { 0x3e, 0x41, 0x3e, 0x1c, 0x1c, 0x08 }, /* Moving Item */ + { 0x7f, 0x7f, 0x1c, 0x3e, 0x77, 0x63 }, /* Keyboard file */ }; const unsigned char bitmap_icons_7x8[][7] = diff --git a/apps/recorder/icons.h b/apps/recorder/icons.h index 62ed73aeac..15747edeeb 100644 --- a/apps/recorder/icons.h +++ b/apps/recorder/icons.h @@ -56,6 +56,7 @@ enum icons_6x8 { Icon_Bookmark, Icon_Queued, Icon_Moving, + Icon_Keyboard, Icon6x8Last }; diff --git a/apps/recorder/keyboard.c b/apps/recorder/keyboard.c index cb982e38e6..47deb3a5b9 100644 --- a/apps/recorder/keyboard.c +++ b/apps/recorder/keyboard.c @@ -33,19 +33,28 @@ #include "rbunicode.h" #include "buttonbar.h" #include "logf.h" +#include "icons.h" +#include "file.h" +#include "hangul.h" -#define KEYBOARD_MARGIN 3 - -#if (LCD_WIDTH >= 160) && (LCD_HEIGHT >= 96) -#define KEYBOARD_LINES 8 -#define KEYBOARD_PAGES 1 - -#else -#define KEYBOARD_LINES 4 -#define KEYBOARD_PAGES 3 - +#ifndef O_BINARY +#define O_BINARY 0 #endif +#if CONFIG_KEYPAD == RECORDER_PAD +#define BUTTONBAR_HEIGHT 8 +#else +#define BUTTONBAR_HEIGHT 0 +#endif + +#if (LCD_WIDTH >= 160) && (LCD_HEIGHT >= 96) +#define DEFAULT_LINES 8 +#else +#define DEFAULT_LINES 4 +#endif + +#define DEFAULT_MARGIN 6 +#define KBD_BUF_SIZE 500 #if (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ (CONFIG_KEYPAD == IRIVER_H300_PAD) @@ -61,7 +70,7 @@ #define KBD_RIGHT BUTTON_RIGHT #define KBD_UP BUTTON_UP #define KBD_DOWN BUTTON_DOWN -#define HAVE_MORSE_INPUT +#define KBD_MORSE_INPUT (BUTTON_ON | BUTTON_MODE) #elif CONFIG_KEYPAD == RECORDER_PAD #define KBD_CURSOR_RIGHT (BUTTON_ON | BUTTON_RIGHT) @@ -172,39 +181,39 @@ #endif -#if KEYBOARD_PAGES == 1 -static const char * const kbdpages[KEYBOARD_PAGES][KEYBOARD_LINES] = { - { "ABCDEFG abcdefg !?\" @#$%+'", - "HIJKLMN hijklmn 789 &_()-`", - "OPQRSTU opqrstu 456 §|{}/<", - "VWXYZ., vwxyz.,0123 ~=[]*>", - "ÀÁÂÃÄÅÆ ÌÍÎÏ ÈÉÊË ¢£¤¥¦§©®", - "àáâãäåæ ìíîï èéêë «»°ºª¹²³", - "ÓÒÔÕÖØ ÇÐÞÝß ÙÚÛÜ ¯±×÷¡¿µ·", - "òóôõöø çðþýÿ ùúûü ¼½¾¬¶¨ " }, -}; - +#if (LCD_WIDTH >= 160) && (LCD_HEIGHT >= 96) +static const unsigned char * default_kbd = + "ABCDEFG abcdefg !?\" @#$%+'\n" + "HIJKLMN hijklmn 789 &_()-`\n" + "OPQRSTU opqrstu 456 §|{}/<\n" + "VWXYZ., vwxyz.,0123 ~=[]*>\n" + "ÀÁÂÃÄÅÆ ÌÍÎÏ ÈÉÊË ¢£¤¥¦§©®\n" + "àáâãäåæ ìíîï èéêë «»°ºª¹²³\n" + "ÓÒÔÕÖØ ÇÐÞÝß ÙÚÛÜ ¯±×÷¡¿µ·\n" + "òóôõöø çðþýÿ ùúûü ¼½¾¬¶¨"; #else -static const char * const kbdpages[KEYBOARD_PAGES][KEYBOARD_LINES] = { - { "ABCDEFG !?\" @#$%+'", - "HIJKLMN 789 &_()-`", - "OPQRSTU 456 §|{}/<", - "VWXYZ.,0123 ~=[]*>" }, +static const unsigned char * default_kbd = + "ABCDEFG !?\" @#$%+'\n" + "HIJKLMN 789 &_()-`\n" + "OPQRSTU 456 §|{}/<\n" + "VWXYZ.,0123 ~=[]*>\n" - { "abcdefg ¢£¤¥¦§©®¬", - "hijklmn «»°ºª¹²³¶", - "opqrstu ¯±×÷¡¿µ·¨", - "vwxyz., ¼½¾ " }, - - { "ÀÁÂÃÄÅÆ ÌÍÎÏ ÈÉÊË", - "àáâãäåæ ìíîï èéêë", - "ÓÒÔÕÖØ ÇÐÞÝß ÙÚÛÜ", - "òóôõöø çðþýÿ ùúûü" }, -}; + "abcdefg ¢£¤¥¦§©®¬\n" + "hijklmn «»°ºª¹²³¶\n" + "opqrstu ¯±×÷¡¿µ·¨\n" + "vwxyz., ¼½¾ \n" + "ÀÁÂÃÄÅÆ ÌÍÎÏ ÈÉÊË\n" + "àáâãäåæ ìíîï èéêë\n" + "ÓÒÔÕÖØ ÇÐÞÝß ÙÚÛÜ\n" + "òóôõöø çðþýÿ ùúûü"; #endif -#ifdef HAVE_MORSE_INPUT +static unsigned short kbd_buf[KBD_BUF_SIZE]; +static bool kbd_loaded = false; +static int nchars = 0; + +#ifdef KBD_MORSE_INPUT /* FIXME: We should put this to a configuration file. */ static const char *morse_alphabets = "abcdefghijklmnopqrstuvwxyz1234567890,.?-@ "; @@ -217,37 +226,139 @@ static const unsigned char morse_codes[] = { static bool morse_mode = false; #endif +/* Loads a custom keyboard into memory + call with NULL to reset keyboard */ +int load_kbd(unsigned char* filename) +{ + int fd, count; + int i = 0; + unsigned char buf[4]; + + if (filename == NULL) { + kbd_loaded = false; + return 0; + } + + fd = open(filename, O_RDONLY|O_BINARY); + if (fd < 0) + return 1; + + while (read(fd, buf, 1) == 1 && i < KBD_BUF_SIZE) { + /* check how many bytes to read */ + if (buf[0] < 0x80) { + count = 0; + } else if (buf[0] < 0xe0) { + count = 1; + } else if (buf[0] < 0xf0) { + count = 2; + } else if (buf[0] < 0xf5) { + count = 3; + } else { + /* Invalid size. */ + continue; + } + + if (read(fd, &buf[1], count) != count) { + close(fd); + kbd_loaded = false; + return 1; + } + + utf8decode(buf, &kbd_buf[i]); + if (kbd_buf[i] != 0xFEFF && kbd_buf[i] != '\n' && + kbd_buf[i] != '\r') /*skip BOM & newlines */ + i++; + } + + close(fd); + kbd_loaded = true; + nchars = i; + return 0; + +} + /* helper function to spell a char if voice UI is enabled */ -static void kbd_spellchar(char c) +static void kbd_spellchar(unsigned short c) { static char spell_char[2] = "\0\0"; /* store char to pass to talk_spell */ - if (global_settings.talk_menu) /* voice UI? */ + if (global_settings.talk_menu && c < 128) /* voice UI? */ { - spell_char[0] = c; + spell_char[0] = (char)c; talk_spell(spell_char, false); } } +void kbd_inschar(unsigned char* text, int buflen, int* editpos, unsigned short ch) +{ + int i, j, k, len; + unsigned char tmp[4]; + unsigned char* utf8; + + len = strlen(text); + k = utf8length(text); + utf8 = utf8encode(ch, tmp); + j = (long)utf8 - (long)tmp; + + if (len + j < buflen) + { + for (i = len+j; k >= *editpos; i--) { + text[i] = text[i-j]; + if ((text[i] & MASK) != COMP) + k--; + } + while (j--) + text[i--] = tmp[j]; + (*editpos)++; + } + return; +} + +void kbd_delchar(unsigned char* text, int* editpos) +{ + int i = 0; + unsigned char* utf8; + + if (*editpos > 0) + { + utf8 = text + utf8seek(text, *editpos); + do { + i++; + utf8--; + } while ((*utf8 & MASK) == COMP); + while (utf8[i]) { + *utf8 = utf8[i]; + utf8++; + } + *utf8 = 0; + (*editpos)--; + } + + return; +} + int kbd_input(char* text, int buflen) { bool done = false; -#if defined(KBD_PAGE_FLIP) || (KEYBOARD_PAGES > 1) int page = 0; -#endif - int font_w = 0, font_h = 0, i, j; + int font_w = 0, font_h = 0, text_w = 0; + int i = 0, j, k, w; int x = 0, y = 0; - int main_x, main_y, max_chars; - int status_y1, status_y2; - int len, len_utf8, c = 0; + int main_x, main_y, max_chars, max_chars_text; + int len_utf8, c = 0; int editpos, curpos, leftpos; + int lines, pages, keyboard_margin; + int curfont; + int statusbar_size = global_settings.statusbar ? STATUSBAR_HEIGHT : 0; + unsigned short ch, tmp, hlead = 0, hvowel = 0, htail = 0; + bool hangul = false; bool redraw = true; unsigned char *utf8; - const char * const *line; -#ifdef HAVE_MORSE_INPUT + const unsigned char *p; +#ifdef KBD_MORSE_INPUT bool morse_reading = false; unsigned char morse_code = 0; - int morse_tick = 0, morse_len; + int morse_tick = 0, morse_len, old_main_y; char buf[2]; #endif #ifdef KBD_MODES @@ -255,7 +366,7 @@ int kbd_input(char* text, int buflen) #endif char outline[256]; - struct font* font = font_get(FONT_SYSFIXED); + struct font* font; int button, lastbutton = 0; #ifdef HAS_BUTTONBAR struct gui_buttonbar buttonbar; @@ -264,44 +375,103 @@ int kbd_input(char* text, int buflen) gui_buttonbar_init(&buttonbar); gui_buttonbar_set_display(&buttonbar, &(screens[SCREEN_MAIN]) ); #endif - lcd_setfont(FONT_SYSFIXED); - font_w = font->maxwidth; + + if (!kbd_loaded) { + curfont = FONT_SYSFIXED; + p = default_kbd; + while (*p != 0) { + p = utf8decode(p, &kbd_buf[i]); + if (kbd_buf[i] == '\n') + while (i % (LCD_WIDTH/6)) + kbd_buf[i++] = ' '; + else + i++; + } + nchars = i; + } + else + curfont = FONT_UI; + + font = font_get(curfont); font_h = font->height; -#ifdef HAVE_MORSE_INPUT + /* check if FONT_UI fits the screen */ + if (2*font_h+3+statusbar_size + BUTTONBAR_HEIGHT > LCD_HEIGHT) { + font = font_get(FONT_SYSFIXED); + font_h = font->height; + curfont = FONT_SYSFIXED; + } + + lcd_setfont(curfont); + + /* find max width of keyboard glyphs */ + for (i=0; i font_w) + font_w = w; + } + + /* find max width for text string */ + utf8 = text; + text_w = font_w; + while (*utf8) { + utf8 = (unsigned char*)utf8decode(utf8, &ch); + w = font_get_width(font, ch); + if (w > text_w) + text_w = w; + } + max_chars_text = LCD_WIDTH / text_w - 2; + + /* calculate keyboard grid size */ + max_chars = LCD_WIDTH / font_w; + if (!kbd_loaded) { + lines = DEFAULT_LINES; + keyboard_margin = DEFAULT_MARGIN; + } else { + lines = (LCD_HEIGHT - BUTTONBAR_HEIGHT - statusbar_size) / font_h - 1; + keyboard_margin = LCD_HEIGHT - BUTTONBAR_HEIGHT - statusbar_size - (lines+1)*font_h; + if (keyboard_margin < 3) { + lines--; + keyboard_margin += font_h; + } + if (keyboard_margin > 6) + keyboard_margin = 6; + } + + pages = (nchars + (lines*max_chars-1))/(lines*max_chars); + if (pages == 1 && kbd_loaded) + lines = (nchars + max_chars - 1) / max_chars; + + main_y = font_h*lines + keyboard_margin + statusbar_size; + main_x = 0; + keyboard_margin -= keyboard_margin/2; + +#ifdef KBD_MORSE_INPUT + old_main_y = main_y; if (morse_mode) main_y = LCD_HEIGHT - font_h; - else #endif - main_y = (KEYBOARD_LINES + 1) * font_h + (2*KEYBOARD_MARGIN); - main_x = 0; - status_y1 = LCD_HEIGHT - font_h; - status_y2 = LCD_HEIGHT; editpos = utf8length(text); - - max_chars = LCD_WIDTH / font_w - 2; /* leave room for < and > */ - line = kbdpages[0]; if (global_settings.talk_menu) /* voice UI? */ talk_spell(text, true); /* spell initial text */ while(!done) { - len = strlen(text); len_utf8 = utf8length(text); if(redraw) { lcd_clear_display(); - lcd_setfont(FONT_SYSFIXED); - -#ifdef HAVE_MORSE_INPUT +#ifdef KBD_MORSE_INPUT if (morse_mode) { + lcd_setfont(FONT_SYSFIXED); /* Draw morse code screen with sysfont */ + w = 6; /* sysfixed font width */ x = 0; - y = font_h; + y = statusbar_size; buf[1] = '\0'; /* Draw morse code table with code descriptions. */ for (i = 0; morse_alphabets[i] != '\0'; i++) @@ -312,7 +482,7 @@ int kbd_input(char* text, int buflen) for (j = 0; (morse_codes[i] >> j) > 0x01; j++) ; morse_len = j; - x += font_w + 3; + x += w + 3; for (j = 0; j < morse_len; j++) { if ((morse_codes[i] >> (morse_len-j-1)) & 0x01) @@ -321,11 +491,11 @@ int kbd_input(char* text, int buflen) lcd_fillrect(x + j*4, y + 3, 1, 2); } - x += font_w * 5 - 3; - if (x >= LCD_WIDTH - (font_w*6)) + x += w * 5 - 3; + if (x >= LCD_WIDTH - (w*6)) { x = 0; - y += font_h; + y += 8; /* sysfixed font height */ } } } @@ -333,50 +503,67 @@ int kbd_input(char* text, int buflen) #endif { /* draw page */ - for (i=0; i < KEYBOARD_LINES; i++) - lcd_putsxy(0, 8+i * font_h, line[i]); - + lcd_setfont(curfont); + k = page*max_chars*lines; + for (i=j=0; j < lines && k < nchars; k++) { + utf8 = utf8encode(kbd_buf[k], outline); + *utf8 = 0; + lcd_getstringsize(outline, &w, NULL); + lcd_putsxy(i*font_w + (font_w-w)/2, j*font_h + statusbar_size, outline); + if (++i == max_chars) { + i = 0; + j++; + } + } } /* separator */ - lcd_hline(0, LCD_WIDTH - 1, main_y - KEYBOARD_MARGIN); + lcd_hline(0, LCD_WIDTH - 1, main_y - keyboard_margin); /* write out the text */ - curpos = MIN(editpos, max_chars - MIN(len_utf8 - editpos, 2)); + lcd_setfont(curfont); + i=j=0; + curpos = MIN(editpos, max_chars_text - MIN(len_utf8 - editpos, 2)); leftpos = editpos - curpos; utf8 = text + utf8seek(text, leftpos); - i=j=0; - while (*utf8 && i < max_chars) { - outline[j++] = *utf8++; - if ((*utf8 & MASK) != COMP) - i++; - } - outline[j] = 0; - - lcd_putsxy(font_w, main_y, outline); - if (leftpos) - lcd_putsxy(0, main_y, "<"); - if (len_utf8 - leftpos > max_chars) - lcd_putsxy(LCD_WIDTH - font_w, main_y, ">"); - + while (*utf8 && i < max_chars_text) { + outline[j++] = *utf8++; + if ((*utf8 & MASK) != COMP) { + outline[j] = 0; + j=0; + i++; + lcd_getstringsize(outline, &w, NULL); + lcd_putsxy(i*text_w + (text_w-w)/2, main_y, outline); + } + } + + if (leftpos) { + lcd_getstringsize("<", &w, NULL); + lcd_putsxy(text_w - w, main_y, "<"); + } + if (len_utf8 - leftpos > max_chars_text) + lcd_putsxy(LCD_WIDTH - text_w, main_y, ">"); + /* cursor */ - i = (curpos + 1) * font_w; + i = (curpos + 1) * text_w; lcd_vline(i, main_y, main_y + font_h); - + if (hangul) /* draw underbar */ + lcd_hline(curpos*text_w, (curpos+1)*text_w, main_y+font_h-1); + #ifdef HAS_BUTTONBAR /* draw the status bar */ gui_buttonbar_set(&buttonbar, "Shift", "OK", "Del"); gui_buttonbar_draw(&buttonbar); #endif - + #ifdef KBD_MODES if (!line_edit) #endif { /* highlight the key that has focus */ lcd_set_drawmode(DRMODE_COMPLEMENT); - lcd_fillrect(font_w * x, 8 + font_h * y, font_w, font_h); + lcd_fillrect(font_w * x, statusbar_size + font_h * y, font_w, font_h); lcd_set_drawmode(DRMODE_SOLID); } @@ -388,7 +575,7 @@ int kbd_input(char* text, int buflen) redraw = true; button = button_get_w_tmo(HZ/2); -#ifdef HAVE_MORSE_INPUT +#ifdef KBD_MORSE_INPUT if (morse_mode) { /* Remap some buttons for morse mode. */ @@ -411,40 +598,43 @@ int kbd_input(char* text, int buflen) #if defined(KBD_PAGE_FLIP) case KBD_PAGE_FLIP: -#ifdef HAVE_MORSE_INPUT +#ifdef KBD_MORSE_INPUT if (morse_mode) - { - main_y = (KEYBOARD_LINES + 1) * font_h + (2*KEYBOARD_MARGIN); - morse_mode = false; - x = y = 0; - } - else + break; #endif - if (++page == KEYBOARD_PAGES) - { + if (++page == pages) page = 0; -#ifdef HAVE_MORSE_INPUT - main_y = LCD_HEIGHT - font_h; - morse_mode = true; - /* FIXME: We should talk something like Morse mode.. */ - break ; -#endif - } - line = kbdpages[page]; - c = utf8seek(line[y], x); - kbd_spellchar(line[y][c]); + k = (page*lines + y)*max_chars + x; + kbd_spellchar(kbd_buf[k]); break; #endif +#ifdef KBD_MORSE_INPUT + case KBD_MORSE_INPUT: + morse_mode = !morse_mode; + x = y = page = 0; + if (morse_mode) { + old_main_y = main_y; + main_y = LCD_HEIGHT - font_h; + } else + main_y = old_main_y; + /* FIXME: We should talk something like Morse mode.. */ + break; +#endif case KBD_RIGHT: case KBD_RIGHT | BUTTON_REPEAT: -#ifdef HAVE_MORSE_INPUT +#ifdef KBD_MORSE_INPUT if (morse_mode) break; #endif #ifdef KBD_MODES if (line_edit) /* right doubles as cursor_right in line_edit */ { + if (hangul) { + hangul = false; + hlead=hvowel=htail=0; + break; + } if (editpos < len_utf8) { editpos++; @@ -455,32 +645,33 @@ int kbd_input(char* text, int buflen) else #endif { - if (x < (int)utf8length(line[y]) - 1) - x++; - else - { + if (++x == max_chars) { x = 0; -#if !defined(KBD_PAGE_FLIP) && KEYBOARD_PAGES > 1 +#if !defined(KBD_PAGE_FLIP) /* no dedicated flip key - flip page on wrap */ - if (++page == KEYBOARD_PAGES) + if (++page == pages) page = 0; - line = kbdpages[page]; #endif } - c = utf8seek(line[y], x); - kbd_spellchar(line[y][c]); + k = (page*lines + y)*max_chars + x; + kbd_spellchar(kbd_buf[k]); } break; case KBD_LEFT: case KBD_LEFT | BUTTON_REPEAT: -#ifdef HAVE_MORSE_INPUT +#ifdef KBD_MORSE_INPUT if (morse_mode) break; #endif #ifdef KBD_MODES if (line_edit) /* left doubles as cursor_left in line_edit */ { + if (hangul) { + hangul = false; + hlead=hvowel=htail=0; + break; + } if (editpos) { editpos--; @@ -490,27 +681,26 @@ int kbd_input(char* text, int buflen) } else #endif - { + { if (x) x--; else { -#if !defined(KBD_PAGE_FLIP) && KEYBOARD_PAGES > 1 +#if !defined(KBD_PAGE_FLIP) /* no dedicated flip key - flip page on wrap */ if (--page < 0) - page = (KEYBOARD_PAGES-1); - line = kbdpages[page]; + page = (pages-1); #endif - x = utf8length(line[y]) - 1; + x = max_chars - 1; } - c = utf8seek(line[y], x); - kbd_spellchar(line[y][c]); + k = (page*lines + y)*max_chars + x; + kbd_spellchar(kbd_buf[k]); } break; case KBD_DOWN: case KBD_DOWN | BUTTON_REPEAT: -#ifdef HAVE_MORSE_INPUT +#ifdef KBD_MORSE_INPUT if (morse_mode) break; #endif @@ -523,7 +713,7 @@ int kbd_input(char* text, int buflen) else { #endif - if (y < KEYBOARD_LINES - 1) + if (y < lines - 1) y++; else #ifndef KBD_MODES @@ -533,20 +723,22 @@ int kbd_input(char* text, int buflen) } if (!line_edit) #endif - c = utf8seek(line[y], x); - kbd_spellchar(line[y][c]); + { + k = (page*lines + y)*max_chars + x; + kbd_spellchar(kbd_buf[k]); + } break; case KBD_UP: case KBD_UP | BUTTON_REPEAT: -#ifdef HAVE_MORSE_INPUT +#ifdef KBD_MORSE_INPUT if (morse_mode) break; #endif #ifdef KBD_MODES if (line_edit) { - y = KEYBOARD_LINES - 1; + y = lines - 1; line_edit = false; } else @@ -556,14 +748,16 @@ int kbd_input(char* text, int buflen) y--; else #ifndef KBD_MODES - y = KEYBOARD_LINES - 1; + y = lines - 1; #else line_edit = true; } if (!line_edit) #endif - c = utf8seek(line[y], x); - kbd_spellchar(line[y][c]); + { + k = (page*lines + y)*max_chars + x; + kbd_spellchar(kbd_buf[k]); + } break; case KBD_DONE: @@ -575,7 +769,7 @@ int kbd_input(char* text, int buflen) done = true; break; -#ifdef HAVE_MORSE_INPUT +#ifdef KBD_MORSE_INPUT case KBD_SELECT | BUTTON_REL: if (morse_mode && morse_reading) { @@ -588,7 +782,7 @@ int kbd_input(char* text, int buflen) #endif case KBD_SELECT: -#ifdef HAVE_MORSE_INPUT +#ifdef KBD_MORSE_INPUT if (morse_mode) { morse_tick = current_tick; @@ -607,44 +801,94 @@ int kbd_input(char* text, int buflen) break; #endif #ifdef KBD_MODES - if (line_edit) /* select doubles as backspace in line_edit */ - { - if (editpos > 0) - { - utf8 = text + utf8seek(text, editpos); - i = 0; - do { - i++; - utf8--; - } while ((*utf8 & MASK) == COMP); - while (utf8[i]) { - *utf8 = utf8[i]; - utf8++; + if (line_edit) { /* select doubles as backspace in line_edit */ + if (hangul) { + if (htail != 0) + htail = 0; + else if (hvowel != 0) + hvowel = 0; + else { + hlead = 0; + hangul = false; } - *utf8 = 0; - editpos--; + } + kbd_delchar(text, &editpos); + if (hangul) { + if (hvowel != 0) + ch = hangul_join(hlead, hvowel, htail); + else + ch = hlead; + kbd_inschar(text, buflen, &editpos, ch); } } else #endif { - const unsigned char *inschar = line[y] + utf8seek(line[y], x); - j = 0; - do { - j++; - } while ((inschar[j] & MASK) == COMP); - if (len + j < buflen) - { - int k = len_utf8; - for (i = len+j; k >= editpos; i--) { - text[i] = text[i-j]; - if ((text[i] & MASK) != COMP) - k--; + /* find input char */ + k = (page*lines + y)*max_chars + x; + if (k < nchars) + ch = kbd_buf[k]; + else + ch = ' '; + + /* check for hangul input */ + if (ch >= 0x3131 && ch <= 0x3163) { + if (hangul) { + if ((hvowel == 0) && (jamo_table[ch-0x3131][1] != 0)) { + hvowel = ch; + ch = hangul_join(hlead, hvowel, htail); + kbd_delchar(text, &editpos); + } + else if ((htail == 0) && (hvowel != 0) && (jamo_table[ch-0x3131][2] != 0)) { + htail = ch; + /* combine into hangul */ + ch = hangul_join(hlead, hvowel, htail); + kbd_delchar(text, &editpos); + } + else { /* invalid following char or hangul complete */ + /* check whether tail is actually lead of next char */ + if (htail != 0 && (jamo_table[htail-0x3131][0] != 0) + && (jamo_table[ch-0x3131][1] != 0)) { + tmp = hangul_join(hlead, hvowel, 0); + kbd_delchar(text, &editpos); + kbd_inschar(text, buflen, &editpos, tmp); + hlead = htail; + hvowel = ch; + htail = 0; + ch = hangul_join(hlead, hvowel, htail); + } + else if (hlead != 0 && hvowel != 0) { + /* finish previous hangul */ + tmp = hangul_join(hlead, hvowel, htail); + kbd_delchar(text, &editpos); + kbd_inschar(text, buflen, &editpos, tmp); + hlead=hvowel=htail=0; + /* start of new hangul? */ + if (jamo_table[ch-0x3131][0] != 0) { + hlead = ch; + } + else + hangul = false; + } + } + } + else if (jamo_table[ch-0x3131][0] != 0) { + hlead = ch; + hangul = true; } - while (j--) - text[i--] = inschar[j]; - editpos++; } + else if (hangul) { + /* finish previous hangul */ + if (hlead != 0 && hvowel != 0) { + tmp = hangul_join(hlead, hvowel, htail); + kbd_delchar(text, &editpos); + kbd_inschar(text, buflen, &editpos, tmp); + } + hangul = false; + hlead=hvowel=htail=0; + } + /* insert char */ + kbd_inschar(text, buflen, &editpos, ch); } if (global_settings.talk_menu) /* voice UI? */ talk_spell(text, false); /* speak revised text */ @@ -653,20 +897,23 @@ int kbd_input(char* text, int buflen) #ifndef KBD_MODES case KBD_BACKSPACE: case KBD_BACKSPACE | BUTTON_REPEAT: - if (editpos > 0) - { - utf8 = text + utf8seek(text, editpos); - i = 0; - do { - i++; - utf8--; - } while ((*utf8 & MASK) == COMP); - while (utf8[i]) { - *utf8 = utf8[i]; - utf8++; + if (hangul) { + if (htail != 0) + htail = 0; + else if (hvowel != 0) + hvowel = 0; + else { + hlead = 0; + hangul = false; } - *utf8 = 0; - editpos--; + } + kbd_delchar(text, &editpos); + if (hangul) { + if (hvowel != 0) + ch = hangul_join(hlead, hvowel, htail); + else + ch = hlead; + kbd_inschar(text, buflen, &editpos, ch); } if (global_settings.talk_menu) /* voice UI? */ talk_spell(text, false); /* speak revised text */ @@ -674,6 +921,11 @@ int kbd_input(char* text, int buflen) case KBD_CURSOR_RIGHT: case KBD_CURSOR_RIGHT | BUTTON_REPEAT: + if (hangul) { + hangul = false; + hlead=hvowel=htail=0; + break; + } if (editpos < len_utf8) { editpos++; @@ -684,6 +936,11 @@ int kbd_input(char* text, int buflen) case KBD_CURSOR_LEFT: case KBD_CURSOR_LEFT | BUTTON_REPEAT: + if (hangul) { + hangul = false; + hlead=hvowel=htail=0; + break; + } if (editpos) { editpos--; @@ -696,7 +953,7 @@ int kbd_input(char* text, int buflen) case BUTTON_NONE: gui_syncstatusbar_draw(&statusbars, false); redraw = false; -#ifdef HAVE_MORSE_INPUT +#ifdef KBD_MORSE_INPUT if (morse_reading) { logf("Morse: 0x%02x", morse_code); @@ -713,15 +970,15 @@ int kbd_input(char* text, int buflen) logf("Morse code not found"); break ; } - - if (len + 1 < buflen) - { - for (i = len ; i > editpos; i--) - text[i] = text[i-1]; - text[len+1] = 0; - text[editpos] = morse_alphabets[j]; - editpos++; + + /* finish hangul char if necessary */ + if (hangul) { + hangul = false; + hlead=hvowel=htail=0; } + + kbd_inschar(text, buflen, &editpos, morse_alphabets[j]); + if (global_settings.talk_menu) /* voice UI? */ talk_spell(text, false); /* speak revised text */ redraw = true; diff --git a/apps/settings.c b/apps/settings.c index 6ea7c34a31..4068f78015 100644 --- a/apps/settings.c +++ b/apps/settings.c @@ -916,6 +916,11 @@ int settings_save( void ) MAX_FILENAME); i+= MAX_FILENAME; #endif +#ifdef HAVE_LCD_BITMAP + strncpy((char *)&config_block[i], (char *)global_settings.kbd_file, + MAX_FILENAME); + i+= MAX_FILENAME; +#endif if(save_config_buffer()) { @@ -1108,6 +1113,15 @@ void settings_apply(void) else font_reset(); + if ( global_settings.kbd_file[0] && + global_settings.kbd_file[0] != 0xff ) { + snprintf(buf, sizeof buf, ROCKBOX_DIR "/%s.kbd", + global_settings.kbd_file); + load_kbd(buf); + } + else + load_kbd(NULL); + lcd_scroll_step(global_settings.scroll_step); gui_list_screen_scroll_step(global_settings.screen_scroll_step); gui_list_screen_scroll_out_of_view(global_settings.offset_out_of_view); @@ -1252,6 +1266,11 @@ void settings_load(int which) strncpy((char *)global_settings.backdrop_file, (char *)&config_block[i], MAX_FILENAME); i+= MAX_FILENAME; +#endif +#ifdef HAVE_LCD_BITMAP + strncpy((char *)global_settings.kbd_file, (char *)&config_block[i], + MAX_FILENAME); + i+= MAX_FILENAME; #endif } } @@ -1425,6 +1444,12 @@ bool settings_load_config(const char* file) set_file(value, (char *)global_settings.backdrop_file, MAX_FILENAME); } #endif +#ifdef HAVE_LCD_BITMAP + else if (!strcasecmp(name, "keyboard")) { + if (!load_kbd(value)) + set_file(value, (char *)global_settings.kbd_file, MAX_FILENAME); + } +#endif /* check for scalar values, using the two tables */ @@ -1583,6 +1608,12 @@ bool settings_save_config(void) global_settings.backdrop_file); #endif +#ifdef HAVE_LCD_BITMAP + if (global_settings.kbd_file[0] != 0) + fdprintf(fd, "keyboard: %s/%s.kbd\r\n", ROCKBOX_DIR, + global_settings.kbd_file); +#endif + /* here's the action: write values to file, specified via table */ save_cfg_table(rtc_bits, sizeof(rtc_bits)/sizeof(rtc_bits[0]), fd); save_cfg_table(hd_bits, sizeof(hd_bits)/sizeof(hd_bits[0]), fd); @@ -1666,6 +1697,9 @@ void settings_reset(void) { global_settings.fg_color = LCD_DEFAULT_FG; global_settings.bg_color = LCD_DEFAULT_BG; #endif +#ifdef HAVE_LCD_BITMAP + global_settings.kbd_file[0] = '\0'; +#endif } diff --git a/apps/settings.h b/apps/settings.h index 3616434642..498c880319 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -481,6 +481,10 @@ struct user_settings bool remote_bl_filter_first_keypress; /* filter first remote keypress when remote dark? */ #endif #endif + +#ifdef HAVE_LCD_BITMAP + unsigned char kbd_file[MAX_FILENAME+1]; /* last keyboard */ +#endif }; enum optiontype { INT, BOOL }; diff --git a/apps/tree.c b/apps/tree.c index 0df5baefd3..61f6971018 100644 --- a/apps/tree.c +++ b/apps/tree.c @@ -110,6 +110,7 @@ const struct filetype filetypes[] = { { "rock",TREE_ATTR_ROCK,Icon_Plugin, VOICE_EXT_ROCK }, #ifdef HAVE_LCD_BITMAP { "fnt", TREE_ATTR_FONT,Icon_Font, VOICE_EXT_FONT }, + { "kbd", TREE_ATTR_KBD, Icon_Keyboard, VOICE_EXT_KBD }, #endif { "bmark",TREE_ATTR_BMARK, Icon_Bookmark, VOICE_EXT_BMARK }, #ifdef BOOTFILE_EXT diff --git a/apps/tree.h b/apps/tree.h index 85dd7f6e1c..6f91f7fcd1 100644 --- a/apps/tree.h +++ b/apps/tree.h @@ -241,6 +241,7 @@ struct tree_context { #define TREE_ATTR_MOD 0x0900 /* firmware file */ #define TREE_ATTR_RWPS 0x1000 /* remote-wps config file */ #define TREE_ATTR_BMP 0x1100 /* backdrop bmp file */ +#define TREE_ATTR_KBD 0x1200 /* keyboard file */ #define TREE_ATTR_MASK 0xFF00 /* which bits tree.c uses for file types */ void tree_get_filetypes(const struct filetype**, int*); diff --git a/firmware/SOURCES b/firmware/SOURCES index 3dbe1e12b9..d145176145 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -63,6 +63,7 @@ drivers/lcd-player.c #ifdef HAVE_LCD_BITMAP arabjoin.c bidi.c +hangul.c #if LCD_DEPTH == 1 drivers/lcd-recorder.c #elif LCD_DEPTH == 2 diff --git a/firmware/export/hangul.h b/firmware/export/hangul.h new file mode 100644 index 0000000000..d5f8b0636c --- /dev/null +++ b/firmware/export/hangul.h @@ -0,0 +1,23 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * + * Copyright (C) 2006 by Frank Dischner + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +extern const char jamo_table[51][3]; + +unsigned short hangul_join(unsigned short lead, unsigned short vowel, + unsigned short tail); diff --git a/firmware/hangul.c b/firmware/hangul.c new file mode 100644 index 0000000000..27123ccb64 --- /dev/null +++ b/firmware/hangul.c @@ -0,0 +1,101 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * + * Copyright (C) 2006 by Frank Dischner + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "hangul.h" + +const char jamo_table[51][3] = { + { 1, 0, 1}, + { 2, 0, 2}, + { 0, 0, 3}, + { 3, 0, 4}, + { 0, 0, 5}, + { 0, 0, 6}, + { 4, 0, 7}, + { 5, 0, 0}, + { 6, 0, 8}, + { 0, 0, 9}, + { 0, 0, 10}, + { 0, 0, 11}, + { 0, 0, 12}, + { 0, 0, 13}, + { 0, 0, 14}, + { 0, 0, 15}, + { 7, 0, 16}, + { 8, 0, 17}, + { 9, 0, 0}, + { 0, 0, 18}, + {10, 0, 19}, + {11, 0, 20}, + {12, 0, 21}, + {13, 0, 22}, + {14, 0, 0}, + {15, 0, 23}, + {16, 0, 24}, + {17, 0, 25}, + {18, 0, 26}, + {19, 0, 27}, + { 0, 1, 0}, + { 0, 2, 0}, + { 0, 3, 0}, + { 0, 4, 0}, + { 0, 5, 0}, + { 0, 6, 0}, + { 0, 7, 0}, + { 0, 8, 0}, + { 0, 9, 0}, + { 0, 10, 0}, + { 0, 11, 0}, + { 0, 12, 0}, + { 0, 13, 0}, + { 0, 14, 0}, + { 0, 15, 0}, + { 0, 16, 0}, + { 0, 17, 0}, + { 0, 18, 0}, + { 0, 19, 0}, + { 0, 20, 0}, + { 0, 21, 0}, +}; + +/* takes three jamo chars and joins them into one hangul */ +unsigned short hangul_join(unsigned short lead, unsigned short vowel, + unsigned short tail) +{ + unsigned short ch = 0xfffd; + + if (lead < 0x3131 || lead > 0x3163) + return ch; + lead = jamo_table[lead-0x3131][0]; + + if (vowel < 0x3131 || vowel > 0x3163) + return ch; + vowel = jamo_table[vowel-0x3131][1]; + + if (tail) { + if (tail < 0x3131 || tail > 0x3163) + return ch; + tail = jamo_table[tail-0x3131][2]; + if (!tail) + return ch; + } + + if (lead && vowel) + ch = tail + (vowel - 1)*28 + (lead - 1)*588 + 44032; + + return ch; +}