mirror of
https://github.com/koreader/koreader
synced 2024-11-16 06:12:56 +00:00
da65ac8b02
* Iterate over varargs directly via select if possible * Use table.pack otherwise (https://github.com/koreader/koreader-base/pull/1535). * This allows us to simplify a few Logger calls, as logger now handles nil values.
252 lines
11 KiB
Lua
252 lines
11 KiB
Lua
--------
|
||
-- 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,
|
||
}
|