2
0
mirror of https://github.com/koreader/koreader synced 2024-11-16 06:12:56 +00:00
koreader/frontend/ui/widget/virtualkeyboard.lua

1182 lines
41 KiB
Lua
Raw Normal View History

2017-04-29 08:38:09 +00:00
local Blitbuffer = require("ffi/blitbuffer")
2013-10-18 20:38:07 +00:00
local BottomContainer = require("ui/widget/container/bottomcontainer")
2017-04-29 08:38:09 +00:00
local CenterContainer = require("ui/widget/container/centercontainer")
local Device = require("device")
local FocusManager = require("ui/widget/focusmanager")
2013-10-18 20:38:07 +00:00
local Font = require("ui/font")
2017-04-29 08:38:09 +00:00
local FrameContainer = require("ui/widget/container/framecontainer")
2013-10-18 20:38:07 +00:00
local Geom = require("ui/geometry")
local GestureRange = require("ui/gesturerange")
2017-04-29 08:38:09 +00:00
local HorizontalGroup = require("ui/widget/horizontalgroup")
local HorizontalSpan = require("ui/widget/horizontalspan")
local ImageWidget = require("ui/widget/imagewidget")
local InputContainer = require("ui/widget/container/inputcontainer")
local KeyboardLayoutDialog = require("ui/widget/keyboardlayoutdialog")
2017-09-13 14:56:20 +00:00
local Size = require("ui/size")
2017-04-29 08:38:09 +00:00
local TextWidget = require("ui/widget/textwidget")
2013-10-18 20:38:07 +00:00
local UIManager = require("ui/uimanager")
2017-04-29 08:38:09 +00:00
local VerticalGroup = require("ui/widget/verticalgroup")
local VerticalSpan = require("ui/widget/verticalspan")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local logger = require("logger")
local time = require("ui/time")
local util = require("util")
2017-04-29 08:38:09 +00:00
local Screen = Device.screen
2013-07-30 15:07:33 +00:00
local keyboard_state = {
force_current_layout = false, -- Set to true to get/set current layout (instead of default layout)
}
local DEFAULT_LABEL_SIZE = 22
local VirtualKeyPopup
Clarify our OOP semantics across the codebase (#9586) Basically: * Use `extend` for class definitions * Use `new` for object instantiations That includes some minor code cleanups along the way: * Updated `Widget`'s docs to make the semantics clearer. * Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283) * Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass). * Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events. * Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier. * Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references. * ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak). * Terminal: Make sure the shell is killed on plugin teardown. * InputText: Fix Home/End/Del physical keys to behave sensibly. * InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...). * OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of. * ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed! * Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
local VirtualKey = InputContainer:extend{
2014-03-13 13:52:43 +00:00
key = nil,
icon = nil,
label = nil,
bold = nil,
2014-03-13 13:52:43 +00:00
keyboard = nil,
callback = nil,
-- This is to inhibit the key's own refresh (useful to avoid conflicts on Layer changing keys)
skiptap = nil,
skiphold = nil,
2014-03-13 13:52:43 +00:00
width = nil,
height = math.max(Screen:getWidth(), Screen:getHeight())*0.33,
bordersize = 0,
focused_bordersize = Size.border.default,
radius = 0,
2017-04-29 08:38:09 +00:00
face = Font:getFace("infont"),
2013-07-30 15:07:33 +00:00
}
-- For caps lock, it's necessary because after setLayout, the new shift key is no longer the same virtual key
-- thus rendering its preset .ignore_key_release property useless
local ignore_key_release
2013-07-30 15:07:33 +00:00
function VirtualKey:init()
local label_font_size = G_reader_settings:readSetting("keyboard_key_font_size", DEFAULT_LABEL_SIZE)
self.face = Font:getFace("infont", label_font_size)
self.bold = G_reader_settings:isTrue("keyboard_key_bold")
if self.keyboard.symbolmode_keys[self.label] ~= nil then
self.callback = function () self.keyboard:setLayer("Sym") end
self.skiptap = true
elseif self.keyboard.shiftmode_keys[self.label] ~= nil or self.keyboard.shiftmode_keys[self.key] ~= nil then
-- self.key needed because the shift key's label could be the capslock instead of the shift
local key = self.key or self.label
local releasable = key == ""
self.callback = function()
self.keyboard.release_shift = releasable
self.keyboard:setLayer("Shift")
end
self.hold_callback = function()
ignore_key_release = true
if releasable then self.keyboard.release_shift = false end
self.keyboard:setLayer("Shift")
end
self.skiptap = true
elseif self.keyboard.utf8mode_keys[self.label] ~= nil then
self.key_chars = self:genKeyboardLayoutKeyChars()
self.callback = function ()
self.keyboard:onSwitchingKeyboardLayout()
local current = G_reader_settings:readSetting("keyboard_layout")
local default = G_reader_settings:readSetting("keyboard_layout_default")
local keyboard_layouts = G_reader_settings:readSetting("keyboard_layouts", {})
local next_layout = nil
local layout_index = util.arrayContains(keyboard_layouts, current)
if layout_index then
if layout_index == #keyboard_layouts then
layout_index = 1
else
layout_index = layout_index + 1
end
else
if default and current ~= default then
next_layout = default
else
layout_index = 1
end
end
next_layout = next_layout or keyboard_layouts[layout_index] or "en"
self.keyboard:setKeyboardLayout(next_layout)
end
self.hold_callback = function()
self.keyboard:onSwitchingKeyboardLayout()
if util.tableSize(self.key_chars) > 5 then -- 2 or more layouts enabled
self.popup = VirtualKeyPopup:new{
parent_key = self,
}
else
self.keyboard_layout_dialog = KeyboardLayoutDialog:new{
parent = self,
keyboard_state = keyboard_state,
}
UIManager:show(self.keyboard_layout_dialog)
end
end
self.hold_cb_is_popup = true
self.swipe_callback = function(ges)
self.keyboard:onSwitchingKeyboardLayout()
local key_function = self.key_chars[ges.direction.."_func"]
if key_function then
key_function()
end
end
self.skiptap = true
elseif self.keyboard.umlautmode_keys[self.label] ~= nil then
self.callback = function () self.keyboard:setLayer("Äéß") end
self.skiptap = true
elseif self.label == "" then
2014-03-13 13:52:43 +00:00
self.callback = function () self.keyboard:delChar() end
self.hold_callback = function ()
self.ignore_key_release = true -- don't have delChar called on release
self.keyboard:delToStartOfLine()
end
--self.skiphold = true
elseif self.label == "" then
self.callback = function() self.keyboard:leftChar() end
self.hold_callback = function()
self.ignore_key_release = true
self.keyboard:goToStartOfLine()
end
elseif self.label == "" then
self.callback = function() self.keyboard:rightChar() end
self.hold_callback = function()
self.ignore_key_release = true
self.keyboard:goToEndOfLine()
end
elseif self.label == "" then
self.callback = function() self.keyboard:upLine() end
self.hold_callback = function()
self.ignore_key_release = true
self.keyboard:scrollUp()
end
elseif self.label == "" then
self.callback = function() self.keyboard:downLine() end
self.hold_callback = function()
self.ignore_key_release = true
self.keyboard:scrollDown()
end
2014-03-13 13:52:43 +00:00
else
self.callback = function()
self.keyboard:addChar(self.key)
if self.close_after_callback_widget then
UIManager:close(self.close_after_callback_widget)
end
if self.keyboard.shiftmode and not self.keyboard.symbolmode and self.keyboard.release_shift then
self.keyboard:setLayer("Shift")
end
end
self.hold_callback = function()
if not self.key_chars then return end
VirtualKeyPopup:new{
parent_key = self,
}
end
self.hold_cb_is_popup = true
self.swipe_callback = function(ges)
local key_string = self.key_chars[ges.direction] or self.key
local key_function = self.key_chars[ges.direction.."_func"]
if not key_function and key_string then
if type(key_string) == "table" and key_string.key then
key_string = key_string.key
end
self.keyboard:addChar(key_string)
elseif key_function then
key_function()
end
end
2014-03-13 13:52:43 +00:00
end
2015-04-27 00:49:27 +00:00
local label_widget
2014-03-13 13:52:43 +00:00
if self.icon then
-- Scale icon to fit other characters height
-- (use *1.5 as our icons have a bit of white padding)
local icon_height = math.ceil(self.face.size * 1.5)
2014-03-13 13:52:43 +00:00
label_widget = ImageWidget:new{
file = self.icon,
scale_factor = 0, -- keep icon aspect ratio
height = icon_height,
width = self.width - 2*self.bordersize,
2014-03-13 13:52:43 +00:00
}
else
label_widget = TextWidget:new{
text = self.label,
face = self.face,
bold = self.bold or false,
2014-03-13 13:52:43 +00:00
}
-- Make long labels fit by decreasing font size
local max_width = self.width - 2*self.bordersize - 2*Size.padding.small
while label_widget:getWidth() > max_width do
local new_size = label_widget.face.orig_size - 1
label_widget:free()
if new_size < 8 then break end -- don't go too small
label_widget = TextWidget:new{
text = self.label,
face = Font:getFace(self.face.orig_font, new_size),
bold = self.bold or false,
}
end
end
if self.alt_label then
local OverlapGroup = require("ui/widget/overlapgroup")
local alt_label_widget = TextWidget:new{
text = self.alt_label,
face = Font:getFace(self.face.orig_font, label_font_size - 4),
bold = self.bold or false,
fgcolor = Blitbuffer.COLOR_DARK_GRAY,
padding = 0, -- no additional padding to font line height
}
local key_inner_dimen = Geom:new{
w = self.width - 2*self.bordersize - 2*Size.padding.default,
h = self.height - 2*self.bordersize - 2*Size.padding.small, -- already some padding via line height
}
label_widget = OverlapGroup:new{
CenterContainer:new{
dimen = key_inner_dimen,
label_widget,
},
WidgetContainer:new{
overlap_align = "right",
dimen = Geom:new{
w = alt_label_widget:getSize().w,
h = key_inner_dimen.h,
},
alt_label_widget,
},
}
2014-03-13 13:52:43 +00:00
end
self[1] = FrameContainer:new{
margin = 0,
bordersize = self.bordersize,
background = Blitbuffer.COLOR_WHITE,
radius = 0,
2014-03-13 13:52:43 +00:00
padding = 0,
allow_mirroring = false,
2014-03-13 13:52:43 +00:00
CenterContainer:new{
dimen = Geom:new{
w = self.width - 2*self.bordersize,
h = self.height - 2*self.bordersize,
},
label_widget,
},
}
self.dimen = Geom:new{
w = self.width,
h = self.height,
}
--self.dimen = self[1]:getSize()
self.ges_events = {
TapSelect = {
GestureRange:new{
ges = "tap",
range = self.dimen,
2014-03-13 13:52:43 +00:00
},
},
HoldSelect = {
GestureRange:new{
ges = "hold",
range = self.dimen,
},
},
HoldReleaseKey = {
GestureRange:new{
ges = "hold_release",
range = self.dimen,
},
},
PanReleaseKey = {
GestureRange:new{
ges = "pan_release",
range = self.dimen,
},
},
SwipeKey = {
GestureRange:new{
ges = "swipe",
range = self.dimen,
},
},
}
if ((self.keyboard.shiftmode_keys[self.label] ~= nil or self.keyboard.shiftmode_keys[self.key]) and self.keyboard.shiftmode) or
(self.keyboard.umlautmode_keys[self.label] ~= nil and self.keyboard.umlautmode) or
(self.keyboard.symbolmode_keys[self.label] ~= nil and self.keyboard.symbolmode) then
self[1].background = Blitbuffer.COLOR_LIGHT_GRAY
end
self.flash_keyboard = G_reader_settings:nilOrTrue("flash_keyboard")
2013-07-30 15:07:33 +00:00
end
function VirtualKey:paintTo(...)
InputContainer.paintTo(self, ...)
-- Fudge self.dimen to include the padding, to make sure said padding is covered by our ges_events range...
-- Like Geom, floor coordinates & ceil dims, to fill the gaps without overlaps.
local coords_padding = math.floor(self.keyboard.key_padding / 2)
local dims_padding = self.keyboard.key_padding -- i.e., coords_padding + math.ceil(self.keyboard.key_padding / 2)
self.dimen.x = self.dimen.x - coords_padding
self.dimen.w = self[1].dimen.w + dims_padding
self.dimen.y = self.dimen.y - coords_padding
self.dimen.h = self[1].dimen.h + dims_padding
end
function VirtualKey:genKeyboardLayoutKeyChars()
local positions = {
"northeast",
"north",
"northwest",
"west",
}
local keyboard_layouts = G_reader_settings:readSetting("keyboard_layouts", {})
local key_chars = {
{ label = "🌐",
},
east = { label = "", },
east_func = function ()
UIManager:close(self.popup)
self.keyboard_layout_dialog = KeyboardLayoutDialog:new{
parent = self,
keyboard_state = keyboard_state,
}
UIManager:show(self.keyboard_layout_dialog)
end,
}
for i = 1, #keyboard_layouts do
key_chars[positions[i]] = string.sub(keyboard_layouts[i], 1, 2)
key_chars[positions[i] .. "_func"] = function()
UIManager:close(self.popup)
self.keyboard:setKeyboardLayout(keyboard_layouts[i])
end
end
return key_chars
end
-- NOTE: We currently don't ever set want_flash to true (c.f., our invert method).
Assorted bag'o tweaks & fixes (#9569) * UIManager: Support more specialized update modes for corner-cases: * A2, which we'll use for the VirtualKeyboards keys (they'd... inadvertently switched to UI with the highlight refactor). * NO_MERGE variants of ui & partial (for sunxi). Use `[ui]` in ReaderHighlight's popup, because of a Sage kernel bug that could otherwise make it translucent, sometimes completely so (*sigh*). * UIManager: Assorted code cleanups & simplifications. * Logger & dbg: Unify logging style, and code cleanups. * SDL: Unbreak suspend/resume outside of the emulator (fix #9567). * NetworkMgr: Cache the network status, and allow it to be queried. (Used by AutoSuspend to avoid repeatedly poking the system when computing the standby schedule delay). * OneTimeMigration: Don't forget about `NETWORK_PROXY` & `STARDICT_DATA_DIR` when migrating `defaults.persistent.lua` (fix #9573) * WakeupMgr: Workaround an apparent limitation of the RTC found on i.MX5 Kobo devices, where setting a wakealarm further than UINT16_MAX seconds in the future would apparently overflow and wraparound... (fix #8039, many thanks to @yfede for the extensive deep-dive and for actually accurately pinpointing the issue!). * Kobo: Handle standby transitions at full CPU clock speeds, in order to limit the latency hit. * UIManager: Properly quit on reboot & exit. This ensures our exit code is preserved, as we exit on our own terms (instead of being killed by the init system). This is important on platforms where exit codes are semantically meaningful (e.g., Kobo). * UIManager: Speaking of reboot & exit, make sure the Screensaver shows in all circumstances (e.g., autoshutdown, re: #9542)), and that there aren't any extraneous refreshes triggered. (Additionally, fix a minor regression since #9448 about tracking this very transient state on Kobo & Cervantes). * Kindle: ID the upcoming Scribe. * Bump base (https://github.com/koreader/koreader-base/pull/1524)
2022-10-02 01:01:49 +00:00
function VirtualKey:update_keyboard(want_flash, want_a2)
-- NOTE: We use "a2" for the highlights.
-- We flash the *full* keyboard when we release a hold.
if want_flash then
UIManager:setDirty(self.keyboard, function()
return "flashui", self.keyboard.dimen
end)
else
local refresh_type = "ui"
Assorted bag'o tweaks & fixes (#9569) * UIManager: Support more specialized update modes for corner-cases: * A2, which we'll use for the VirtualKeyboards keys (they'd... inadvertently switched to UI with the highlight refactor). * NO_MERGE variants of ui & partial (for sunxi). Use `[ui]` in ReaderHighlight's popup, because of a Sage kernel bug that could otherwise make it translucent, sometimes completely so (*sigh*). * UIManager: Assorted code cleanups & simplifications. * Logger & dbg: Unify logging style, and code cleanups. * SDL: Unbreak suspend/resume outside of the emulator (fix #9567). * NetworkMgr: Cache the network status, and allow it to be queried. (Used by AutoSuspend to avoid repeatedly poking the system when computing the standby schedule delay). * OneTimeMigration: Don't forget about `NETWORK_PROXY` & `STARDICT_DATA_DIR` when migrating `defaults.persistent.lua` (fix #9573) * WakeupMgr: Workaround an apparent limitation of the RTC found on i.MX5 Kobo devices, where setting a wakealarm further than UINT16_MAX seconds in the future would apparently overflow and wraparound... (fix #8039, many thanks to @yfede for the extensive deep-dive and for actually accurately pinpointing the issue!). * Kobo: Handle standby transitions at full CPU clock speeds, in order to limit the latency hit. * UIManager: Properly quit on reboot & exit. This ensures our exit code is preserved, as we exit on our own terms (instead of being killed by the init system). This is important on platforms where exit codes are semantically meaningful (e.g., Kobo). * UIManager: Speaking of reboot & exit, make sure the Screensaver shows in all circumstances (e.g., autoshutdown, re: #9542)), and that there aren't any extraneous refreshes triggered. (Additionally, fix a minor regression since #9448 about tracking this very transient state on Kobo & Cervantes). * Kindle: ID the upcoming Scribe. * Bump base (https://github.com/koreader/koreader-base/pull/1524)
2022-10-02 01:01:49 +00:00
if want_a2 then
refresh_type = "a2"
end
Enable HW dithering in a few key places (#4541) * Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4) * FileManager and co. (where appropriate, i.e., when covers are shown) * Book Status * Reader, where appropriate: * CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone). * Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices). * ScreenSaver * ImageViewer * Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it). (The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu). * Hunted down a few redundant repaints (unneeded setDirty("all") calls), either by switching the widget to nil when only a refresh was needed, and not a repaint, or by passing the appropritate widget to setDirty. (Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard). There were also a few instances of 'em right behind a widget close. * Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog. We unfortunately do need to do it when switching tabs, because of their variable heights. * On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes! * Fix another debug guard in Kobo sysfs_light * Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not. PS: (Almost :100: commits! :D)
2019-02-07 00:14:37 +00:00
-- Only repaint the key itself, not the full board...
-- NOTE: We use self[1] (i.e., FrameContainer),
-- because we fudge self.dimen to include the padding for the gesture hitbox...
Enable HW dithering in a few key places (#4541) * Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4) * FileManager and co. (where appropriate, i.e., when covers are shown) * Book Status * Reader, where appropriate: * CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone). * Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices). * ScreenSaver * ImageViewer * Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it). (The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu). * Hunted down a few redundant repaints (unneeded setDirty("all") calls), either by switching the widget to nil when only a refresh was needed, and not a repaint, or by passing the appropritate widget to setDirty. (Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard). There were also a few instances of 'em right behind a widget close. * Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog. We unfortunately do need to do it when switching tabs, because of their variable heights. * On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes! * Fix another debug guard in Kobo sysfs_light * Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not. PS: (Almost :100: commits! :D)
2019-02-07 00:14:37 +00:00
UIManager:widgetRepaint(self[1], self[1].dimen.x, self[1].dimen.y)
Assorted bag'o tweaks & fixes (#9569) * UIManager: Support more specialized update modes for corner-cases: * A2, which we'll use for the VirtualKeyboards keys (they'd... inadvertently switched to UI with the highlight refactor). * NO_MERGE variants of ui & partial (for sunxi). Use `[ui]` in ReaderHighlight's popup, because of a Sage kernel bug that could otherwise make it translucent, sometimes completely so (*sigh*). * UIManager: Assorted code cleanups & simplifications. * Logger & dbg: Unify logging style, and code cleanups. * SDL: Unbreak suspend/resume outside of the emulator (fix #9567). * NetworkMgr: Cache the network status, and allow it to be queried. (Used by AutoSuspend to avoid repeatedly poking the system when computing the standby schedule delay). * OneTimeMigration: Don't forget about `NETWORK_PROXY` & `STARDICT_DATA_DIR` when migrating `defaults.persistent.lua` (fix #9573) * WakeupMgr: Workaround an apparent limitation of the RTC found on i.MX5 Kobo devices, where setting a wakealarm further than UINT16_MAX seconds in the future would apparently overflow and wraparound... (fix #8039, many thanks to @yfede for the extensive deep-dive and for actually accurately pinpointing the issue!). * Kobo: Handle standby transitions at full CPU clock speeds, in order to limit the latency hit. * UIManager: Properly quit on reboot & exit. This ensures our exit code is preserved, as we exit on our own terms (instead of being killed by the init system). This is important on platforms where exit codes are semantically meaningful (e.g., Kobo). * UIManager: Speaking of reboot & exit, make sure the Screensaver shows in all circumstances (e.g., autoshutdown, re: #9542)), and that there aren't any extraneous refreshes triggered. (Additionally, fix a minor regression since #9448 about tracking this very transient state on Kobo & Cervantes). * Kindle: ID the upcoming Scribe. * Bump base (https://github.com/koreader/koreader-base/pull/1524)
2022-10-02 01:01:49 +00:00
logger.dbg("update key", self.key)
UIManager:setDirty(nil, refresh_type, self[1].dimen)
-- NOTE: On MTK, we'd have to forcibly stall a bit for the highlights to actually show.
--[[
UIManager:forceRePaint()
UIManager:yieldToEPDC(3000)
--]]
end
end
function VirtualKey:onFocus()
self[1].inner_bordersize = self.focused_bordersize
end
function VirtualKey:onUnfocus()
self[1].inner_bordersize = 0
end
function VirtualKey:onTapSelect(skip_flash)
Device:performHapticFeedback("KEYBOARD_TAP")
-- just in case it's not flipped to false on hold release where it's supposed to
self.keyboard.ignore_first_hold_release = false
if self.flash_keyboard and not skip_flash and not self.skiptap then
self:invert(true)
UIManager:forceRePaint()
UIManager:yieldToEPDC()
self:invert(false)
if self.callback then
self.callback()
end
UIManager:forceRePaint()
else
if self.callback then
self.callback()
end
2014-03-13 13:52:43 +00:00
end
return true
2013-07-30 15:07:33 +00:00
end
function VirtualKey:onHoldSelect()
Device:performHapticFeedback("LONG_PRESS")
-- No visual feedback necessary if we're going to show a popup on top of the key ;).
if self.flash_keyboard and not self.skiphold and not self.hold_cb_is_popup then
self:invert(true)
UIManager:forceRePaint()
UIManager:yieldToEPDC()
-- NOTE: We do *NOT* set hold to true here,
-- because some mxcfb drivers apparently like to merge the flash that it would request
-- with the following key redraw, leading to an unsightly double flash :/.
self:invert(false)
if self.hold_callback then
self.hold_callback()
end
UIManager:forceRePaint()
else
if self.hold_callback then
self.hold_callback()
end
end
return true
end
function VirtualKey:onSwipeKey(arg, ges)
if G_reader_settings:isFalse("keyboard_swipes_enabled") then return true end
Device:performHapticFeedback("KEYBOARD_TAP")
if self.flash_keyboard then
self:invert(true)
UIManager:forceRePaint()
UIManager:yieldToEPDC()
self:invert(false)
if self.swipe_callback then
self.swipe_callback(ges)
end
UIManager:forceRePaint()
else
if self.swipe_callback then
self.swipe_callback(ges)
end
end
return true
end
function VirtualKey:onHoldReleaseKey()
if ignore_key_release then
ignore_key_release = nil
return true
end
if self.ignore_key_release then
self.ignore_key_release = nil
return true
end
Device:performHapticFeedback("LONG_PRESS")
if self.keyboard.ignore_first_hold_release then
self.keyboard.ignore_first_hold_release = false
return true
end
self:onTapSelect()
return true
end
function VirtualKey:onPanReleaseKey()
if self.ignore_key_release then
self.ignore_key_release = nil
return true
end
Device:performHapticFeedback("LONG_PRESS")
if self.keyboard.ignore_first_hold_release then
self.keyboard.ignore_first_hold_release = false
return true
end
self:onTapSelect()
return true
end
-- NOTE: We currently don't ever set hold to true (c.f., our onHoldSelect method)
function VirtualKey:invert(invert, hold)
if invert then
self[1].inner_bordersize = self.focused_bordersize
else
self[1].inner_bordersize = 0
end
Assorted bag'o tweaks & fixes (#9569) * UIManager: Support more specialized update modes for corner-cases: * A2, which we'll use for the VirtualKeyboards keys (they'd... inadvertently switched to UI with the highlight refactor). * NO_MERGE variants of ui & partial (for sunxi). Use `[ui]` in ReaderHighlight's popup, because of a Sage kernel bug that could otherwise make it translucent, sometimes completely so (*sigh*). * UIManager: Assorted code cleanups & simplifications. * Logger & dbg: Unify logging style, and code cleanups. * SDL: Unbreak suspend/resume outside of the emulator (fix #9567). * NetworkMgr: Cache the network status, and allow it to be queried. (Used by AutoSuspend to avoid repeatedly poking the system when computing the standby schedule delay). * OneTimeMigration: Don't forget about `NETWORK_PROXY` & `STARDICT_DATA_DIR` when migrating `defaults.persistent.lua` (fix #9573) * WakeupMgr: Workaround an apparent limitation of the RTC found on i.MX5 Kobo devices, where setting a wakealarm further than UINT16_MAX seconds in the future would apparently overflow and wraparound... (fix #8039, many thanks to @yfede for the extensive deep-dive and for actually accurately pinpointing the issue!). * Kobo: Handle standby transitions at full CPU clock speeds, in order to limit the latency hit. * UIManager: Properly quit on reboot & exit. This ensures our exit code is preserved, as we exit on our own terms (instead of being killed by the init system). This is important on platforms where exit codes are semantically meaningful (e.g., Kobo). * UIManager: Speaking of reboot & exit, make sure the Screensaver shows in all circumstances (e.g., autoshutdown, re: #9542)), and that there aren't any extraneous refreshes triggered. (Additionally, fix a minor regression since #9448 about tracking this very transient state on Kobo & Cervantes). * Kindle: ID the upcoming Scribe. * Bump base (https://github.com/koreader/koreader-base/pull/1524)
2022-10-02 01:01:49 +00:00
self:update_keyboard(hold, true)
2013-07-30 15:07:33 +00:00
end
Clarify our OOP semantics across the codebase (#9586) Basically: * Use `extend` for class definitions * Use `new` for object instantiations That includes some minor code cleanups along the way: * Updated `Widget`'s docs to make the semantics clearer. * Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283) * Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass). * Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events. * Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier. * Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references. * ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak). * Terminal: Make sure the shell is killed on plugin teardown. * InputText: Fix Home/End/Del physical keys to behave sensibly. * InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...). * OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of. * ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed! * Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
VirtualKeyPopup = FocusManager:extend{
modal = true,
disable_double_tap = true,
inputbox = nil,
Clarify our OOP semantics across the codebase (#9586) Basically: * Use `extend` for class definitions * Use `new` for object instantiations That includes some minor code cleanups along the way: * Updated `Widget`'s docs to make the semantics clearer. * Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283) * Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass). * Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events. * Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier. * Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references. * ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak). * Terminal: Make sure the shell is killed on plugin teardown. * InputText: Fix Home/End/Del physical keys to behave sensibly. * InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...). * OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of. * ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed! * Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
layout = nil, -- array
}
function VirtualKeyPopup:onTapClose(arg, ges)
if ges.pos:notIntersectWith(self.dimen) then
UIManager:close(self)
return true
end
return false
end
function VirtualKeyPopup:onClose()
UIManager:close(self)
return true
end
function VirtualKeyPopup:onCloseWidget()
self:free()
UIManager:setDirty(nil, function()
return "ui", self.dimen
end)
end
function VirtualKeyPopup:init()
local parent_key = self.parent_key
local key_chars = parent_key.key_chars
local key_char_orig = key_chars[1]
local key_char_orig_func = parent_key.callback
Clarify our OOP semantics across the codebase (#9586) Basically: * Use `extend` for class definitions * Use `new` for object instantiations That includes some minor code cleanups along the way: * Updated `Widget`'s docs to make the semantics clearer. * Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283) * Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass). * Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events. * Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier. * Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references. * ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak). * Terminal: Make sure the shell is killed on plugin teardown. * InputText: Fix Home/End/Del physical keys to behave sensibly. * InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...). * OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of. * ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed! * Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
self.layout = {}
local rows = {
extra_key_chars = {
key_chars[2],
key_chars[3],
key_chars[4],
-- _func equivalent for unnamed extra keys
key_chars[5],
key_chars[6],
key_chars[7],
},
top_key_chars = {
key_chars.northwest,
key_chars.north,
key_chars.northeast,
key_chars.northwest_func,
key_chars.north_func,
key_chars.northeast_func,
},
middle_key_chars = {
key_chars.west,
key_char_orig,
key_chars.east,
key_chars.west_func,
key_char_orig_func,
key_chars.east_func,
},
bottom_key_chars = {
key_chars.southwest,
key_chars.south,
key_chars.southeast,
key_chars.southwest_func,
key_chars.south_func,
key_chars.southeast_func,
},
}
if util.tableSize(rows.extra_key_chars) == 0 then rows.extra_key_chars = nil end
if util.tableSize(rows.top_key_chars) == 0 then rows.top_key_chars = nil end
-- we should always have a middle
if util.tableSize(rows.bottom_key_chars) == 0 then rows.bottom_key_chars = nil end
-- to store if a column exists
local columns = {}
local blank = {
HorizontalSpan:new{width = 0},
HorizontalSpan:new{width = parent_key.width},
HorizontalSpan:new{width = 0},
}
local h_key_padding = {
HorizontalSpan:new{width = 0},
HorizontalSpan:new{width = parent_key.keyboard.key_padding},
HorizontalSpan:new{width = 0},
}
local v_key_padding = VerticalSpan:new{width = parent_key.keyboard.key_padding}
local vertical_group = VerticalGroup:new{ allow_mirroring = false }
local horizontal_group_extra = HorizontalGroup:new{ allow_mirroring = false }
local horizontal_group_top = HorizontalGroup:new{ allow_mirroring = false }
local horizontal_group_middle = HorizontalGroup:new{ allow_mirroring = false }
local horizontal_group_bottom = HorizontalGroup:new{ allow_mirroring = false }
local function horizontalRow(chars, group)
local layout_horizontal = {}
for i = 1,3 do
local v = chars[i]
local v_func = chars[i+3]
if v then
columns[i] = true
blank[i].width = blank[2].width
if i == 1 then
h_key_padding[i].width = h_key_padding[2].width
end
local key = type(v) == "table" and v.key or v
local label = type(v) == "table" and v.label or key
local icon = type(v) == "table" and v.icon
local bold = type(v) == "table" and v.bold
local virtual_key = VirtualKey:new{
key = key,
label = label,
icon = icon,
bold = bold,
keyboard = parent_key.keyboard,
key_chars = key_chars,
width = parent_key.width,
height = parent_key.height,
close_after_callback_widget = self,
}
-- Support any function as a callback.
if v_func then
virtual_key.callback = v_func
end
-- don't open another popup on hold
virtual_key.hold_callback = nil
-- close popup on hold release
virtual_key.onHoldReleaseKey = function()
-- NOTE: Check our *parent* key!
if parent_key.ignore_key_release then
parent_key.ignore_key_release = nil
return true
end
Device:performHapticFeedback("LONG_PRESS")
if virtual_key.keyboard.ignore_first_hold_release then
virtual_key.keyboard.ignore_first_hold_release = false
return true
end
virtual_key:onTapSelect(true)
UIManager:close(self)
return true
end
virtual_key.onPanReleaseKey = virtual_key.onHoldReleaseKey
if v == key_char_orig then
virtual_key[1].background = Blitbuffer.COLOR_LIGHT_GRAY
-- restore ability to hold/pan release on central key after opening popup
virtual_key._keyOrigHoldPanHandler = function()
virtual_key.onHoldReleaseKey = virtual_key._onHoldReleaseKey
virtual_key.onPanReleaseKey = virtual_key._onPanReleaseKey
end
virtual_key._onHoldReleaseKey = virtual_key.onHoldReleaseKey
virtual_key.onHoldReleaseKey = virtual_key._keyOrigHoldPanHandler
virtual_key._onPanReleaseKey = virtual_key.onPanReleaseKey
virtual_key.onPanReleaseKey = virtual_key._keyOrigHoldPanHandler
end
table.insert(group, virtual_key)
table.insert(layout_horizontal, virtual_key)
else
table.insert(group, blank[i])
end
table.insert(group, h_key_padding[i])
end
table.insert(vertical_group, group)
table.insert(self.layout, layout_horizontal)
end
if rows.extra_key_chars then
horizontalRow(rows.extra_key_chars, horizontal_group_extra)
table.insert(vertical_group, v_key_padding)
end
if rows.top_key_chars then
horizontalRow(rows.top_key_chars, horizontal_group_top)
table.insert(vertical_group, v_key_padding)
end
-- always middle row
horizontalRow(rows.middle_key_chars, horizontal_group_middle)
if rows.bottom_key_chars then
table.insert(vertical_group, v_key_padding)
horizontalRow(rows.bottom_key_chars, horizontal_group_bottom)
end
if not columns[3] then
h_key_padding[2].width = 0
end
local num_rows = util.tableSize(rows)
local num_columns = util.tableSize(columns)
local keyboard_frame = FrameContainer:new{
margin = 0,
bordersize = Size.border.default,
background = G_reader_settings:nilOrTrue("keyboard_key_border") and Blitbuffer.COLOR_LIGHT_GRAY or Blitbuffer.COLOR_WHITE,
radius = 0,
padding = parent_key.keyboard.padding,
allow_mirroring = false,
CenterContainer:new{
dimen = Geom:new{
w = parent_key.width*num_columns + 2*Size.border.default + (num_columns+1)*parent_key.keyboard.key_padding,
h = parent_key.height*num_rows + 2*Size.border.default + (num_rows+1)*parent_key.keyboard.key_padding,
},
vertical_group,
}
}
keyboard_frame.dimen = keyboard_frame:getSize()
self.dimen = keyboard_frame.dimen
self.ges_events.TapClose = {
GestureRange:new{
ges = "tap",
}
}
self.tap_interval_override = time.ms(G_reader_settings:readSetting("ges_tap_interval_on_keyboard_ms", 0))
if Device:hasKeys() then
self.key_events.Close = { { Device.input.group.Back } }
end
local offset_x = 2*keyboard_frame.bordersize + keyboard_frame.padding + parent_key.keyboard.key_padding
if columns[1] then
offset_x = offset_x + parent_key.width + parent_key.keyboard.key_padding
end
local offset_y = 2*keyboard_frame.bordersize + keyboard_frame.padding + parent_key.keyboard.key_padding
if rows.extra_key_chars then
offset_y = offset_y + parent_key.height + parent_key.keyboard.key_padding
end
if rows.top_key_chars then
offset_y = offset_y + parent_key.height + parent_key.keyboard.key_padding
end
local position_container = WidgetContainer:new{
dimen = {
x = parent_key.dimen.x - offset_x,
y = parent_key.dimen.y - offset_y,
h = Screen:getSize().h,
w = Screen:getSize().w,
},
keyboard_frame,
}
if position_container.dimen.x < 0 then
position_container.dimen.x = 0
-- We effectively move the popup, which means the key underneath our finger may no longer *exactly* be parent_key.
-- Make sure we won't close the popup right away, as that would risk being a *different* key, in order to make that less confusing.
parent_key.ignore_key_release = true
elseif position_container.dimen.x + keyboard_frame.dimen.w > Screen:getWidth() then
position_container.dimen.x = Screen:getWidth() - keyboard_frame.dimen.w
parent_key.ignore_key_release = true
end
if position_container.dimen.y < 0 then
position_container.dimen.y = 0
parent_key.ignore_key_release = true
elseif position_container.dimen.y + keyboard_frame.dimen.h > Screen:getHeight() then
position_container.dimen.y = Screen:getHeight() - keyboard_frame.dimen.h
parent_key.ignore_key_release = true
end
self[1] = position_container
UIManager:show(self)
-- Ensure the post-paint refresh will be able to grab updated coordinates from keyboard_frame by using a refresh function
UIManager:setDirty(self, function()
return "ui", self.dimen
end)
end
Clarify our OOP semantics across the codebase (#9586) Basically: * Use `extend` for class definitions * Use `new` for object instantiations That includes some minor code cleanups along the way: * Updated `Widget`'s docs to make the semantics clearer. * Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283) * Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass). * Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events. * Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier. * Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references. * ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak). * Terminal: Make sure the shell is killed on plugin teardown. * InputText: Fix Home/End/Del physical keys to behave sensibly. * InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...). * OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of. * ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed! * Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
local VirtualKeyboard = FocusManager:extend{
name = "VirtualKeyboard",
visible = nil,
lock_visibility = false,
covers_footer = true,
modal = true,
2014-03-13 13:52:43 +00:00
disable_double_tap = true,
inputbox = nil,
Clarify our OOP semantics across the codebase (#9586) Basically: * Use `extend` for class definitions * Use `new` for object instantiations That includes some minor code cleanups along the way: * Updated `Widget`'s docs to make the semantics clearer. * Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283) * Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass). * Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events. * Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier. * Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references. * ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak). * Terminal: Make sure the shell is killed on plugin teardown. * InputText: Fix Home/End/Del physical keys to behave sensibly. * InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...). * OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of. * ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed! * Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
KEYS = nil, -- table to store layouts
shiftmode_keys = nil, -- table
symbolmode_keys = nil, -- table
utf8mode_keys = nil, -- table
umlautmode_keys = nil, -- table
keyboard_layer = 2,
2014-03-13 13:52:43 +00:00
shiftmode = false,
symbolmode = false,
2014-08-09 11:59:05 +00:00
umlautmode = false,
Clarify our OOP semantics across the codebase (#9586) Basically: * Use `extend` for class definitions * Use `new` for object instantiations That includes some minor code cleanups along the way: * Updated `Widget`'s docs to make the semantics clearer. * Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283) * Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass). * Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events. * Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier. * Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references. * ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak). * Terminal: Make sure the shell is killed on plugin teardown. * InputText: Fix Home/End/Del physical keys to behave sensibly. * InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...). * OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of. * ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed! * Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
layout = nil, -- array
height = nil,
default_label_size = DEFAULT_LABEL_SIZE,
2017-09-13 14:56:20 +00:00
bordersize = Size.border.default,
padding = 0,
key_padding = Size.padding.small,
2013-07-30 15:07:33 +00:00
lang_to_keyboard_layout = {
ar = "ar_keyboard",
2021-02-28 18:59:02 +00:00
bg_BG = "bg_keyboard",
bn = "bn_keyboard",
da = "da_keyboard",
de = "de_keyboard",
el = "el_keyboard",
en = "en_keyboard",
es = "es_keyboard",
fa = "fa_keyboard",
fr = "fr_keyboard",
2019-09-22 20:21:00 +00:00
he = "he_keyboard",
ja = "ja_keyboard",
2021-02-01 21:35:15 +00:00
ka = "ka_keyboard",
ko_KR = "ko_KR_keyboard",
nb_NO = "no_keyboard",
pl = "pl_keyboard",
pt_BR = "pt_keyboard",
ro = "ro_keyboard",
ru = "ru_keyboard",
sk = "sk_keyboard",
sv = "sv_keyboard",
th = "th_keyboard",
2020-01-27 23:05:16 +00:00
tr = "tr_keyboard",
uk = "uk_keyboard",
vi = "vi_keyboard",
zh = "zh_keyboard",
zh_CN = "zh_CN_keyboard",
},
lang_has_submenu = {
ja = true,
zh = true,
zh_CN = true,
},
}
2013-07-30 15:07:33 +00:00
function VirtualKeyboard:init()
if self.uwrap_func then
self.uwrap_func()
self.uwrap_func = nil
end
local lang = self:getKeyboardLayout()
local keyboard_layout = self.lang_to_keyboard_layout[lang] or self.lang_to_keyboard_layout["en"]
local keyboard = require("ui/data/keyboardlayouts/" .. keyboard_layout)
Clarify our OOP semantics across the codebase (#9586) Basically: * Use `extend` for class definitions * Use `new` for object instantiations That includes some minor code cleanups along the way: * Updated `Widget`'s docs to make the semantics clearer. * Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283) * Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass). * Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events. * Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier. * Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references. * ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak). * Terminal: Make sure the shell is killed on plugin teardown. * InputText: Fix Home/End/Del physical keys to behave sensibly. * InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...). * OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of. * ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed! * Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
self.KEYS = keyboard.keys or {}
self.shiftmode_keys = keyboard.shiftmode_keys or {}
self.symbolmode_keys = keyboard.symbolmode_keys or {}
self.utf8mode_keys = keyboard.utf8mode_keys or {}
self.umlautmode_keys = keyboard.umlautmode_keys or {}
self.width = Screen:getWidth()
local keys_height = G_reader_settings:isTrue("keyboard_key_compact") and 48 or 64
self.height = Screen:scaleBySize(keys_height * #self.KEYS)
self.min_layer = keyboard.min_layer
self.max_layer = keyboard.max_layer
self:initLayer(self.keyboard_layer)
self.tap_interval_override = time.ms(G_reader_settings:readSetting("ges_tap_interval_on_keyboard_ms", 0))
if Device:hasKeys() then
self.key_events.Close = { { "Back" } }
end
if keyboard.wrapInputBox then
self.uwrap_func = keyboard.wrapInputBox(self.inputbox) or self.uwrap_func
end
if Device:hasDPad() then
-- hadDPad() would have FocusManager handle arrow keys strokes to navigate
-- and activate this VirtualKeyboard's touch keys (needed on non-touch Kindle).
-- If we have a keyboard, we'd prefer arrow keys (and Enter, and Del) to be
-- handled by InputText to navigate the cursor inside the text box, and to
-- add newline and delete chars. And if we are a touch device, we don't
-- need focus manager to help us navigate keys and fields.
-- So, disable all key_event handled by FocusManager
if Device:isTouchDevice() then
-- Remove all FocusManager key event handlers.
for k, _ in pairs(self.builtin_key_events) do
self.key_events[k] = nil
end
for k, _ in pairs(self.extra_key_events) do
self.key_events[k] = nil
end
elseif Device:hasKeyboard() then
-- Use physical keyboard for most characters
-- For special characters not available in physical keyboard
-- Use arrow and Press keys to select in VirtualKeyboard
for k, seq in pairs(self.extra_key_events) do
if self:_isTextKeyWithoutModifier(seq) then
self.key_events[k] = nil
end
end
end
end
end
function VirtualKeyboard:_isTextKeyWithoutModifier(seq)
for _, oneseq in ipairs(seq) do
if #oneseq ~= 1 then -- has modifier key combination
return false
end
if #oneseq[1] ~= 1 then -- not simple text key, like Home, End
return false
end
end
return true
end
function VirtualKeyboard:getKeyboardLayout()
if G_reader_settings:isFalse("keyboard_remember_layout") and not keyboard_state.force_current_layout then
local lang = G_reader_settings:readSetting("keyboard_layout_default")
or G_reader_settings:readSetting("keyboard_layout", "en")
G_reader_settings:saveSetting("keyboard_layout", lang)
end
return G_reader_settings:readSetting("keyboard_layout") or G_reader_settings:readSetting("language")
end
function VirtualKeyboard:setKeyboardLayout(layout)
keyboard_state.force_current_layout = true
local prev_keyboard_height = self.dimen and self.dimen.h
G_reader_settings:saveSetting("keyboard_layout", layout)
self:init()
if prev_keyboard_height and self.dimen.h ~= prev_keyboard_height then
self:_refresh(true, true)
-- Keyboard height change: notify parent (InputDialog)
if self.inputbox and self.inputbox.parent and self.inputbox.parent.onKeyboardHeightChanged then
self.inputbox.parent:onKeyboardHeightChanged()
end
else
self:_refresh(true)
end
keyboard_state.force_current_layout = false
end
function VirtualKeyboard:onClose()
UIManager:close(self)
if self.inputbox and Device:hasDPad() then
-- let input text handle Back event to unfocus
-- otherwise, another extra Back event needed
return false
end
return true
2013-07-30 15:07:33 +00:00
end
function VirtualKeyboard:_refresh(want_flash, fullscreen)
local refresh_type = "ui"
if want_flash then
refresh_type = "flashui"
end
if fullscreen then
UIManager:setDirty("all", refresh_type)
return
end
UIManager:setDirty(self, function()
return refresh_type, self.dimen
end)
end
function VirtualKeyboard:onShow()
self:_refresh(true)
self.visible = true
Device:startTextInput()
return true
end
function VirtualKeyboard:onCloseWidget()
self:_refresh(true)
self.visible = false
-- NOTE: This effectively stops SDL text input when a keyboard is hidden (... but navigational stuff still works).
-- If you instead wanted it to be enabled as long as an input dialog is displayed, regardless of VK's state,
-- this could be moved to InputDialog's onShow/onCloseWidget handlers (but, it would allow input on unfocused fields).
-- NOTE: But something more complex, possibly based on an in-class ref count would have to be implemented in order to be able to deal
-- with multiple InputDialogs being shown and closed in asymmetric fashion... Ugh.
Device:stopTextInput()
end
function VirtualKeyboard:lockVisibility(toggle)
self.lock_visibility = toggle
end
function VirtualKeyboard:setVisibility(toggle)
if self.lock_visibility then
return
end
if toggle then
UIManager:show(self)
else
self:onClose()
end
end
function VirtualKeyboard:isVisible()
return self.visible
end
function VirtualKeyboard:showKeyboard(ignore_first_hold_release)
if not self:isVisible() then
self.ignore_first_hold_release = ignore_first_hold_release
self:setVisibility(true)
end
end
function VirtualKeyboard:hideKeyboard()
if self:isVisible() then
self:setVisibility(false)
end
end
function VirtualKeyboard:initLayer(layer)
local function VKLayer(b1, b2, b3)
2014-03-13 13:52:43 +00:00
local function boolnum(bool)
return bool and 1 or 0
end
return 2 - boolnum(b1) + 2 * boolnum(b2) + 4 * boolnum(b3)
end
if layer then
-- to be sure layer is selected properly
layer = math.max(layer, self.min_layer)
layer = math.min(layer, self.max_layer)
self.keyboard_layer = layer
-- fill the layer modes
self.shiftmode = (layer == 1 or layer == 3 or layer == 5 or layer == 7 or layer == 9 or layer == 11)
self.symbolmode = (layer == 3 or layer == 4 or layer == 7 or layer == 8 or layer == 11 or layer == 12)
self.umlautmode = (layer == 5 or layer == 6 or layer == 7 or layer == 8)
else -- or, without input parameter, restore layer from current layer modes
self.keyboard_layer = VKLayer(self.shiftmode, self.symbolmode, self.umlautmode)
2014-03-13 13:52:43 +00:00
end
self:addKeys()
2013-07-30 15:07:33 +00:00
end
function VirtualKeyboard:addKeys()
self:free() -- free previous keys' TextWidgets
self.layout = {}
local base_key_width = math.floor((self.width - (#self.KEYS[1] + 1)*self.key_padding - 2*self.padding)/#self.KEYS[1])
local base_key_height = math.floor((self.height - (#self.KEYS + 1)*self.key_padding - 2*self.padding)/#self.KEYS)
2014-03-13 13:52:43 +00:00
local h_key_padding = HorizontalSpan:new{width = self.key_padding}
local v_key_padding = VerticalSpan:new{width = self.key_padding}
local vertical_group = VerticalGroup:new{ allow_mirroring = false }
2014-03-13 13:52:43 +00:00
for i = 1, #self.KEYS do
local horizontal_group = HorizontalGroup:new{ allow_mirroring = false }
local layout_horizontal = {}
2014-03-13 13:52:43 +00:00
for j = 1, #self.KEYS[i] do
local key
local key_chars = self.KEYS[i][j][self.keyboard_layer]
local label
local alt_label
local width_factor
if type(key_chars) == "table" then
key = key_chars[1]
label = key_chars.label
alt_label = key_chars.alt_label
width_factor = key_chars.width
else
key = key_chars
key_chars = nil
end
width_factor = width_factor or self.KEYS[i][j].width or self.KEYS[i].width or 1.0
local key_width = math.floor((base_key_width + self.key_padding) * width_factor)
2014-03-13 13:52:43 +00:00
- self.key_padding
local key_height = base_key_height
label = label or self.KEYS[i][j].label or key
if label == "" and self.shiftmode and (not self.release_shift or self.symbolmode) then
key = label
label = "" -- capslock symbol
end
local virtual_key = VirtualKey:new{
key = key,
key_chars = key_chars,
2014-03-13 13:52:43 +00:00
icon = self.KEYS[i][j].icon,
label = label,
alt_label = alt_label,
bold = self.KEYS[i][j].bold,
2014-03-13 13:52:43 +00:00
keyboard = self,
width = key_width,
height = key_height,
}
if not virtual_key.key_chars then
virtual_key.swipe_callback = nil
end
table.insert(horizontal_group, virtual_key)
table.insert(layout_horizontal, virtual_key)
if j ~= #self.KEYS[i] then
2014-03-13 13:52:43 +00:00
table.insert(horizontal_group, h_key_padding)
end
end
table.insert(vertical_group, horizontal_group)
table.insert(self.layout, layout_horizontal)
2014-03-13 13:52:43 +00:00
if i ~= #self.KEYS then
table.insert(vertical_group, v_key_padding)
end
end
2014-03-13 13:52:43 +00:00
local keyboard_frame = FrameContainer:new{
margin = 0,
bordersize = Size.border.default,
background = G_reader_settings:nilOrTrue("keyboard_key_border") and Blitbuffer.COLOR_LIGHT_GRAY or Blitbuffer.COLOR_WHITE,
2014-03-13 13:52:43 +00:00
radius = 0,
padding = self.padding,
allow_mirroring = false,
2014-03-13 13:52:43 +00:00
CenterContainer:new{
dimen = Geom:new{
w = self.width - 2*Size.border.default - 2*self.padding,
h = self.height - 2*Size.border.default - 2*self.padding,
2014-03-13 13:52:43 +00:00
},
vertical_group,
}
}
self[1] = BottomContainer:new{
dimen = Screen:getSize(),
keyboard_frame,
}
-- Point our top-level dimen to the relevant widget, keyboard_frame
keyboard_frame.dimen = keyboard_frame:getSize()
self.dimen = keyboard_frame.dimen
2013-07-30 15:07:33 +00:00
end
function VirtualKeyboard:setLayer(key)
2014-03-13 13:52:43 +00:00
if key == "Shift" then
self.shiftmode = not self.shiftmode
elseif key == "Sym" or key == "ABC" then
self.symbolmode = not self.symbolmode
2014-08-09 11:59:05 +00:00
elseif key == "Äéß" then
self.umlautmode = not self.umlautmode
2014-03-13 13:52:43 +00:00
end
self:initLayer()
self:_refresh(false)
2013-07-30 15:07:33 +00:00
end
function VirtualKeyboard:addChar(key)
logger.dbg("add char", key)
self.inputbox:addChars(key)
2013-07-30 15:07:33 +00:00
end
function VirtualKeyboard:delChar()
logger.dbg("delete char")
2014-03-13 13:52:43 +00:00
self.inputbox:delChar()
2013-07-30 15:07:33 +00:00
end
2013-10-18 20:38:07 +00:00
function VirtualKeyboard:delToStartOfLine()
logger.dbg("delete to start of line")
self.inputbox:delToStartOfLine()
end
function VirtualKeyboard:leftChar()
self.inputbox:leftChar()
end
function VirtualKeyboard:rightChar()
self.inputbox:rightChar()
end
function VirtualKeyboard:goToStartOfLine()
self.inputbox:goToStartOfLine()
end
-- Some keyboard with intermediate state (ie. zh) may need to be notified
function VirtualKeyboard:onSwitchingKeyboardLayout()
if self.inputbox.onSwitchingKeyboardLayout then self.inputbox:onSwitchingKeyboardLayout() end
end
function VirtualKeyboard:goToEndOfLine()
self.inputbox:goToEndOfLine()
end
function VirtualKeyboard:upLine()
self.inputbox:upLine()
end
function VirtualKeyboard:scrollUp()
self.inputbox:scrollUp()
end
function VirtualKeyboard:downLine()
self.inputbox:downLine()
end
function VirtualKeyboard:scrollDown()
self.inputbox:scrollDown()
end
function VirtualKeyboard:clear()
logger.dbg("clear input")
self.inputbox:clear()
end
2013-10-18 20:38:07 +00:00
return VirtualKeyboard