--------
-- Japanese 12-key flick keyboard layout, modelled after Android's flick
-- keyboard. Rather than being modal, it has the ability to apply modifiers to
-- the previous character. In addition, users can tap a kana key to cycle
-- through the various kana in that kana row (and associated small kana).
--
-- Note that because we cannot have tri-state buttons (and we want to be able
-- to input katakana) we emulate a quad-state button using the symbol and shift
-- layers. Users just have to tap whatever mode they want and they should be
-- able to get there easily.
--------
local logger = require ( " logger " )
local util = require ( " util " )
local time = require ( " ui/time " )
local _ = require ( " gettext " )
local C_ = _.pgettext
local N_ = _.ngettext
local T = require ( " ffi/util " ) . template
local K = require ( " frontend/ui/data/keyboardlayouts/ja_keyboard_keys " )
local DEFAULT_KEITAI_TAP_INTERVAL_S = 2
-- "Keitai input" is an input mode similar to T9 mobile input, where you tap a
-- key to cycle through several candidate characters. The tap interval is how
-- long we are going to wait before committing to the current character. See
-- <https://en.wikipedia.org/wiki/Japanese_input_method#Mobile_phones> for more
-- information.
local function getKeitaiTapInterval ( )
return time.s ( G_reader_settings : readSetting ( " keyboard_japanese_keitai_tap_interval " , DEFAULT_KEITAI_TAP_INTERVAL_S ) )
end
local function setKeitaiTapInterval ( interval )
G_reader_settings : saveSetting ( " keyboard_japanese_keitai_tap_interval " , time.to_s ( interval ) )
end
local function exitKeitaiMode ( inputbox )
logger.dbg ( " ja_kbd: clearing keitai window last tap tv " )
inputbox._ja_last_tap_time = nil
end
local function wrappedAddChars ( inputbox , char )
-- Find the relevant modifier cycle tables.
local modifier_table = K.MODIFIER_TABLE [ char ]
local keitai_cycle = K.KEITAI_TABLE [ char ]
-- For keitai buttons, are we still in the tap interval?
local within_tap_window
if keitai_cycle then
if inputbox._ja_last_tap_time then
within_tap_window = time.since ( inputbox._ja_last_tap_time ) < getKeitaiTapInterval ( )
end
inputbox._ja_last_tap_time = time.now ( )
else
-- This is a non-keitai or non-tap key, so break out of keitai window.
exitKeitaiMode ( inputbox )
end
-- Get the character behind the cursor and figure out how to modify it.
local new_char
local current_char = inputbox : getChar ( - 1 )
if modifier_table then
new_char = modifier_table [ current_char ]
elseif keitai_cycle and keitai_cycle [ current_char ] and within_tap_window then
new_char = keitai_cycle [ current_char ]
else
-- Regular key, just add it as normal.
inputbox.addChars : raw_method_call ( char )
return
end
-- Replace character if there was a valid replacement.
logger.dbg ( " ja_kbd: applying " , char , " key to " , current_char , " yielded " , new_char )
if not current_char then return end -- no character to modify
if new_char then
-- Use the raw methods to avoid calling the callbacks.
inputbox.delChar : raw_method_call ( )
inputbox.addChars : raw_method_call ( new_char )
end
end
local function wrapInputBox ( inputbox )
if inputbox._ja_wrapped == nil then
inputbox._ja_wrapped = true
local wrappers = { }
-- Wrap all of the navigation and non-single-character-input keys with
-- a callback to clear the tap window, but pass through to the
-- original function.
-- Delete text.
table.insert ( wrappers , util.wrapMethod ( inputbox , " delChar " , nil , exitKeitaiMode ) )
table.insert ( wrappers , util.wrapMethod ( inputbox , " delToStartOfLine " , nil , exitKeitaiMode ) )
table.insert ( wrappers , util.wrapMethod ( inputbox , " clear " , nil , exitKeitaiMode ) )
-- Navigation.
table.insert ( wrappers , util.wrapMethod ( inputbox , " leftChar " , nil , exitKeitaiMode ) )
table.insert ( wrappers , util.wrapMethod ( inputbox , " rightChar " , nil , exitKeitaiMode ) )
table.insert ( wrappers , util.wrapMethod ( inputbox , " upLine " , nil , exitKeitaiMode ) )
table.insert ( wrappers , util.wrapMethod ( inputbox , " downLine " , nil , exitKeitaiMode ) )
-- Move to other input box.
table.insert ( wrappers , util.wrapMethod ( inputbox , " unfocus " , nil , exitKeitaiMode ) )
-- Gestures to move cursor.
table.insert ( wrappers , util.wrapMethod ( inputbox , " onTapTextBox " , nil , exitKeitaiMode ) )
table.insert ( wrappers , util.wrapMethod ( inputbox , " onHoldTextBox " , nil , exitKeitaiMode ) )
table.insert ( wrappers , util.wrapMethod ( inputbox , " onSwipeTextBox " , nil , exitKeitaiMode ) )
-- addChars is the only method we need a more complicated wrapper for.
table.insert ( wrappers , util.wrapMethod ( inputbox , " addChars " , wrappedAddChars , nil ) )
return function ( )
if inputbox._ja_wrapped then
for _ , wrapper in ipairs ( wrappers ) do
wrapper : revert ( )
end
inputbox._ja_last_tap_time = nil
inputbox._ja_wrapped = nil
end
end
end
end
local function genMenuItems ( self )
return {
{
text_func = function ( )
local interval = getKeitaiTapInterval ( )
if interval ~= 0 then
-- @translators Keitai input is a kind of Japanese keyboard input mode (similar to T9 keypad input). See <https://en.wikipedia.org/wiki/Japanese_input_method#Mobile_phones> for more information.
return T ( N_ ( " Keitai tap interval: %1 second " , " Keitai tap interval: %1 seconds " , time.to_s ( interval ) ) , time.to_s ( interval ) )
else
-- @translators Flick and keitai are kinds of Japanese keyboard input modes. See <https://en.wikipedia.org/wiki/Japanese_input_method#Mobile_phones> for more information.
return _ ( " Keitai input: disabled (flick-only input) " )
end
end ,
help_text = _ ( " How long to wait for the next tap when in keitai input mode before committing to the current character. During this window, tapping a single key will loop through candidates for the current character being input. Any other input will cause you to leave keitai mode. " ) ,
keep_menu_open = true ,
callback = function ( touchmenu_instance )
local SpinWidget = require ( " ui/widget/spinwidget " )
local UIManager = require ( " ui/uimanager " )
local Screen = require ( " device " ) . screen
local items = SpinWidget : new {
title_text = _ ( " Keitai tap interval " ) ,
info_text = _ ( [ [
How long to wait ( in seconds ) for the next tap when in keitai input mode before committing to the current character . During this window , tapping a single key will loop through candidates for the current character being input . Any other input will cause you to leave keitai mode .
If set to 0 , keitai input is disabled entirely and only flick input can be used . ] ] ) ,
width = math.floor ( Screen : getWidth ( ) * 0.75 ) ,
value = time.to_s ( getKeitaiTapInterval ( ) ) ,
value_min = 0 ,
value_max = 10 ,
value_step = 1 ,
unit = C_ ( " Time " , " s " ) ,
ok_text = _ ( " Set interval " ) ,
default_value = DEFAULT_KEITAI_TAP_INTERVAL_S ,
callback = function ( spin )
setKeitaiTapInterval ( time.s ( spin.value ) )
if touchmenu_instance then touchmenu_instance : updateItems ( ) end
end ,
}
UIManager : show ( items )
end ,
} ,
}
end
-- Basic modifier keys.
local M_l = { label = " ← " , } -- Arrow left
local M_r = { label = " → " , } -- Arrow right
local Msw = { label = " 🌐 " , } -- Switch keyboard
local Mbk = { label = " " , bold = false , } -- Backspace
-- Modifier key for kana input.
local Mmd = { label = " ◌゙ ◌゚ " , alt_label = " 大⇔小 " ,
K.MODIFIER_KEY_CYCLIC ,
west = K.MODIFIER_KEY_DAKUTEN ,
north = K.MODIFIER_KEY_SMALLKANA ,
east = K.MODIFIER_KEY_HANDAKUTEN , }
-- Modifier key for latin input.
local Msh = { label = " a⇔A " ,
K.MODIFIER_KEY_SHIFT }
-- In order to emulate the tri-modal system of 12-key keyboards we treat shift
-- and symbol modes as being used to specify which of the three target layers
-- to use. The four modes are hiragana (default), katakana (shift), English
-- letters (symbol), numbers and symbols (shift+symbol).
--
-- In order to make it easy for users to know which button will take them to a
-- specific mode, we need to give different keys the same name at certain
-- times, so we append a \0 to one set so that the VirtualKeyboard can
-- differentiate them on key tap even though they look the same to the user.
-- Shift-mode toggle button.
local Sh_abc = { label = " ABC \0 " , alt_label = " ひらがな " , bold = true , }
local Sh_sym = { label = " 記号 \0 " , bold = true , } -- Switch to numbers and symbols.
local Sh_hir = { label = " ひらがな \0 " , bold = true , } -- Switch to hiragana.
local Sh_kat = { label = " カタカナ \0 " , bold = true , } -- Switch to katakana.
-- Symbol-mode toggle button.
local Sy_abc = { label = " ABC " , alt_label = " 記号 " , bold = true , }
local Sy_sym = { label = " 記号 " , bold = true , } -- Switch to numbers and symbols.
local Sy_hir = { label = " ひらがな " , bold = true , } -- Switch to hiragana.
local Sy_kat = { label = " カタカナ " , bold = true , } -- Switch to katakana.
return {
min_layer = 1 ,
max_layer = 4 ,
shiftmode_keys = { [ " ABC \0 " ] = true , [ " 記号 \0 " ] = true , [ " カタカナ \0 " ] = true , [ " ひらがな \0 " ] = true } ,
symbolmode_keys = { [ " ABC " ] = true , [ " 記号 " ] = true , [ " ひらがな " ] = true , [ " カタカナ " ] = true } ,
utf8mode_keys = { [ " 🌐 " ] = true } ,
keys = {
-- first row [🌐, あ, か, さ, <bksp>]
{ -- R r S s
Msw ,
{ K.k_a , K.h_a , K.s_1 , K.l_1 , } ,
{ K.kKa , K.hKa , K.s_2 , K.l_2 , } ,
{ K.kSa , K.hSa , K.s_3 , K.l_3 , } ,
Mbk ,
} ,
-- second row [←, た, な, は, →]
{ -- R r S s
M_l ,
{ K.kTa , K.hTa , K.s_4 , K.l_4 , } ,
{ K.kNa , K.hNa , K.s_5 , K.l_5 , } ,
{ K.kHa , K.hHa , K.s_6 , K.l_6 , } ,
M_r ,
} ,
-- third row [<shift>, ま, や, ら, < >]
{ -- R r S s
{ Sh_hir , Sh_kat , Sh_abc , Sh_sym , } , -- Shift
{ K.kMa , K.hMa , K.s_7 , K.l_7 , } ,
{ K.kYa , K.hYa , K.s_8 , K.l_8 , } ,
{ K.kRa , K.hRa , K.s_9 , K.l_9 , } ,
{ label = " ␣ " ,
" " , " " , " " , " " , } -- whitespace
} ,
-- fourth row [symbol, modifier, わ, 。, enter]
{ -- R r S s
{ Sy_sym , Sy_abc , Sy_kat , Sy_hir , } , -- Symbols
{ Mmd , Mmd , K.s_b , Msh , } ,
{ K.kWa , K.hWa , K.s_0 , K.l_0 , } ,
{ K.k_P , K.h_P , K.s_p , K.l_P , } ,
{ label = " ⮠ " , bold = true ,
" \n " , " \n " , " \n " , " \n " , } , -- newline
} ,
} ,
-- Methods.
wrapInputBox = wrapInputBox ,
genMenuItems = genMenuItems ,
}