Index: apps/action.h
===================================================================
--- apps/action.h	(revision 27373)
+++ apps/action.h	(working copy)
@@ -239,6 +239,8 @@
     ACTION_KBD_DOWN,
     ACTION_KBD_MORSE_INPUT,
     ACTION_KBD_MORSE_SELECT,
+    ACTION_KBD_SCROLL_FWD,
+    ACTION_KBD_SCROLL_BACK,
     
 #ifdef HAVE_TOUCHSCREEN
     /* the following are helper actions for touchscreen targets,
Index: apps/recorder/keyboard.c
===================================================================
--- apps/recorder/keyboard.c	(revision 27373)
+++ apps/recorder/keyboard.c	(working copy)
@@ -43,14 +43,31 @@
 #define O_BINARY 0
 #endif
 
-
 #define DEFAULT_MARGIN 6
 #define KBD_BUF_SIZE 500
 
-#ifdef HAVE_TOUCHSCREEN
-#define MIN_GRID_SIZE   16
-#define GRID_SIZE(s, x)   \
-        ((s) == SCREEN_MAIN && MIN_GRID_SIZE > (x) ? MIN_GRID_SIZE: (x))
+#define PRED_TEXT
+#define MAX_DICT 10000 
+#define MAX_DICT_WORD 50
+
+#if (CONFIG_KEYPAD == ONDIO_PAD) \
+    || (CONFIG_KEYPAD == IPOD_1G2G_PAD) \
+    || (CONFIG_KEYPAD == IPOD_3G_PAD) \
+    || (CONFIG_KEYPAD == IPOD_4G_PAD) \
+    || (CONFIG_KEYPAD == IRIVER_IFP7XX_PAD) \
+    || (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) \
+    || (CONFIG_KEYPAD == IAUDIO_M3_PAD) \
+    || (CONFIG_KEYPAD == IRIVER_H10_PAD) \
+    || (CONFIG_KEYPAD == PBELL_VIBE500_PAD) \
+    || (CONFIG_KEYPAD == IRIVER_H100_PAD) \
+    || (CONFIG_KEYPAD == IRIVER_H300_PAD) \
+    || (CONFIG_KEYPAD == GIGABEAT_PAD) \
+    || (CONFIG_KEYPAD == GIGABEAT_S_PAD) \
+    || (CONFIG_KEYPAD == SANSA_E200_PAD) \
+    || (CONFIG_KEYPAD == SANSA_FUZE_PAD) \
+    || (CONFIG_KEYPAD == SANSA_C200_PAD) \
+    || (CONFIG_KEYPAD == SAMSUNG_YH_PAD)
+#define KBD_MODES /* uses 2 modes, picker and line edit */
 #endif
 
 #if (CONFIG_KEYPAD == IRIVER_H100_PAD) \
@@ -63,24 +80,20 @@
     || (CONFIG_KEYPAD == GIGABEAT_S_PAD) \
     || (CONFIG_KEYPAD == MROBE100_PAD) \
     || (CONFIG_KEYPAD == SANSA_E200_PAD) \
+    || (CONFIG_KEYPAD == SANSA_FUZE_PAD) \
     || (CONFIG_KEYPAD == PHILIPS_HDD1630_PAD) \
     || (CONFIG_KEYPAD == PHILIPS_SA9200_PAD) \
     || (CONFIG_KEYPAD == PBELL_VIBE500_PAD)
-/* certain key combos toggle input mode between keyboard input and Morse input */
+/* certain key combos change keyboard entry modes */
 #define KBD_TOGGLE_INPUT
 #endif
 
-#define CHANGED_PICKER 1
-#define CHANGED_CURSOR 2
-#define CHANGED_TEXT   3
-
 struct keyboard_parameters
 {
     unsigned short kbd_buf[KBD_BUF_SIZE];
     unsigned short max_line_len;
     int default_lines;
-    int last_k;
-    int last_i;
+    int nchars;
     int font_w;
     int font_h;
     int text_w;
@@ -99,12 +112,38 @@
     int page;
     int x;
     int y;
+#ifdef KBD_MODES
     bool line_edit;
-#ifdef HAVE_TOUCHSCREEN
-    bool show_buttons;
 #endif
 };
 
+enum { KBD_MODE_STANDARD=0,
+#ifdef HAVE_SCROLLWHEEL
+       KBD_MODE_SIDEWAYS,       
+       KBD_MODE_KEEP_ROLLIN,
+       KBD_MODE_SCROLL_1D,
+       KBD_MODE_ROWCOL,
+#endif
+#ifdef HAVE_MORSE_INPUT
+       KBD_MODE_MORSE,
+#endif
+       KBD_MODE_END 
+};
+
+char  *mode_labels[] = 
+{   ID2P(LANG_KBD_MODE_STANDARD), 
+#ifdef HAVE_SCROLLWHEEL
+    ID2P(LANG_KBD_MODE_SIDEWAYS), 
+    ID2P(LANG_KBD_MODE_KEEP_ROLLIN), 
+    ID2P(LANG_KBD_MODE_SCROLL_1D), 
+    ID2P(LANG_KBD_MODE_ROW_COL),
+#endif
+#ifdef HAVE_MORSE_INPUT
+    ID2P(LANG_KBD_MODE_MORSE),
+#endif
+    "Bad Mode!" 
+};
+
 struct edit_state
 {
     char* text;
@@ -115,12 +154,21 @@
     bool hangul;
     unsigned short hlead, hvowel, htail;
 #ifdef HAVE_MORSE_INPUT
-    bool morse_mode;
     bool morse_reading;
     unsigned char morse_code;
     int morse_tick;
 #endif
-    int changed;
+#if defined (HAVE_MORSE_INPUT) || defined (HAVE_SCROLLWHEEL)
+    int keyboard_mode;
+#endif
+#ifdef HAVE_SCROLLWHEEL
+    int prev_button;
+    bool row;
+#endif
+#ifdef PRED_TEXT
+    char pred_word[MAX_DICT_WORD+1];
+    int pred_input_len;
+#endif
 };
 
 static struct keyboard_parameters kbd_param[NB_SCREENS];
@@ -137,6 +185,61 @@
     0x73,0x55,0x4c,0x61,0x5a,0x80 };
 #endif
 
+static void kbd_calc_params(struct keyboard_parameters *pm,
+                            struct screen *sc, struct edit_state *state);
+static void kbd_draw_picker(struct keyboard_parameters *pm,
+                            struct screen *sc, struct edit_state *state);
+static void kbd_draw_edit_line(struct keyboard_parameters *pm,
+                               struct screen *sc, struct edit_state *state);
+static void kbd_inschar(struct edit_state *state, unsigned short ch);
+
+static void kbd_delchar(struct edit_state *state);
+
+#ifdef HAVE_MORSE_INPUT
+static void kbd_morse_select(struct edit_state *state);
+#endif /* HAVE_MORSE_INPUT */
+#ifdef HAVE_SCROLLWHEEL     
+static void kbd_scroll_fwd( struct edit_state *state, 
+                            struct keyboard_parameters *pm);
+static void kbd_scroll_back(struct edit_state *state, 
+                            struct keyboard_parameters *pm);
+#endif
+#ifdef PRED_TEXT
+static void kbd_learn_text(char *input);
+
+static void kbd_load_predicted(void);
+
+static void kbd_save_predicted(void);
+
+static void kbd_draw_predicted(struct keyboard_parameters *pm,
+                               struct screen *sc, struct edit_state *state);
+static void kbd_insert_predicted(struct edit_state *state);
+#endif
+static void kbd_picker_right( struct edit_state *state, 
+                              struct keyboard_parameters *pm );
+
+static void kbd_right( struct edit_state *state, 
+                       struct keyboard_parameters *pm );
+static void kbd_picker_left( struct edit_state *state, 
+                             struct keyboard_parameters *pm );
+static void kbd_left( struct edit_state *state, 
+                      struct keyboard_parameters *pm );
+static void kbd_down(struct edit_state *state, 
+                     struct keyboard_parameters *pm);
+static void kbd_up (struct edit_state *state, 
+                    struct keyboard_parameters *pm);
+static void kbd_cursor_right(struct edit_state *state);
+
+static void kbd_cursor_left(struct edit_state *state);
+
+static void kbd_backspace(struct edit_state *state);
+
+static void kbd_insert_current_char(struct edit_state *state, 
+                                 struct keyboard_parameters *pm);
+static void kbd_select(struct edit_state *state, 
+                       struct keyboard_parameters *pm);
+static void kbd_none(struct edit_state *state);
+
 /* Loads a custom keyboard into memory
    call with NULL to reset keyboard    */
 int load_kbd(unsigned char* filename)
@@ -144,7 +247,6 @@
     int fd, l;
     int i, line_len, max_line_len;
     unsigned char buf[4];
-    unsigned short *pbuf;
 
     if (filename == NULL)
     {
@@ -156,11 +258,10 @@
     if (fd < 0)
         return 1;
 
-    pbuf = kbd_param[0].kbd_buf;
     line_len = 0;
     max_line_len = 1;
-    i = 1;
-    while (read(fd, buf, 1) == 1 && i < KBD_BUF_SIZE-1)
+    i = 0;
+    while (read(fd, buf, 1) == 1 && i < KBD_BUF_SIZE)
     {
         /* check how many bytes to read for this character */
         static const unsigned char sizes[4] = { 0x80, 0xe0, 0xf0, 0xf5 };
@@ -186,17 +287,17 @@
         utf8decode(buf, &ch);
         if (ch != 0xFEFF && ch != '\r') /* skip BOM & carriage returns */
         {
+            FOR_NB_SCREENS(l)
+                kbd_param[l].kbd_buf[i] = ch;
             i++;
             if (ch == '\n')
             {
                 if (max_line_len < line_len)
                     max_line_len = line_len;
-                *pbuf = line_len;
-                pbuf += line_len + 1;
                 line_len = 0;
             }
             else
-                pbuf[++line_len] = ch;
+                line_len++;
         }
     }
 
@@ -205,20 +306,11 @@
 
     if (max_line_len < line_len)
         max_line_len = line_len;
-    if (i == 1 || line_len != 0) /* ignore last empty line */
-    {
-        *pbuf = line_len;
-        pbuf += line_len + 1;
-    }
-    *pbuf = 0xFEFF; /* mark end of characters */
-    i++;
+
     FOR_NB_SCREENS(l)
     {
         struct keyboard_parameters *pm = &kbd_param[l];
-#if NB_SCREENS > 1
-        if (l > 0)
-            memcpy(pm->kbd_buf, kbd_param[0].kbd_buf, i*sizeof(unsigned short));
-#endif
+        pm->nchars = i;
         /* initialize parameters */
         pm->x = pm->y = pm->page = 0;
         pm->default_lines = 0;
@@ -228,20 +320,31 @@
     return 0;
 }
 
-/* helper function to spell a char */
+/* helper function to spell a char if voice UI is enabled */
 static void kbd_spellchar(unsigned short c)
 {
-    unsigned char tmp[5];
-    /* store char to pass to talk_spell */
-    unsigned char* utf8 = utf8encode(c, tmp);
-    *utf8 = 0;
+    if (global_settings.talk_menu) /* voice UI? */
+    {
+        unsigned char tmp[5];
+        /* store char to pass to talk_spell */
+        unsigned char* utf8 = utf8encode(c, tmp);
+        *utf8 = 0;
 
-    if (c == ' ')
-        talk_id(VOICE_BLANK, false);
-    else
-        talk_spell(tmp, false);
+        if (c == ' ')
+            talk_id(VOICE_BLANK, false);
+        else
+            talk_spell(tmp, false);
+    }
 }
 
+#ifdef KBD_MODES
+static void say_edit(void)
+{
+    if (global_settings.talk_menu)
+        talk_id(VOICE_EDIT, false);
+}
+#endif
+
 static void kbd_inschar(struct edit_state *state, unsigned short ch)
 {
     int i, j, len;
@@ -259,7 +362,6 @@
         memmove(utf8 + j, utf8, len - i + 1);
         memcpy(utf8, tmp, j);
         state->editpos++;
-        state->changed = CHANGED_TEXT;
     }
 }
 
@@ -276,55 +378,615 @@
         utf8 = state->text + i;
         j = utf8seek(utf8, 1);
         memmove(utf8, utf8 + j, len - i - j + 1);
-        state->changed = CHANGED_TEXT;
     }
 }
 
 /* Lookup k value based on state of param (pm) */
-static unsigned short get_kbd_ch(struct keyboard_parameters *pm, int x, int y)
+static unsigned short get_kbd_ch(const struct keyboard_parameters *pm)
 {
-    int i = 0, k = pm->page*pm->lines + y, n;
-    unsigned short *pbuf;
-    if (k >= pm->last_k)
+    int k = (pm->page*pm->lines + pm->y)*pm->max_chars + pm->x;
+    return (k < pm->nchars)? pm->kbd_buf[k]: ' ';
+}
+
+#ifdef KBD_TOGGLE_INPUT
+static void kbd_switch_mode ( struct edit_state *state ) 
+{
+    int l;
+    FOR_NB_SCREENS(l)
     {
-        i = pm->last_i;
-        k -= pm->last_k;
+#ifdef KBD_MODES
+        kbd_param[l].line_edit = false;
+#endif
+        kbd_param[l].x = 0;
+        kbd_param[l].y = 0;
+        kbd_param[l].page = 0;
     }
-    for (pbuf = &pm->kbd_buf[i]; (i = *pbuf) != 0xFEFF; pbuf += i + 1)
+    state->keyboard_mode++;
+    if ( state->keyboard_mode == KBD_MODE_END )
+        state->keyboard_mode = KBD_MODE_STANDARD;
+    splash(HZ,mode_labels[state->keyboard_mode-KBD_MODE_STANDARD] );
+}
+#endif
+
+static void kbd_page_flip ( struct edit_state *state, 
+                            struct keyboard_parameters *pm ) 
+{
+    (void) state;
+#ifdef HAVE_MORSE_INPUT
+    if (state->keyboard_mode == KBD_MODE_MORSE)
+        return;
+#endif
+    if (++pm->page >= pm->pages)
+        pm->page = 0;
+
+    kbd_spellchar( get_kbd_ch(pm) );
+}
+
+#ifdef HAVE_SCROLLWHEEL     
+static void kbd_scroll_fwd( struct edit_state *state, 
+                            struct keyboard_parameters *pm ) 
+{
+    switch ( state->keyboard_mode ) 
     {
-        n = i ? (i + pm->max_chars - 1) / pm->max_chars : 1;
-        if (k < n) break;
-        k -= n;
+        case KBD_MODE_ROWCOL:
+            if ( state->row )
+                kbd_down( state, pm );
+            else
+                kbd_picker_right( state, pm );
+            break;
+
+        case KBD_MODE_KEEP_ROLLIN:
+            if ( state->prev_button == ACTION_KBD_SCROLL_FWD )
+                kbd_picker_right( state, pm );
+            break;
+
+        case KBD_MODE_MORSE:
+            kbd_cursor_right( state );
+            break;
+
+        case KBD_MODE_SCROLL_1D:
+            kbd_picker_right( state, pm );
+            break;
+
+        case KBD_MODE_SIDEWAYS:
+#ifdef KBD_MODES
+            if ( pm->line_edit )
+                kbd_cursor_right( state );
+            else
+#endif
+                kbd_picker_right( state, pm );
+            break;
+        case KBD_MODE_STANDARD:
+        default:
+            kbd_down( state, pm );
+            break;
     }
-    if (y == 0 && i != 0xFEFF)
+    state->prev_button = ACTION_KBD_SCROLL_FWD;
+}
+
+static void kbd_scroll_back( struct edit_state *state, 
+                             struct keyboard_parameters *pm ) 
+{
+    switch ( state->keyboard_mode ) 
     {
-        pm->last_k = pm->page*pm->lines - k;
-        pm->last_i = pbuf - pm->kbd_buf;
+        case KBD_MODE_ROWCOL:
+        if ( state->row )
+            kbd_up( state, pm );
+        else
+            kbd_picker_left( state, pm );
+            break;
+        case KBD_MODE_KEEP_ROLLIN:
+            if ( state->prev_button == ACTION_KBD_SCROLL_FWD ) 
+            {
+                kbd_insert_current_char( state, pm );
+                pm->x = pm->y = 0;
+            }
+            else 
+                kbd_down( state, pm );
+            break;
+        case KBD_MODE_SCROLL_1D:
+            kbd_picker_left( state, pm );
+            break;
+
+        case KBD_MODE_MORSE:
+            kbd_cursor_left( state );
+            break;
+            
+        case KBD_MODE_SIDEWAYS:
+#ifdef KBD_MODES
+            if ( pm->line_edit )
+                kbd_cursor_left( state );
+            else
+#endif
+            kbd_picker_left( state, pm );
+            break;
+
+        case KBD_MODE_STANDARD:
+        default:
+            kbd_up( state, pm );
     }
-    k = k * pm->max_chars + x;
-    return (*pbuf != 0xFEFF && k < *pbuf)? pbuf[k+1]: ' ';
+    state->prev_button = ACTION_KBD_SCROLL_BACK;
 }
+#endif
 
-static void kbd_calc_params(struct keyboard_parameters *pm,
-                            struct screen *sc, struct edit_state *state);
-static void kbd_draw_picker(struct keyboard_parameters *pm,
-                            struct screen *sc, struct edit_state *state);
-static void kbd_draw_edit_line(struct keyboard_parameters *pm,
-                               struct screen *sc, struct edit_state *state);
-#ifdef HAVE_TOUCHSCREEN
-static void kbd_draw_buttons(struct keyboard_parameters *pm, struct screen *sc);
-static int keyboard_touchscreen(struct keyboard_parameters *pm,
-                                struct screen *sc, struct edit_state *state);
+static void kbd_picker_right( struct edit_state *state, 
+                              struct keyboard_parameters *pm ) 
+{
+    (void) state;
+    if (++pm->x >= pm->max_chars) 
+    {
+#ifdef HAVE_SCROLLWHEEL
+        switch ( state->keyboard_mode ) 
+        {
+            case KBD_MODE_ROWCOL:
+                pm->x = pm->max_chars-1;
+                state->row = true;
+                return;
+            case KBD_MODE_SCROLL_1D:
+                kbd_down( state, pm );
+            case KBD_MODE_KEEP_ROLLIN:
+                pm->x = 0;
+                return;
+    /* FIXME: */
+    #ifdef HAVE_KBD_MODES
+            case KBD_MODE_SIDEWAYS:
+    #endif
+            default:
+                break;
+        }
+#endif    
+        if (++pm->page >= pm->pages)
+            pm->page = 0;
+        pm->x = 0;
+    }
+    kbd_spellchar( get_kbd_ch(pm) );
+}
+
+static void kbd_right( struct edit_state *state, 
+                       struct keyboard_parameters *pm ) 
+{
+#ifdef HAVE_SCROLLWHEEL
+    switch ( state->keyboard_mode ) 
+    {
+        case KBD_MODE_STANDARD:
+#ifdef KBD_MODES
+            if ( pm->line_edit )
+                kbd_cursor_right( state );
+            else
+#endif /* KBD_MODES */
+                kbd_picker_right( state, pm );
+            return;
+        
+        case KBD_MODE_SIDEWAYS:
+            kbd_down( state, pm );
+            return;
+        
+        case KBD_MODE_KEEP_ROLLIN:
+        case KBD_MODE_SCROLL_1D:
+        case KBD_MODE_ROWCOL:
+        case KBD_MODE_MORSE:
+            kbd_cursor_right( state );
+            return;
+        default:
+            break;
+    }
+#else /* (not) HAVE_SCROLLWHEEL */
+
+#ifdef KBD_MODES
+    if ( pm->line_edit )
+        kbd_cursor_right( state );
+    else
+#endif /* KBD_MODES */
+
+        kbd_picker_right( state, pm );
+#endif /* HAVE_SCROLLWHEEL */
+}
+
+static void kbd_picker_left( struct edit_state *state, 
+                             struct keyboard_parameters *pm ) 
+{
+    (void) state;
+    if (--pm->x < 0) 
+    {
+#ifdef HAVE_SCROLLWHEEL
+        switch ( state->keyboard_mode ) 
+        {
+            case KBD_MODE_ROWCOL:
+                pm->x = 0;
+                state->row = true;
+                return;
+            case KBD_MODE_SCROLL_1D:
+                kbd_up( state, pm );
+            case KBD_MODE_KEEP_ROLLIN:
+                pm->x = pm->max_chars - 1;
+                return;
+            default:
+                break;
+        }
+#endif    
+
+        if (--pm->page < 0)
+            pm->page = pm->pages - 1;
+        pm->x = pm->max_chars - 1;
+    }
+    kbd_spellchar( get_kbd_ch(pm) );
+}
+
+static void kbd_left( struct edit_state *state, 
+                      struct keyboard_parameters *pm ) 
+{
+#ifdef HAVE_SCROLLWHEEL
+    switch ( state->keyboard_mode ) 
+    {
+        case KBD_MODE_STANDARD:
+#ifdef KBD_MODES
+        if ( pm->line_edit )
+            kbd_cursor_left( state );
+        else
 #endif
-static void kbd_insert_selected(struct keyboard_parameters *pm,
-                                struct edit_state *state);
-static void kbd_backspace(struct edit_state *state);
-static void kbd_move_cursor(struct edit_state *state, int dir);
-static void kbd_move_picker_horizontal(struct keyboard_parameters *pm,
-                                       struct edit_state *state, int dir);
-static void kbd_move_picker_vertical(struct keyboard_parameters *pm,
-                                     struct edit_state *state, int dir);
+            kbd_picker_left( state, pm );
+        return;
+        
+        case KBD_MODE_SIDEWAYS:
+            kbd_up( state, pm );
+            return; 
+        case KBD_MODE_KEEP_ROLLIN:
+        case KBD_MODE_SCROLL_1D:
+        case KBD_MODE_ROWCOL:
+        case KBD_MODE_MORSE:
+            kbd_cursor_left( state );
+            return;
+        default:
+            return;
+    }
+#else /* (not) HAVE_SCROLLWHEEL */
 
+#ifdef KBD_MODES
+    if ( pm->line_edit )
+        kbd_cursor_left( state );
+    else
+#endif /* KBD_MODES */
+
+        kbd_picker_left( state, pm );    
+#endif /* HAVE_SCROLLWHEEL */
+
+}
+               
+static void kbd_down( struct edit_state *state, 
+                      struct keyboard_parameters *pm ) 
+{
+#ifdef HAVE_SCROLLWHEEL
+    if ( state->keyboard_mode == KBD_MODE_KEEP_ROLLIN ||
+         state->keyboard_mode == KBD_MODE_SCROLL_1D ||
+         state->keyboard_mode == KBD_MODE_ROWCOL ) 
+    {
+        if (++pm->y >= pm->lines) 
+        {
+           if (++pm->page >= pm->pages)
+                pm->page = 0;
+           pm->y = 0;
+        }
+        return;
+    }
+#endif
+#ifdef HAVE_MORSE_INPUT
+    if (state->keyboard_mode == KBD_MODE_MORSE) 
+    {
+#ifdef KBD_MODES
+        pm->line_edit = !pm->line_edit;
+        if (pm->line_edit)
+        say_edit();
+#endif
+        return;
+    }
+#endif /* HAVE_MORSE_INPUT */
+
+#ifdef KBD_MODES
+    if (pm->line_edit)
+    {
+        pm->y = 0;
+        pm->line_edit = false;
+    }
+    else if (++pm->y >= pm->lines)
+    {
+        pm->line_edit = true;
+        say_edit();
+    }
+    if (!pm->line_edit)
+    {
+        kbd_spellchar( get_kbd_ch(pm) );
+    }
+    return;
+#else /* KBD_MODES */
+
+    if (++pm->y >= pm->lines)
+        pm->y = 0;
+    
+    kbd_spellchar( get_kbd_ch(pm) );
+#endif
+}
+
+static void kbd_up ( struct edit_state *state, 
+                     struct keyboard_parameters *pm ) 
+{
+#ifdef HAVE_SCROLLWHEEL
+    if ( state->keyboard_mode == KBD_MODE_KEEP_ROLLIN ||
+         state->keyboard_mode == KBD_MODE_SCROLL_1D ||
+         state->keyboard_mode == KBD_MODE_ROWCOL ) 
+    {
+        if (--pm->y < 0) 
+        {
+            pm->y = pm->lines - 1;
+            if (--pm->page < 0)
+                pm->page = pm->pages - 1;
+        }
+        return;
+    }
+#endif
+#ifdef HAVE_MORSE_INPUT
+    if (state->keyboard_mode == KBD_MODE_MORSE)
+    {
+#ifdef KBD_MODES
+        pm->line_edit = !pm->line_edit;
+        if (pm->line_edit)
+            say_edit();
+#endif
+        return;
+    }
+#endif /* HAVE_MORSE_INPUT */
+#ifdef KBD_MODES
+    if (pm->line_edit)
+    {
+        pm->y = pm->lines - 1;
+        pm->line_edit = false;
+    }
+    else if (--pm->y < 0)
+    {
+        pm->line_edit = true;
+        say_edit();
+    }
+    if (!pm->line_edit)    
+        kbd_spellchar( get_kbd_ch(pm) );
+#else /* KBD_MODES */
+
+    if (--pm->y < 0)
+        pm->y = pm->lines - 1;
+    
+    kbd_spellchar( get_kbd_ch(pm) );
+#endif
+}
+
+static void kbd_cursor_right( struct edit_state *state ) 
+{
+    state->hangul = false;
+
+    if (state->editpos < state->len_utf8)
+    {
+        int c = utf8seek(state->text, ++state->editpos);
+        kbd_spellchar(state->text[c]);
+        return;
+    }
+#ifdef PRED_TEXT
+    if( state->pred_word[0] ) 
+    {
+        kbd_insert_predicted( state );
+        return;
+    }
+#endif
+#if CONFIG_CODEC == SWCODEC
+    if (global_settings.talk_menu)
+        pcmbuf_beep(1000, 150, 1500);
+#endif
+}
+
+static void kbd_cursor_left( struct edit_state *state ) 
+{
+    state->hangul = false;
+
+    if (state->editpos > 0)
+    {
+        int c = utf8seek(state->text, --state->editpos);
+        kbd_spellchar(state->text[c]);
+    }
+#if CONFIG_CODEC == SWCODEC
+    else if (global_settings.talk_menu)
+        pcmbuf_beep(1000, 150, 1500);
+#endif
+}
+
+static void kbd_backspace( struct edit_state *state ) 
+{
+    unsigned short ch;
+    
+    if (state->hangul)
+    {
+        if (state->htail)
+            state->htail = 0;
+        else if (state->hvowel)
+            state->hvowel = 0;
+        else
+            state->hangul = false;
+    }
+
+    kbd_delchar(state);
+
+    if (state->hangul)
+    {
+        if (state->hvowel)
+            ch = hangul_join(state->hlead, state->hvowel, state->htail);
+        else
+            ch = state->hlead;
+        kbd_inschar(state, ch);
+    }
+
+    if (global_settings.talk_menu)      /* voice UI? */
+        talk_spell(state->text, false);  /* speak revised text */
+}
+
+static void kbd_insert_current_char( struct edit_state *state, 
+                                     struct keyboard_parameters *pm ) 
+{
+
+    /* inserts the selected char */
+    /* find input char */
+    unsigned short ch;
+    ch = get_kbd_ch(pm);
+
+    /* check for hangul input */
+    if (ch >= 0x3131 && ch <= 0x3163)
+    {
+        unsigned short tmp;
+
+        if (!state->hangul)
+        {
+            state->hlead = state->hvowel = state->htail = 0;
+            state->hangul = true;
+        }
+
+        if (!state->hvowel)
+        {
+            state->hvowel = ch;
+        }
+        else if (!state->htail)
+        {
+            state->htail = ch;
+        }
+        else
+        {
+            /* previous hangul complete */
+            /* check whether tail is actually lead of next char */
+            tmp = hangul_join(state->htail, ch, 0);
+
+            if (tmp != 0xfffd)
+            {
+                tmp = hangul_join(state->hlead, state->hvowel, 0);
+                kbd_delchar(state);
+                kbd_inschar(state, tmp);
+                /* insert dummy char */
+                kbd_inschar(state, ' ');
+                state->hlead = state->htail;
+                state->hvowel = ch;
+                state->htail = 0;
+            }
+            else
+            {
+                state->hvowel = state->htail = 0;
+                state->hlead = ch;
+            }
+        }
+
+        /* combine into hangul */
+        tmp = hangul_join(state->hlead, state->hvowel, state->htail);
+
+        if (tmp != 0xfffd)
+        {
+            kbd_delchar(state);
+            ch = tmp;
+        }
+        else
+        {
+            state->hvowel = state->htail = 0;
+            state->hlead = ch;
+        }
+    }
+    else
+    {
+        state->hangul = false;
+    }
+
+    /* insert char */
+    kbd_inschar(state, ch);
+
+    if (global_settings.talk_menu)      /* voice UI? */
+        talk_spell(state->text, false);  /* speak revised text */
+}
+
+static void kbd_select( struct edit_state *state, 
+                        struct keyboard_parameters *pm ) 
+{
+#ifdef HAVE_SCROLLWHEEL
+    if ( state->keyboard_mode == KBD_MODE_ROWCOL ) 
+    {
+        if ( state->row ) 
+        {
+            state->row = false;
+            return;
+        }
+        else 
+        {
+            kbd_insert_current_char( state, pm );
+            return;
+        }
+    }
+#endif /* HAVE_SCROLLWHEEL */
+#ifdef KBD_MODES
+    if ( pm->line_edit ) 
+    {
+        kbd_backspace( state );
+        return;
+    }
+#endif
+#ifdef HAVE_MORSE_INPUT
+    if (state->keyboard_mode == KBD_MODE_MORSE)
+    {
+        state->morse_tick = current_tick;
+
+        if (!state->morse_reading)
+        {
+            logf("Morse char start:\n");
+            state->morse_reading = true;
+            state->morse_code = 1;
+        }
+    }
+    else
+#endif /* HAVE_MORSE_INPUT */
+        kbd_insert_current_char( state, pm );
+}
+
+#ifdef HAVE_MORSE_INPUT
+static void kbd_morse_select( struct edit_state *state )
+{         
+    if (state->keyboard_mode == KBD_MODE_MORSE && state->morse_reading)
+    {
+        state->morse_code <<= 1;
+        if ((current_tick - state->morse_tick) > HZ/5)
+            state->morse_code |= 0x01;
+    }
+}
+#endif /* HAVE_MORSE_INPUT */
+
+static void kbd_none( struct edit_state *state )
+{
+    (void) state;
+#ifdef HAVE_MORSE_INPUT
+    if (state->morse_reading)
+    {
+        int j;
+        logf("Morse: 0x%02x", state->morse_code);
+        state->morse_reading = false;
+
+        for (j = 0; morse_alphabets[j] != '\0'; j++)
+        {
+            if (morse_codes[j] == state->morse_code)
+                break;
+        }
+
+        if (morse_alphabets[j] == '\0')
+        {
+            logf("Morse code not found");
+            return;
+        }
+
+        /* turn off hangul input */
+        state->hangul = false;
+        kbd_inschar(state, morse_alphabets[j]);
+
+        if (global_settings.talk_menu)      /* voice UI? */
+            talk_spell(state->text, false);  /* speak revised text */
+    }
+#endif /* HAVE_MORSE_INPUT */
+}
+
 int kbd_input(char* text, int buflen)
 {
     bool done = false;
@@ -336,7 +998,6 @@
 #endif
     struct edit_state state;
     int l; /* screen loop variable */
-    unsigned short ch;
     int ret = 0; /* assume success */
     FOR_NB_SCREENS(l)
     {
@@ -355,15 +1016,24 @@
     /* initialize state */
     state.text = text;
     state.buflen = buflen;
+#ifdef HAVE_SCROLLWHEEL
+    state.prev_button = BUTTON_NONE;
+    state.row = true;
+#endif
+#ifdef PRED_TEXT
+    kbd_load_predicted();
+    state.pred_word[0] = 0;
+#endif
     /* Initial edit position is after last character */
     state.editpos = utf8length(state.text);
     state.cur_blink = true;
+#if defined (HAVE_MORSE_INPUT) || defined (HAVE_SCROLLWHEEL)
+    state.keyboard_mode = global_settings.keyboard_mode;
+#endif
 #ifdef HAVE_MORSE_INPUT
-    state.morse_mode = global_settings.morse_input;
     state.morse_reading = false;
 #endif
     state.hangul = false;
-    state.changed = 0;
 
     if (!kbd_loaded)
     {
@@ -371,9 +1041,8 @@
         FOR_NB_SCREENS(l)
         {
             struct keyboard_parameters *pm = &param[l];
-            unsigned short *pbuf;
             const unsigned char *p;
-            int len = 0;
+            int i = 0;
 
 #if LCD_WIDTH >= 160 && LCD_HEIGHT >= 96
             struct screen *sc = &screens[l];
@@ -414,22 +1083,10 @@
                 pm->max_line_len = 18;
             }
 
-            pbuf = pm->kbd_buf;
             while (*p)
-            {
-                p = utf8decode(p, &pbuf[len+1]);
-                if (pbuf[len+1] == '\n')
-                {
-                    *pbuf = len;
-                    pbuf += len+1;
-                    len = 0;
-                }
-                else
-                    len++;
-            }
-            *pbuf = len;
-            pbuf[len+1] = 0xFEFF;   /* mark end of characters */
+                p = utf8decode(p, &pm->kbd_buf[i++]);
 
+            pm->nchars = i;
             /* initialize parameters */
             pm->x = pm->y = pm->page = 0;
         }
@@ -441,11 +1098,19 @@
         struct keyboard_parameters *pm = &param[l];
         struct screen *sc = &screens[l];
         kbd_calc_params(pm, sc, &state);
+#ifdef HAVE_SCROLLWHEEL
+        pm->x = pm->y = pm->page = 0;
+#ifdef KBD_MODES
+        pm->line_edit = false;
+#endif
+#endif
     }
 
     if (global_settings.talk_menu)      /* voice UI? */
         talk_spell(state.text, true);   /* spell initial text */
 
+    //splash(HZ,mode_labels[state.keyboard_mode-KBD_MODE_STANDARD] );
+    
     while (!done)
     {
         /* These declarations are assigned to the screen on which the key
@@ -471,12 +1136,14 @@
             sc->clear_display();
             kbd_draw_picker(pm, sc, &state);
             kbd_draw_edit_line(pm, sc, &state);
-#ifdef HAVE_TOUCHSCREEN
-            if (pm->show_buttons)
-                kbd_draw_buttons(pm, sc);
+#ifdef PRED_TEXT
+            //if ( state.keyboard_mode == KBD_MODE_KEEP_ROLLIN ) 
+            kbd_draw_predicted(pm, sc, &state);
 #endif
         }
 
+        state.cur_blink = !state.cur_blink;
+
 #ifdef HAVE_BUTTONBAR
         /* draw the button bar */
         gui_buttonbar_set(&buttonbar, "Shift", "OK", "Del");
@@ -486,41 +1153,27 @@
         FOR_NB_SCREENS(l)
             screens[l].update();
 
-        state.cur_blink = !state.cur_blink;
-
-        button = get_action(
 #ifdef HAVE_MORSE_INPUT
-                state.morse_mode? CONTEXT_MORSE_INPUT:
+        button = get_action( state.keyboard_mode == KBD_MODE_MORSE ? 
+                         CONTEXT_MORSE_INPUT: CONTEXT_KEYBOARD, HZ/2 );
+#else
+        button = get_action( CONTEXT_KEYBOARD, HZ/2 );
 #endif
-                            CONTEXT_KEYBOARD, HZ/2);
+
 #if NB_SCREENS > 1
         button_screen = (get_action_statuscode(NULL) & ACTION_REMOTE) ? 1 : 0;
 #endif
         pm = &param[button_screen];
         sc = &screens[button_screen];
-#ifdef HAVE_TOUCHSCREEN
-        if (button == ACTION_TOUCHSCREEN)
-            button = keyboard_touchscreen(pm, sc, &state);
-#endif
 
-        /* Remap some buttons to allow to move
-         * cursor in line edit mode and morse mode. */
-        if (pm->line_edit
-#ifdef HAVE_MORSE_INPUT
-            || state.morse_mode
-#endif /* HAVE_MORSE_INPUT */
-            )
-        {
-            if (button == ACTION_KBD_LEFT)
-                button = ACTION_KBD_CURSOR_LEFT;
-            if (button == ACTION_KBD_RIGHT)
-                button = ACTION_KBD_CURSOR_RIGHT;
-        }
-
         switch ( button )
         {
             case ACTION_KBD_DONE:
                 /* accepts what was entered and continues */
+#ifdef PRED_TEXT
+                kbd_learn_text( state.text );
+                kbd_save_predicted();
+#endif
                 ret = 0;
                 done = true;
                 break;
@@ -529,118 +1182,64 @@
                 ret = -1;
                 done = true;
                 break;
-
+#ifdef HAVE_SCROLLWHEEL
+            case ACTION_KBD_SCROLL_FWD:
+                kbd_scroll_fwd( &state, pm );
+                break;
+        
+            case ACTION_KBD_SCROLL_BACK:
+                kbd_scroll_back( &state, pm );
+                break;
+#endif
             case ACTION_KBD_PAGE_FLIP:
-#ifdef HAVE_MORSE_INPUT
-                if (state.morse_mode)
-                    break;
-#endif
-                if (++pm->page >= pm->pages)
-                    pm->page = 0;
+                kbd_page_flip( &state, pm );
+                break;
 
-                state.changed = CHANGED_PICKER;
+#ifdef KBD_TOGGLE_INPUT
+            case ACTION_KBD_MORSE_INPUT:
+                kbd_switch_mode( &state );
                 break;
+#endif
+#ifdef HAVE_MORSE_INPUT
+            case ACTION_KBD_MORSE_SELECT:
+                kbd_morse_select( &state );
+                break;
+#endif /* HAVE_MORSE_INPUT */
 
             case ACTION_KBD_RIGHT:
-                kbd_move_picker_horizontal(pm, &state, 1);
+                kbd_right( &state, pm );
                 break;
 
             case ACTION_KBD_LEFT:
-                kbd_move_picker_horizontal(pm, &state, -1);
+                kbd_left( &state, pm );
                 break;
 
             case ACTION_KBD_DOWN:
-                kbd_move_picker_vertical(pm, &state, 1);
+                kbd_down( &state, pm );
                 break;
 
             case ACTION_KBD_UP:
-                kbd_move_picker_vertical(pm, &state, -1);
+                kbd_up( &state, pm );
                 break;
 
-#ifdef HAVE_MORSE_INPUT
-#ifdef KBD_TOGGLE_INPUT
-            case ACTION_KBD_MORSE_INPUT:
-                state.morse_mode = !state.morse_mode;
-                state.changed = CHANGED_PICKER;
-
-                FOR_NB_SCREENS(l)
-                {
-                    struct keyboard_parameters *pm = &param[l];
-                    int y = pm->main_y;
-                    pm->main_y = pm->old_main_y;
-                    pm->old_main_y = y;
-                }
-                break;
-#endif /* KBD_TOGGLE_INPUT */
-
-            case ACTION_KBD_MORSE_SELECT:
-                if (state.morse_mode && state.morse_reading)
-                {
-                    state.morse_code <<= 1;
-                    if ((current_tick - state.morse_tick) > HZ/5)
-                        state.morse_code |= 0x01;
-                }
-                break;
-#endif /* HAVE_MORSE_INPUT */
-
             case ACTION_KBD_SELECT:
-                /* select doubles as backspace in line_edit */
-                if (pm->line_edit)
-                    kbd_backspace(&state);
-                else
-#ifdef HAVE_MORSE_INPUT
-                if (state.morse_mode)
-                {
-                    state.morse_tick = current_tick;
-
-                    if (!state.morse_reading)
-                    {
-                        state.morse_reading = true;
-                        state.morse_code = 1;
-                    }
-                }
-                else
-#endif /* HAVE_MORSE_INPUT */
-                    kbd_insert_selected(pm, &state);
+                kbd_select( &state, pm );
                 break;
 
             case ACTION_KBD_BACKSPACE:
-                kbd_backspace(&state);
+                kbd_backspace( &state );
                 break;
 
             case ACTION_KBD_CURSOR_RIGHT:
-                kbd_move_cursor(&state, 1);
+                kbd_cursor_right( &state );
                 break;
 
             case ACTION_KBD_CURSOR_LEFT:
-                kbd_move_cursor(&state, -1);
+                kbd_cursor_left( &state );
                 break;
 
             case ACTION_NONE:
-#ifdef HAVE_MORSE_INPUT
-                if (state.morse_reading)
-                {
-                    int j;
-                    logf("Morse: 0x%02x", state.morse_code);
-                    state.morse_reading = false;
-
-                    for (j = 0; morse_alphabets[j] != '\0'; j++)
-                    {
-                        if (morse_codes[j] == state.morse_code)
-                            break ;
-                    }
-
-                    if (morse_alphabets[j] == '\0')
-                    {
-                        logf("Morse code not found");
-                        break ;
-                    }
-
-                    /* turn off hangul input */
-                    state.hangul = false;
-                    kbd_inschar(&state, morse_alphabets[j]);
-                }
-#endif /* HAVE_MORSE_INPUT */
+                kbd_none( &state );
                 break;
 
             default:
@@ -652,38 +1251,10 @@
                 break;
 
         } /* end switch */
-
         if (button != ACTION_NONE)
         {
             state.cur_blink = true;
         }
-        if (global_settings.talk_menu)  /* voice UI? */
-        {
-            if (state.changed == CHANGED_PICKER)
-            {
-                if (pm->line_edit)
-                {
-                    talk_id(VOICE_EDIT, false);
-                }
-                else
-#ifdef HAVE_MORSE_INPUT
-                /* FIXME: We should talk something like Morse mode.. */
-                if (!state.morse_mode)
-#endif
-                {
-                    ch = get_kbd_ch(pm, pm->x, pm->y);
-                    kbd_spellchar(ch);
-                }
-            }
-            else if (state.changed == CHANGED_CURSOR)
-            {
-                int c = utf8seek(state.text, state.editpos);
-                kbd_spellchar(state.text[c]);
-            }
-            else if (state.changed == CHANGED_TEXT)
-                talk_spell(state.text, false);  /* speak revised text */
-        }
-        state.changed = 0;
     }
 
 #ifdef HAVE_BUTTONBAR
@@ -692,11 +1263,10 @@
 
     if (ret < 0)
         splash(HZ/2, ID2P(LANG_CANCEL));
-
-#if defined(HAVE_MORSE_INPUT) && defined(KBD_TOGGLE_INPUT)
-    if (global_settings.morse_input != state.morse_mode)
+#if defined(HAVE_MORSE_INPUT) || defined(HAVE_SCROLLWHEEL)
+    if (global_settings.keyboard_mode != state.keyboard_mode)
     {
-        global_settings.morse_input = state.morse_mode;
+        global_settings.keyboard_mode = state.keyboard_mode;
         settings_save();
     }
 #endif /* HAVE_MORSE_INPUT && KBD_TOGGLE_INPUT */
@@ -714,15 +1284,9 @@
 {
     struct font* font;
     const unsigned char *p;
-    unsigned short ch, *pbuf;
+    unsigned short ch;
     int icon_w, sc_w, sc_h, w;
     int i, total_lines;
-#ifdef HAVE_TOUCHSCREEN
-    int button_h = 0;
-    bool flippage_button = false;
-    pm->show_buttons = (sc->screen_type == SCREEN_MAIN &&
-                                (touchscreen_get_mode() == TOUCHSCREEN_POINT));
-#endif
 
     pm->curfont = pm->default_lines ? FONT_SYSFIXED : FONT_UI;
     font = font_get(pm->curfont);
@@ -735,19 +1299,16 @@
         font = font_get(FONT_SYSFIXED);
         pm->font_h = font->height;
     }
-#ifdef HAVE_TOUCHSCREEN
-    pm->font_h = GRID_SIZE(sc->screen_type, pm->font_h);
-#endif
 
     /* find max width of keyboard glyphs.
      * since we're going to be adding spaces,
      * max width is at least their width */
     pm->font_w = font_get_width(font, ' ');
-    for (pbuf = pm->kbd_buf; *pbuf != 0xFEFF; pbuf += i)
+    for (i = 0; i < pm->nchars; i++)
     {
-        for (i = 0; ++i <= *pbuf; )
+        if (pm->kbd_buf[i] != '\n')
         {
-            w = font_get_width(font, pbuf[i]);
+            w = font_get_width(font, pm->kbd_buf[i]);
             if (pm->font_w < w)
                 pm->font_w = w;
         }
@@ -764,9 +1325,6 @@
             pm->text_w = w;
     }
 
-#ifdef HAVE_TOUCHSCREEN
-    pm->font_w = GRID_SIZE(sc->screen_type, pm->font_w);
-#endif
     /* calculate how many characters to put in a row. */
     icon_w = get_icon_width(sc->screen_type);
     sc_w = sc->getwidth();
@@ -777,20 +1335,62 @@
     if (pm->max_chars_text < 3 && icon_w > pm->text_w)
         pm->max_chars_text = sc_w / pm->text_w - 2;
 
+
+    i = 0;
+    /* Pad lines with spaces */
+    while (i < pm->nchars)
+    {
+        if (pm->kbd_buf[i] == '\n')
+        {
+            int k = pm->max_chars - i % ( pm->max_chars ) - 1;
+            int j;
+
+            if (k == pm->max_chars - 1)
+            {
+                pm->nchars--;
+
+                for (j = i; j < pm->nchars; j++)
+                {
+                    pm->kbd_buf[j] = pm->kbd_buf[j + 1];
+                }
+            }
+            else
+            {
+                if (pm->nchars + k - 1 >= KBD_BUF_SIZE)
+                {   /* We don't want to overflow the buffer */
+                    k = KBD_BUF_SIZE - pm->nchars;
+                }
+
+                for (j = pm->nchars + k - 1; j > i + k; j--)
+                {
+                    pm->kbd_buf[j] = pm->kbd_buf[j-k];
+                }
+
+                pm->nchars += k;
+                k++;
+
+                while (k--)
+                {
+                    pm->kbd_buf[i++] = ' ';
+                }
+            }
+        }
+        else
+        {
+            i++;
+        }
+    }
+    if (pm->nchars == 0)
+        pm->kbd_buf[pm->nchars++] = ' ';
+
     /* calculate pm->pages and pm->lines */
     sc_h = sc->getheight();
-#ifdef HAVE_TOUCHSCREEN
-    /* add space for buttons */
-    if (pm->show_buttons)
-    {
-        /* reserve place for OK/Del/Cancel buttons, use ui font for them */
-        button_h = GRID_SIZE(sc->screen_type, sc->getcharheight());
-        sc_h -= MAX(MIN_GRID_SIZE*2, button_h);
-    }
-recalc_param:
+#ifdef PRED_TEXT
+    /* reserve a line for predicted text */
+    sc_h -= pm->font_h;
 #endif
     pm->lines = (sc_h - BUTTONBAR_HEIGHT) / pm->font_h - 1;
-
+    
     if (pm->default_lines && pm->lines > pm->default_lines)
         pm->lines = pm->default_lines;
 
@@ -806,35 +1406,16 @@
     if (pm->keyboard_margin > DEFAULT_MARGIN)
         pm->keyboard_margin = DEFAULT_MARGIN;
 
-    total_lines = 0;
-    for (pbuf = pm->kbd_buf; (i = *pbuf) != 0xFEFF; pbuf += i + 1)
-        total_lines += (i ? (i + pm->max_chars - 1) / pm->max_chars : 1);
-
+    total_lines = (pm->nchars + pm->max_chars - 1) / pm->max_chars;
     pm->pages = (total_lines + pm->lines - 1) / pm->lines;
     pm->lines = (total_lines + pm->pages - 1) / pm->pages;
-#ifdef HAVE_TOUCHSCREEN
-    if (pm->pages > 1 && pm->show_buttons && !flippage_button)
-    {
-        /* add space for flip page button */
-        sc_h -= button_h;
-        flippage_button = true;
-        goto recalc_param;
-    }
-#endif
-    if (pm->page >= pm->pages)
-        pm->x = pm->y = pm->page = 0;
 
     pm->main_y = pm->font_h*pm->lines + pm->keyboard_margin;
     pm->keyboard_margin -= pm->keyboard_margin/2;
-#ifdef HAVE_TOUCHSCREEN
-    /* flip page button is put between piker and edit line */
-    if (flippage_button)
-        pm->main_y += button_h;
-#endif
 
 #ifdef HAVE_MORSE_INPUT
     pm->old_main_y = sc_h - pm->font_h - BUTTONBAR_HEIGHT;
-    if (state->morse_mode)
+    if (state->keyboard_mode == KBD_MODE_MORSE)
     {
         int y = pm->main_y;
         pm->main_y = pm->old_main_y;
@@ -848,7 +1429,7 @@
 {
     char outline[8];
 #ifdef HAVE_MORSE_INPUT
-    if (state->morse_mode)
+    if (state->keyboard_mode == KBD_MODE_MORSE)
     {
         const int w = 6, h = 8; /* sysfixed font width, height */
         int i, j, x, y;
@@ -899,31 +1480,51 @@
 #endif /* HAVE_MORSE_INPUT */
     {
         /* draw page */
-        int i, j;
-        int w, h;
-        unsigned short ch;
-        unsigned char *utf8;
+        int i, j, k;
 
         sc->setfont(pm->curfont);
 
-        for (j = 0; j < pm->lines; j++)
+        k = pm->page*pm->max_chars*pm->lines;
+
+        for (i = j = 0; k < pm->nchars; k++)
         {
-            for (i = 0; i < pm->max_chars; i++)
+            int w;
+            unsigned char *utf8;
+            utf8 = utf8encode(pm->kbd_buf[k], outline);
+            *utf8 = 0;
+
+            sc->getstringsize(outline, &w, NULL);
+            sc->putsxy(i*pm->font_w + (pm->font_w-w) / 2,
+                       j*pm->font_h, outline);
+
+            if (++i >= pm->max_chars)
             {
-                ch = get_kbd_ch(pm, i, j);
-                utf8 = utf8encode(ch, outline);
-                *utf8 = 0;
-
-                sc->getstringsize(outline, &w, &h);
-                sc->putsxy(i*pm->font_w + (pm->font_w-w) / 2,
-                           j*pm->font_h + (pm->font_h-h) / 2, outline);
+                i = 0;
+                if (++j >= pm->lines)
+                    break;
             }
         }
 
+#ifdef KBD_MODES
         if (!pm->line_edit)
+#endif
         {
             /* highlight the key that has focus */
             sc->set_drawmode(DRMODE_COMPLEMENT);
+#ifdef HAVE_SCROLLWHEEL
+            if ( (state->keyboard_mode == KBD_MODE_ROWCOL && state->row) ||
+                    (state->keyboard_mode == KBD_MODE_KEEP_ROLLIN && 
+                     state->prev_button != ACTION_KBD_SCROLL_FWD ))
+/* define this if you want selected row inversed instead of underlined */
+#ifdef KBD_INVERSE_ROW 
+                sc->fillrect(0,pm->font_h*pm->y,
+                    pm->font_w*pm->max_chars, pm->font_h);
+#else /* underline current row */
+                sc->hline(pm->font_w, pm->font_w*(pm->max_chars-1), 
+                    pm->font_h*(pm->y+1));
+#endif
+            else
+#endif /* HAVE_SCROLLWHEEL */
             sc->fillrect(pm->font_w*pm->x, pm->font_h*pm->y,
                          pm->font_w, pm->font_h);
             sc->set_drawmode(DRMODE_SOLID);
@@ -934,6 +1535,7 @@
 static void kbd_draw_edit_line(struct keyboard_parameters *pm,
                                struct screen *sc, struct edit_state *state)
 {
+    //char mode_label[20];
     char outline[8];
     unsigned char *utf8;
     int i = 0, j = 0, icon_w, w;
@@ -1007,279 +1609,270 @@
     if (state->hangul) /* draw underbar */
         sc->hline(i - pm->text_w, i, pm->main_y + pm->font_h - 1);
 
+#ifdef KBD_TOGGLE_INPUT
     if (pm->line_edit)
     {
         sc->set_drawmode(DRMODE_COMPLEMENT);
         sc->fillrect(0, y + 2, sc_w, pm->font_h + 2);
         sc->set_drawmode(DRMODE_SOLID);
     }
+
+#if 1
+    sc->putsxy(0, sc->getheight()-pm->font_h,  
+               P2STR((unsigned char *)mode_labels[state->keyboard_mode]) );
+#endif
+#endif
 }
 
-#ifdef HAVE_TOUCHSCREEN
-static void kbd_draw_buttons(struct keyboard_parameters *pm, struct screen *sc)
+#ifdef PRED_TEXT
+/* This is a very simple dictionary based text predictor.
+   The dictionary file is one word per line (\n terminated.)
+   at /.rockbox/pred_dict.txt.
+
+   The first word in the file that matches the letters input 
+   will be offered for completion. The dictionary should be
+   ordered with most common words first for better matching.
+   Dictionary words can be phrases containing spaces but the 
+   code will only match the first phrase with the same first
+   word.
+
+   Needs to reboot to reload dictionary.
+   
+*/
+#include "ctype.h"
+#include "string.h"
+
+static bool dict_loaded = false, dict_changed = false;
+static char dictionary[MAX_DICT];
+
+static void kbd_learn_text(char *input)
 {
-    struct viewport vp;
-    int button_h, text_h, text_y;
-    int sc_w = sc->getwidth(), sc_h = sc->getheight();
-    viewport_set_defaults(&vp, sc->screen_type);
-    vp.flags |= VP_FLAG_ALIGN_CENTER;
-    sc->set_viewport(&vp);
-    text_h = sc->getcharheight();
-    button_h = GRID_SIZE(sc->screen_type, text_h);
-    text_y = (button_h - text_h) / 2 + 1;
-    vp.x = 0;
-    vp.y = 0;
-    vp.width = sc_w;
-    vp.height = button_h;
-    /* draw buttons */
-    if (pm->pages > 1)
+    /* dictionary end, word start, word end */
+    int  de,ws,we,i,len;
+    char *line, word[MAX_DICT_WORD+1];
+
+    for ( de = 0; dictionary[de]; de++ )
+    if ( de == MAX_DICT )
+        return;
+    
+
+// 01234567890
+// Bob_Dora_Steve_
+//    mmm0  
+//    s  e
+// mmm0xxxxxxxxxxxx    
+// aaa0
+
+    line = input;
+
+    while(1)
     {
-        /* button to flip page. */
-        vp.y = pm->lines*pm->font_h;
-        sc->hline(0, sc_w - 1, 0);
-        sc->putsxy(0, text_y, ">");
+        /* find word start */
+        for ( ws = 0; line[ws] == ' '; ws++ );
+        if ( line[ws] == 0 )
+             return;
+        /* find word end */
+        for ( we = ws; line[we] != ' ' && line[we] != 0 ; we++ );
+        len = we-ws;
+        if ( de+len >= MAX_DICT ) {
+            splash(HZ,"No room.");return;}
+
+        if ( len >= MAX_DICT_WORD ) {
+            splash(HZ,"Word too big.");return;}
+        
+        /* don't save small words */
+        if ( len < 3 ) 
+        {
+            line += we;
+            continue;
+        }
+            
+        /* check if it's in there already */
+        /* FIXME: do something smart about case */
+        for ( i=0; i < len; i++ )
+            word[i] = line[i+ws];
+        word[len]=0;
+        if ( strstr( dictionary, word ) != NULL ) 
+        {
+            line += we;
+            continue;
+        }
+
+        splashf(HZ/3,"Adding %s",word);
+        dict_changed = true;
+
+        /* shift dictionary forward */
+        memmove( dictionary+len+1, dictionary, de );
+
+        /* copy word */
+        strcpy( dictionary, word );
+        dictionary[len]='\n';
+        de += len;
+        line += we;
     }
-    /* OK/Del/Cancel buttons */
-    button_h = MAX(MIN_GRID_SIZE*2, button_h);
-    text_y = (button_h - text_h) / 2 + 1;
-    vp.y = sc_h - button_h - 1;
-    vp.height = button_h;
-    sc->hline(0, sc_w - 1, 0);
-    vp.width = sc_w/3;
-    sc->putsxy(0, text_y, str(LANG_KBD_OK));
-    vp.x += vp.width;
-    sc->vline(0, 0, button_h);
-    sc->putsxy(0, text_y, str(LANG_KBD_DELETE));
-    vp.x += vp.width;
-    sc->vline(0, 0, button_h);
-    sc->putsxy(0, text_y, str(LANG_KBD_CANCEL));
-    sc->set_viewport(NULL);
 }
 
-static int keyboard_touchscreen(struct keyboard_parameters *pm,
-                                struct screen *sc, struct edit_state *state)
+static void kbd_save_predicted()
 {
-    short x, y;
-    const int button = action_get_touchscreen_press(&x, &y);
-    const int sc_w = sc->getwidth(), sc_h = sc->getheight();
-    int button_h = MAX(MIN_GRID_SIZE*2, sc->getcharheight());
-#ifdef HAVE_MORSE_INPUT
-    if (state->morse_mode && y < pm->main_y - pm->keyboard_margin)
-    {
-        /* don't return ACTION_NONE since it has effect in morse mode. */
-        return button == BUTTON_TOUCHSCREEN? ACTION_KBD_SELECT:
-               button & BUTTON_REL? ACTION_KBD_MORSE_SELECT: ACTION_STD_OK;
-    }
-#else
-    (void) state;
-#endif
-    if (x < 0 || y < 0)
-        return ACTION_NONE;
-    if (y < pm->lines*pm->font_h)
-    {
-        if (x/pm->font_w < pm->max_chars)
+    int length;
+    
+    if ( dict_loaded && dict_changed )
+    {    
+        int fd = open("/.rockbox/pred_dict.txt", O_WRONLY|O_BINARY);
+        if (fd < 0)
         {
-            /* picker area */
-            state->changed = CHANGED_PICKER;
-            pm->x = x/pm->font_w;
-            pm->y = y/pm->font_h;
-            pm->line_edit = false;
-            if (button == BUTTON_REL)
-                return ACTION_KBD_SELECT;
+            splash(HZ,"Can't open file");
+            return;
         }
-    }
-    else if (y < pm->main_y - pm->keyboard_margin)
-    {
-        /* button to flip page */
-        if (button == BUTTON_REL)
-            return ACTION_KBD_PAGE_FLIP;
-    }
-    else if (y < sc_h - button_h)
-    {
-        /* edit line */
-        if (button & (BUTTON_REPEAT|BUTTON_REL))
+        for ( length = 0; dictionary[length]; length++ )
+            if ( length == MAX_DICT )
+                break;
+            //ssize_t write(int fd, const void* buf, size_t count)
+        if ( write( fd, dictionary, length ) != length )
         {
-            if (x < sc_w/2)
-                return ACTION_KBD_CURSOR_LEFT;
-            else
-                return ACTION_KBD_CURSOR_RIGHT;
+            splash(HZ,"Write error");
         }
+        close(fd);
     }
-    else
+    return;
+}
+    
+     
+static void kbd_load_predicted()
+{
+    int length;
+    
+    if ( ! dict_loaded ) 
     {
-        /* OK/Del/Cancel button */
-        if (button == BUTTON_REL)
-        {
-            if (x < sc_w/3)
-                return ACTION_KBD_DONE;
-            else if (x < (sc_w/3) * 2)
-                return ACTION_KBD_BACKSPACE;
-            else
-                return ACTION_KBD_ABORT;
-        }
-    }
-    return ACTION_NONE;
+        int fd = open("/.rockbox/pred_dict.txt", O_RDONLY|O_BINARY);
+        if (fd < 0)
+            return; /* File Open Error */
+        length = read(fd, dictionary, MAX_DICT);
+        if (length < 0)                 
+            return; /* Read Error */
+            else {
+                dictionary[length] = 0;
+                dict_loaded = true;
+            }
+        close(fd);
+    }   
 }
-#endif
 
-/* inserts the selected char */
-static void kbd_insert_selected(struct keyboard_parameters *pm,
-                                struct edit_state *state)
+static void kbd_draw_predicted(struct keyboard_parameters *pm,
+                               struct screen *sc, struct edit_state *state)
 {
-    /* find input char */
-    unsigned short ch = get_kbd_ch(pm, pm->x, pm->y);
+    int i, left = -1, length;
+    static int  last_editpos = 0;
+    char *partial, *dict;
 
-    /* check for hangul input */
-    if (ch >= 0x3131 && ch <= 0x3163)
+    if ( !dict_loaded )
+        return;
+    
+    /* be lazy if edit cursor hasn't moved */
+    if ( state->editpos == last_editpos )
     {
-        unsigned short tmp;
+        sc->putsxy(0, pm->main_y+pm->font_h, state->pred_word);
+        return;
+    }
+    else
+        last_editpos = state->editpos;
+    
+    /* Start with nothing predicted */
+    state->pred_word[0] = 0; 
 
-        if (!state->hangul)
-        {
-            state->hlead = state->hvowel = state->htail = 0;
-            state->hangul = true;
-        }
+    dict = dictionary;            /* Points to current dictionary word */
+    i = state->editpos;
 
-        if (!state->hvowel)
+    if ( state->editpos != state->len_utf8 )
+        return;
+
+    while( --i >= 0 )     /* find left of word */
+        if ( state->text[utf8seek(state->text,i)] != ' ' )
+            left = i;
+        else
+            break;
+
+    if ( left == -1 ) /* whitespace */
+        return;
+
+    /* search for matching word */
+    length = utf8seek(state->text,state->editpos) - 
+                    utf8seek(state->text,left);
+
+    partial = &state->text[utf8seek(state->text,left)];
+    for ( i = 0; i <= length ; )
+    {
+        if ( dict[i] == '\n' )
         {
-            state->hvowel = ch;
+            /* already typed out this whole word, look for another */
+            dict += i +1;
+            i = 0;
+            continue;
         }
-        else if (!state->htail)
+        if ( dict[i] == 0 )
         {
-            state->htail = ch;
+            /* end of dictionary, give up */
+            return;
         }
-        else
+        if ( partial[i] == dict[i] )
         {
-            /* previous hangul complete */
-            /* check whether tail is actually lead of next char */
-            tmp = hangul_join(state->htail, ch, 0);
-
-            if (tmp != 0xfffd)
-            {
-                tmp = hangul_join(state->hlead, state->hvowel, 0);
-                kbd_delchar(state);
-                kbd_inschar(state, tmp);
-                /* insert dummy char */
-                kbd_inschar(state, ' ');
-                state->hlead = state->htail;
-                state->hvowel = ch;
-                state->htail = 0;
-            }
-            else
-            {
-                state->hvowel = state->htail = 0;
-                state->hlead = ch;
-            }
+            i++;
+            continue;
         }
-
-        /* combine into hangul */
-        tmp = hangul_join(state->hlead, state->hvowel, state->htail);
-
-        if (tmp != 0xfffd)
+        /* Case fuzz on first letter */
+        if ( !i && tolower(partial[i]) == tolower(dict[i]) )
         {
-            kbd_delchar(state);
-            ch = tmp;
+            i++;
+            continue;
         }
-        else
+        if ( i == length ) /* got a match */
         {
-            state->hvowel = state->htail = 0;
-            state->hlead = ch;
+            break; 
         }
+        /* mismatch, reset to next word */
+        while ( dict[++i] != '\n' )
+            if ( dict[i] == 0 )
+                return;
+        dict += i + 1;
+        i = 0;
     }
-    else
-    {
-        state->hangul = false;
-    }
 
-    /* insert char */
-    kbd_inschar(state, ch);
-}
+    /* get length of dictionary word in bytes */
+    for ( i = 0 ; dict[i] != '\n' && dict[i] != 0 ; 
+            i += utf8seek( dict,1) ) 
+        if ( i >= MAX_DICT_WORD ) 
+        {
+            i = MAX_DICT_WORD;
+            break;
+        }
+    /* copy */
+    state->pred_word[i] = 0;
+    while ( --i >= 0 )
+        state->pred_word[i] = dict[i];
+    state->pred_input_len = length;
 
-static void kbd_backspace(struct edit_state *state)
-{
-    unsigned short ch;
-    if (state->hangul)
-    {
-        if (state->htail)
-            state->htail = 0;
-        else if (state->hvowel)
-            state->hvowel = 0;
-        else
-            state->hangul = false;
-    }
-
-    kbd_delchar(state);
-
-    if (state->hangul)
-    {
-        if (state->hvowel)
-            ch = hangul_join(state->hlead, state->hvowel, state->htail);
-        else
-            ch = state->hlead;
-        kbd_inschar(state, ch);
-    }
+    sc->putsxy(0, pm->main_y+pm->font_h, state->pred_word);
+    
+    
 }
 
-static void kbd_move_cursor(struct edit_state *state, int dir)
+static void kbd_insert_predicted(struct edit_state *state)
 {
-    state->hangul = false;
-    state->editpos += dir;
-
-    if (state->editpos >= 0 && state->editpos <= state->len_utf8)
+    int i;
+    
+    i = utf8seek(state->text,state->editpos);
+    if ( i + (int)strlen(state->pred_word) - 
+         state->pred_input_len >= state->buflen )
+        return; 
+    
+    if( state->pred_word[0] ) 
     {
-        state->changed = CHANGED_CURSOR;
-    }
-    else
-    {
-        state->editpos -= dir;
-#if CONFIG_CODEC == SWCODEC
-        if (global_settings.talk_menu)
-            pcmbuf_beep(1000, 150, 1500);
-#endif
-    }
-}
+        strcpy( i + state->text,
+                state->pred_word + state->pred_input_len );
 
-static void kbd_move_picker_horizontal(struct keyboard_parameters *pm,
-                                       struct edit_state *state, int dir)
-{
-    state->changed = CHANGED_PICKER;
-
-    pm->x += dir;
-    if (pm->x < 0)
-    {
-        if (--pm->page < 0)
-            pm->page = pm->pages - 1;
-        pm->x = pm->max_chars - 1;
-    }
-    else if (pm->x >= pm->max_chars)
-    {
-        if (++pm->page >= pm->pages)
-            pm->page = 0;
-        pm->x = 0;
-    }
-}
-
-static void kbd_move_picker_vertical(struct keyboard_parameters *pm,
-                                     struct edit_state *state, int dir)
-{
-    state->changed = CHANGED_PICKER;
-
-#ifdef HAVE_MORSE_INPUT
-    if (state->morse_mode)
-    {
-        pm->line_edit = !pm->line_edit;
+        while ( state->text[utf8seek(state->text,state->editpos )] != 0 )
+            state->editpos++;
         return;
     }
-#endif /* HAVE_MORSE_INPUT */
-
-    pm->y += dir;
-    if (pm->line_edit)
-    {
-        pm->y = (dir > 0 ? 0 : pm->lines - 1);
-        pm->line_edit = false;
-    }
-    else if (pm->y < 0 || pm->y >= pm->lines)
-    {
-        pm->line_edit = true;
-    }
 }
+#endif
Index: apps/plugins/frotz/frotz.h
===================================================================
--- apps/plugins/frotz/frotz.h	(revision 27373)
+++ apps/plugins/frotz/frotz.h	(working copy)
@@ -520,7 +520,8 @@
 #define ERR_REPORT_ALWAYS (2)
 #define ERR_REPORT_FATAL (3)
 
-#define ERR_DEFAULT_REPORT_MODE ERR_REPORT_ONCE
+//#define ERR_DEFAULT_REPORT_MODE ERR_REPORT_ONCE
+#define ERR_DEFAULT_REPORT_MODE ERR_REPORT_NEVER
 
 
 /*** Various global functions ***/
Index: apps/plugins/frotz/err.c
===================================================================
--- apps/plugins/frotz/err.c	(revision 27373)
+++ apps/plugins/frotz/err.c	(working copy)
@@ -25,7 +25,7 @@
    player prefs are specified, or replace report_zstrict_error() 
    completely if you want to change the way errors are reported. */
 
-/* int err_report_mode = ERR_DEFAULT_REPORT_MODE; */
+int err_report_mode = ERR_DEFAULT_REPORT_MODE; 
 
 static int error_count[ERR_NUM_ERRORS];
 
Index: apps/lang/english.lang
===================================================================
--- apps/lang/english.lang	(revision 27373)
+++ apps/lang/english.lang	(working copy)
@@ -12619,3 +12619,116 @@
     *: "Update on Stop"
   </voice>
 </phrase>
+<phrase>
+  id: LANG_KBD_MODE
+  desc: in settings.general.system
+  user: core
+  <source>
+    *: "Keyboard Mode"
+  </source>
+  <dest>
+    *: "Keyboard Mode"
+  </dest>
+  <voice>
+    *: "Keyboard Mode"
+  </voice>
+</phrase>
+<phrase>
+  id: LANG_KBD_MODE_STANDARD
+  desc: in settings.general.system
+  user: core
+  <source>
+    *: "Standard"
+  </source>
+  <dest>
+    *: "Standard"
+  </dest>
+  <voice>
+    *: "Standard"
+  </voice>
+</phrase>
+<phrase>
+  id: LANG_KBD_MODE_SIDEWAYS
+  desc: in settings.general.system
+  user: core
+  <source>
+    *: "Sideways"
+  </source>
+  <dest>
+    *: "Sideways"
+  </dest>
+  <voice>
+    *: "Sideways"
+  </voice>
+</phrase>
+<phrase>
+  id: LANG_KBD_MODE_MORSE
+  desc: in settings.general.system
+  user: core
+  <source>
+    *: "Morse"
+  </source>
+  <dest>
+    *: "Morse"
+  </dest>
+  <voice>
+    *: "Morse"
+  </voice>
+</phrase>
+<phrase>
+  id: LANG_KBD_MODE_KEEP_ROLLIN
+  desc: in settings.general.system
+  user: core
+  <source>
+    *: "Keep Rollin"
+  </source>
+  <dest>
+    *: "Keep Rollin"
+  </dest>
+  <voice>
+    *: "Keep Rollin"
+  </voice>
+</phrase>
+<phrase>
+  id: LANG_KBD_MODE_SCROLL_1D
+  desc: in settings.general.system
+  user: core
+  <source>
+    *: "Scroll 1D"
+  </source>
+  <dest>
+    *: "Scroll 1D"
+  </dest>
+  <voice>
+    *: "Scroll won dee"
+  </voice>
+</phrase>
+<phrase>
+  id: LANG_KBD_MODE_ROW_COL
+  desc: in settings.general.system
+  user: core
+  <source>
+    *: "Row-Column"
+  </source>
+  <dest>
+    *: "Row-Column"
+  </dest>
+  <voice>
+    *: "Row Colum"
+  </voice>
+</phrase>
+<phrase>
+  id: LANG_WHEEL_SPEED
+  desc: Display for setting IPOD wheel spped
+  user: core
+  <source>
+    *: "Thumb Wheel Speed"
+  </source>
+  <dest>
+    *: "Thumb Wheel Speed"
+  </dest>
+  <voice>
+    *: "Thumb Wheel Speed"
+  </voice>
+</phrase>
+
Index: apps/settings.h
===================================================================
--- apps/settings.h	(revision 27373)
+++ apps/settings.h	(working copy)
@@ -814,10 +814,14 @@
     int compressor_release_time;
 #endif
 
-#ifdef HAVE_MORSE_INPUT
-    bool morse_input; /* text input method setting */
+#if defined(HAVE_MORSE_INPUT) || defined(HAVE_SCROLLWHEEL)
+    int keyboard_mode;
 #endif
 
+#ifdef IPOD_ARCH
+    int wheel_speed;
+#endif
+
 #ifdef HAVE_HOTKEY
     /* hotkey assignments - acceptable values are in
        hotkey_action enum in onplay.h */
Index: apps/menus/settings_menu.c
===================================================================
--- apps/menus/settings_menu.c	(revision 27373)
+++ apps/menus/settings_menu.c	(working copy)
@@ -259,10 +259,14 @@
 MENUITEM_SETTING(usb_keypad_mode, &global_settings.usb_keypad_mode, NULL);
 #endif
 
-#ifdef HAVE_MORSE_INPUT
-MENUITEM_SETTING(morse_input, &global_settings.morse_input, NULL);
+#if defined(HAVE_MORSE_INPUT) || defined(HAVE_SCROLLWHEEL)
+MENUITEM_SETTING(keyboard_mode, &global_settings.keyboard_mode, NULL);
 #endif
 
+#ifdef IPOD_ARCH
+MENUITEM_SETTING(wheel_speed, &global_settings.wheel_speed, NULL);
+#endif
+
 #ifdef HAVE_BUTTON_LIGHT
 MENUITEM_SETTING(buttonlight_timeout, &global_settings.buttonlight_timeout, NULL);
 #endif
@@ -286,8 +290,8 @@
 #endif
             &poweroff,
             &limits_menu,
-#ifdef HAVE_MORSE_INPUT
-            &morse_input,
+#if defined(HAVE_MORSE_INPUT) || defined(HAVE_SCROLLWHEEL)
+            &keyboard_mode,
 #endif
 #if CONFIG_CODEC == MAS3507D
             &line_in,
@@ -313,6 +317,9 @@
 #if CONFIG_CODEC == SWCODEC
             &keyclick_menu,
 #endif
+#ifdef IPOD_ARCH
+            &wheel_speed,
+#endif
 #ifdef HAVE_TOUCHPAD_SENSITIVITY_SETTING
             &touchpad_sensitivity,
 #endif
Index: apps/settings_list.c
===================================================================
--- apps/settings_list.c	(revision 27373)
+++ apps/settings_list.c	(working copy)
@@ -1746,10 +1746,33 @@
 #endif
 #endif
 
-#ifdef HAVE_MORSE_INPUT
-    OFFON_SETTING(0, morse_input, LANG_MORSE_INPUT, false, "morse input", NULL),
+#if defined (HAVE_SCROLLWHEEL) && defined (HAVE_MORSE_INPUT)
+CHOICE_SETTING(0, keyboard_mode, LANG_KBD_MODE, 0, "keyboard mode", 
+  "Standard,Sideways,Keep_Rollin,Scroll_1D,Row_Column,Morse", NULL, 6, 
+   ID2P(LANG_KBD_MODE_STANDARD), ID2P(LANG_KBD_MODE_SIDEWAYS),
+   ID2P(LANG_KBD_MODE_KEEP_ROLLIN), ID2P(LANG_KBD_MODE_SCROLL_1D), 
+   ID2P(LANG_KBD_MODE_ROW_COL), ID2P(LANG_KBD_MODE_MORSE) ),
+#else
+#ifdef HAVE_MORSE_INPUT /* No scrollwheel */
+     CHOICE_SETTING(0, keyboard_mode, LANG_KBD_MODE, 0, "keyboard mode", 
+         "Standard,Morse", NULL, 2, 
+         ID2P(LANG_KBD_MODE_STANDARD), ID2P(LANG_KBD_MODE_MORSE)),
 #endif
+#ifdef HAVE_SCROLLWHEEL /* No Morse */
+   CHOICE_SETTING(0, keyboard_mode, LANG_KBD_MODE, 0, "keyboard mode", 
+       "Standard,Sideways,Keep_Rollin,Scroll_1D,Row_Column", NULL, 5, 
+   ID2P(LANG_KBD_MODE_STANDARD), ID2P(LANG_KBD_MODE_SIDEWAYS),
+   ID2P(LANG_KBD_MODE_KEEP_ROLLIN), ID2P(LANG_KBD_MODE_SCROLL_1D), 
+   ID2P(LANG_KBD_MODE_ROW_COL)),
+#endif
+#endif
 
+#ifdef IPOD_ARCH
+    INT_SETTING(0, wheel_speed, LANG_WHEEL_SPEED, 8,
+            "wheel_speed", UNIT_INT, 3, 20, 1, NULL, NULL,
+            NULL),
+#endif
+
 #ifdef HAVE_HOTKEY
     TABLE_SETTING(F_ALLOW_ARBITRARY_VALS, hotkey_wps,
         LANG_HOTKEY_WPS, HOTKEY_VIEW_PLAYLIST, "hotkey wps",
Index: apps/keymaps/keymap-e200.c
===================================================================
--- apps/keymaps/keymap-e200.c	(revision 27373)
+++ apps/keymaps/keymap-e200.c	(working copy)
@@ -256,10 +256,10 @@
     { ACTION_KBD_CURSOR_RIGHT, BUTTON_REC|BUTTON_RIGHT,               BUTTON_NONE },
     { ACTION_KBD_CURSOR_RIGHT, BUTTON_REC|BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
     
-    { ACTION_KBD_UP,           BUTTON_SCROLL_BACK,                 BUTTON_NONE },
-    { ACTION_KBD_UP,           BUTTON_SCROLL_BACK|BUTTON_REPEAT,   BUTTON_NONE },
-    { ACTION_KBD_DOWN,         BUTTON_SCROLL_FWD,               BUTTON_NONE },
-    { ACTION_KBD_DOWN,         BUTTON_SCROLL_FWD|BUTTON_REPEAT, BUTTON_NONE },
+    { ACTION_KBD_SCROLL_BACK,  BUTTON_SCROLL_BACK,                 BUTTON_NONE },
+    { ACTION_KBD_SCROLL_BACK,  BUTTON_SCROLL_BACK|BUTTON_REPEAT,   BUTTON_NONE },
+    { ACTION_KBD_SCROLL_FWD,   BUTTON_SCROLL_FWD,               BUTTON_NONE },
+    { ACTION_KBD_SCROLL_FWD,   BUTTON_SCROLL_FWD|BUTTON_REPEAT, BUTTON_NONE },
     { ACTION_KBD_PAGE_FLIP,    BUTTON_REC|BUTTON_SELECT,         BUTTON_REC },
     { ACTION_KBD_BACKSPACE,    BUTTON_DOWN,                      BUTTON_NONE },
     { ACTION_KBD_BACKSPACE,    BUTTON_DOWN|BUTTON_REPEAT,        BUTTON_NONE },
Index: apps/keymaps/keymap-ipod.c
===================================================================
--- apps/keymaps/keymap-ipod.c	(revision 27373)
+++ apps/keymaps/keymap-ipod.c	(working copy)
@@ -90,7 +90,7 @@
     { ACTION_WPS_VOLDOWN,   BUTTON_SCROLL_BACK|BUTTON_REPEAT,   BUTTON_NONE },
     { ACTION_WPS_VOLUP,     BUTTON_SCROLL_FWD,                  BUTTON_NONE },
     { ACTION_WPS_VOLUP,     BUTTON_SCROLL_FWD|BUTTON_REPEAT,    BUTTON_NONE },
-    { ACTION_WPS_BROWSE,    BUTTON_SELECT|BUTTON_REL,           BUTTON_SELECT },
+    { ACTION_WPS_HOTKEY,    BUTTON_SELECT|BUTTON_REL,           BUTTON_SELECT },
     { ACTION_WPS_CONTEXT,   BUTTON_SELECT|BUTTON_REPEAT,        BUTTON_SELECT },
     { ACTION_WPS_HOTKEY,        BUTTON_SELECT|BUTTON_PLAY,      BUTTON_NONE },
     { ACTION_WPS_MENU,          BUTTON_MENU|BUTTON_REL,         BUTTON_MENU },
@@ -162,12 +162,13 @@
     { ACTION_KBD_RIGHT,        BUTTON_RIGHT,                          BUTTON_NONE },
     { ACTION_KBD_RIGHT,        BUTTON_RIGHT|BUTTON_REPEAT,            BUTTON_NONE },
     { ACTION_KBD_SELECT,       BUTTON_SELECT,                         BUTTON_NONE },
-    { ACTION_KBD_DONE,         BUTTON_PLAY,                           BUTTON_NONE },
-    { ACTION_KBD_ABORT,        BUTTON_MENU|BUTTON_REL,                BUTTON_MENU },
-    { ACTION_KBD_UP,           BUTTON_SCROLL_BACK,                    BUTTON_NONE },
-    { ACTION_KBD_UP,           BUTTON_SCROLL_BACK|BUTTON_REPEAT,      BUTTON_NONE },
-    { ACTION_KBD_DOWN,         BUTTON_SCROLL_FWD,                     BUTTON_NONE },
-    { ACTION_KBD_DOWN,         BUTTON_SCROLL_FWD|BUTTON_REPEAT,       BUTTON_NONE },
+    { ACTION_KBD_DONE,         BUTTON_PLAY|BUTTON_REL,                BUTTON_NONE },
+    { ACTION_KBD_ABORT,        BUTTON_PLAY|BUTTON_REPEAT,             BUTTON_NONE },
+    { ACTION_KBD_SCROLL_BACK,  BUTTON_SCROLL_BACK,                    BUTTON_NONE },
+    { ACTION_KBD_SCROLL_BACK,  BUTTON_SCROLL_BACK|BUTTON_REPEAT,      BUTTON_NONE },
+    { ACTION_KBD_SCROLL_FWD,   BUTTON_SCROLL_FWD,                     BUTTON_NONE },
+    { ACTION_KBD_SCROLL_FWD,   BUTTON_SCROLL_FWD|BUTTON_REPEAT,       BUTTON_NONE },
+    { ACTION_KBD_BACKSPACE,    BUTTON_MENU|BUTTON_REL,                BUTTON_MENU },
     { ACTION_KBD_MORSE_INPUT,  BUTTON_MENU|BUTTON_REPEAT,             BUTTON_MENU },
     { ACTION_KBD_MORSE_SELECT, BUTTON_SELECT|BUTTON_REL,              BUTTON_NONE },
     LAST_ITEM_IN_LIST
Index: apps/keymaps/keymap-fuze.c
===================================================================
--- apps/keymaps/keymap-fuze.c	(revision 27373)
+++ apps/keymaps/keymap-fuze.c	(working copy)
@@ -258,13 +258,13 @@
     { ACTION_KBD_CURSOR_RIGHT, BUTTON_HOME|BUTTON_RIGHT,               BUTTON_NONE },
     { ACTION_KBD_CURSOR_RIGHT, BUTTON_HOME|BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE },
     
-    { ACTION_KBD_UP,           BUTTON_SCROLL_BACK,               BUTTON_NONE },
-    { ACTION_KBD_UP,           BUTTON_SCROLL_BACK|BUTTON_REPEAT, BUTTON_NONE },
-    { ACTION_KBD_DOWN,         BUTTON_SCROLL_FWD,                BUTTON_NONE },
-    { ACTION_KBD_DOWN,         BUTTON_SCROLL_FWD|BUTTON_REPEAT,  BUTTON_NONE },
+    { ACTION_KBD_SCROLL_BACK,  BUTTON_SCROLL_BACK,               BUTTON_NONE },
+    { ACTION_KBD_SCROLL_BACK,  BUTTON_SCROLL_BACK|BUTTON_REPEAT, BUTTON_NONE },
+    { ACTION_KBD_SCROLL_FWD,   BUTTON_SCROLL_FWD,                BUTTON_NONE },
+    { ACTION_KBD_SCROLL_FWD,   BUTTON_SCROLL_FWD|BUTTON_REPEAT,  BUTTON_NONE },
     { ACTION_KBD_PAGE_FLIP,    BUTTON_HOME|BUTTON_SELECT,        BUTTON_HOME },
-    { ACTION_KBD_BACKSPACE,    BUTTON_DOWN,                      BUTTON_NONE },
-    { ACTION_KBD_BACKSPACE,    BUTTON_DOWN|BUTTON_REPEAT,        BUTTON_NONE },
+    { ACTION_KBD_BACKSPACE,    BUTTON_DOWN|BUTTON_REL,           BUTTON_DOWN },
+    { ACTION_KBD_MORSE_INPUT,  BUTTON_DOWN|BUTTON_REPEAT,        BUTTON_DOWN },
     { ACTION_KBD_SELECT,       BUTTON_SELECT,                    BUTTON_NONE },
     { ACTION_KBD_DONE,         BUTTON_UP,                        BUTTON_NONE },
     { ACTION_KBD_ABORT,        BUTTON_HOME|BUTTON_REPEAT,        BUTTON_NONE },
Index: firmware/target/arm/ipod/button-clickwheel.c
===================================================================
--- firmware/target/arm/ipod/button-clickwheel.c	(revision 27373)
+++ firmware/target/arm/ipod/button-clickwheel.c	(working copy)
@@ -35,6 +35,9 @@
 #include "system.h"
 #include "button.h"
 #include "kernel.h"
+#ifndef BOOTLOADER
+#include "settings.h"
+#endif
 #include "backlight.h"
 #include "serial.h"
 #include "power.h"
@@ -57,15 +60,7 @@
 
 #define WHEELCLICKS_PER_ROTATION     96 /* wheelclicks per full rotation */
 
-/* This amount of clicks is needed for at least scrolling 1 item. Choose small values 
- * to have high sensitivity but few precision, choose large values to have less 
- * sensitivity and good precision. */
-#if defined(IPOD_NANO) || defined(IPOD_NANO2G)
-#define WHEEL_SENSITIVITY 6 /* iPod nano has smaller wheel, lower sensitivity needed */
-#else
-#define WHEEL_SENSITIVITY 4 /* default sensitivity */
-#endif
-
+int  wheel_sensitivity;
 int  old_wheel_value  = -1;
 int  new_wheel_value  = 0;
 int  repeat           = 0;
@@ -97,6 +92,7 @@
 }
 #endif
 
+
 static inline int ipod_4g_button_read(void)
 {
     int whl = -1;
@@ -153,6 +149,11 @@
                     /* This is for later = BUTTON_SCROLL_TOUCH;*/
                     wheel_delta = new_wheel_value - old_wheel_value;
                     unsigned int wheel_keycode = BUTTON_NONE;
+#ifdef BOOTLOADER
+                    wheel_sensitivity = 6;
+#else
+                    wheel_sensitivity = global_settings.wheel_speed;
+#endif
 
                     /* Taking into account wrapping during transition from highest 
                      * to lowest wheel position and back */
@@ -163,9 +164,9 @@
     
                     /* Getting direction and wheel_keycode from wheel_delta.
                      * Need at least some clicks to be sure to avoid haptic fuzziness */
-                    if      (wheel_delta >=  WHEEL_SENSITIVITY)
+                    if      (wheel_delta >=  wheel_sensitivity)
                         wheel_keycode = BUTTON_SCROLL_FWD;
-                    else if (wheel_delta <= -WHEEL_SENSITIVITY)
+                    else if (wheel_delta <= -wheel_sensitivity)
                         wheel_keycode = BUTTON_SCROLL_BACK;
                     else 
                         wheel_keycode = BUTTON_NONE;
@@ -217,8 +218,8 @@
                         /* The queue should have no other events when scrolling */
                         if (queue_empty(&button_queue))
                         {
-                            /* each WHEEL_SENSITIVITY clicks = scrolling 1 item */
-                            accumulated_wheel_delta /= WHEEL_SENSITIVITY;
+                            /* each wheel_sensitivity clicks = scrolling 1 item */
+                            accumulated_wheel_delta /= wheel_sensitivity;
 #ifdef HAVE_SCROLLWHEEL
                             /* use data-format for HAVE_SCROLLWHEEL */
                             /* always use acceleration mode (1<<31) */
