diff --git a/frontend/apps/filemanager/filemanager.lua b/frontend/apps/filemanager/filemanager.lua index 5a5981501..57f7a4e6e 100644 --- a/frontend/apps/filemanager/filemanager.lua +++ b/frontend/apps/filemanager/filemanager.lua @@ -377,6 +377,8 @@ function FileManager:registerKeyEvents() self.file_chooser.key_events.Back = { { Device.input.group.Back } } if Device:hasScreenKB() then self.key_events.ToggleWifi = { { "ScreenKB", "Home" } } + elseif Device:hasKeyboard() then + self.key_events.ToggleWifi = { { "Shift", "Home" } } end if not Device:hasFewKeys() then -- Also remove the handler assigned to the "Back" key by menu.lua diff --git a/frontend/apps/filemanager/filemanagerfilesearcher.lua b/frontend/apps/filemanager/filemanagerfilesearcher.lua index 7076f1d14..f94d0cbb4 100644 --- a/frontend/apps/filemanager/filemanagerfilesearcher.lua +++ b/frontend/apps/filemanager/filemanagerfilesearcher.lua @@ -1,14 +1,15 @@ local ButtonDialog = require("ui/widget/buttondialog") local CheckButton = require("ui/widget/checkbutton") local ConfirmBox = require("ui/widget/confirmbox") +local Device = require("device") local DocSettings = require("docsettings") local DocumentRegistry = require("document/documentregistry") local FileChooser = require("ui/widget/filechooser") local InfoMessage = require("ui/widget/infomessage") +local InputContainer = require("ui/widget/container/inputcontainer") local InputDialog = require("ui/widget/inputdialog") local Menu = require("ui/widget/menu") local UIManager = require("ui/uimanager") -local WidgetContainer = require("ui/widget/container/widgetcontainer") local Utf8Proc = require("ffi/utf8proc") local filemanagerutil = require("apps/filemanager/filemanagerutil") local lfs = require("libs/libkoreader-lfs") @@ -17,12 +18,23 @@ local _ = require("gettext") local N_ = _.ngettext local T = require("ffi/util").template -local FileSearcher = WidgetContainer:extend{ +local FileSearcher = InputContainer:extend{ case_sensitive = false, include_subfolders = true, include_metadata = false, } +function FileSearcher:init() + self:registerKeyEvents() +end + +function FileSearcher:registerKeyEvents() + if Device:hasKeyboard() then + self.key_events.ShowFileSearch = { { "Alt", "F" } } + self.key_events.ShowFileSearchBlank = { { "Alt", "Shift", "F" }, event = "ShowFileSearch", args = "" } + end +end + function FileSearcher:onShowFileSearch(search_string) local search_dialog local check_button_case, check_button_subfolders, check_button_metadata @@ -94,6 +106,7 @@ function FileSearcher:onShowFileSearch(search_string) end UIManager:show(search_dialog) search_dialog:onShowKeyboard() + return true end function FileSearcher:doSearch() diff --git a/frontend/apps/reader/modules/readerdictionary.lua b/frontend/apps/reader/modules/readerdictionary.lua index 4e119b2fe..d4c091b02 100644 --- a/frontend/apps/reader/modules/readerdictionary.lua +++ b/frontend/apps/reader/modules/readerdictionary.lua @@ -6,6 +6,7 @@ local DictQuickLookup = require("ui/widget/dictquicklookup") local Event = require("ui/event") local Geom = require("ui/geometry") local InfoMessage = require("ui/widget/infomessage") +local InputContainer = require("ui/widget/container/inputcontainer") local InputDialog = require("ui/widget/inputdialog") local JSON = require("json") local KeyValuePage = require("ui/widget/keyvaluepage") @@ -15,7 +16,6 @@ local NetworkMgr = require("ui/network/manager") local SortWidget = require("ui/widget/sortwidget") local Trapper = require("ui/trapper") local UIManager = require("ui/uimanager") -local WidgetContainer = require("ui/widget/container/widgetcontainer") local ffi = require("ffi") local C = ffi.C local ffiUtil = require("ffi/util") @@ -60,7 +60,7 @@ local function getIfosInDir(path) return ifos end -local ReaderDictionary = WidgetContainer:extend{ +local ReaderDictionary = InputContainer:extend{ data_dir = nil, lookup_msg = _("Searching dictionary for:\n%1"), } @@ -99,6 +99,8 @@ local function getDictionaryFixHtmlFunc(path) end function ReaderDictionary:init() + self:registerKeyEvents() + self.disable_lookup_history = G_reader_settings:isTrue("disable_lookup_history") self.dicts_order = G_reader_settings:readSetting("dicts_order", {}) self.dicts_disabled = G_reader_settings:readSetting("dicts_disabled", {}) @@ -162,6 +164,12 @@ function ReaderDictionary:init() end end +function ReaderDictionary:registerKeyEvents() + if Device:hasKeyboard() then + self.key_events.ShowDictionaryLookup = { { "Alt", "D" } } + end +end + function ReaderDictionary:sortAvailableIfos() table.sort(available_ifos, function(lifo, rifo) local lord = self.dicts_order[lifo.file] diff --git a/frontend/apps/reader/modules/readersearch.lua b/frontend/apps/reader/modules/readersearch.lua index 831f6fe72..7661ea98b 100644 --- a/frontend/apps/reader/modules/readersearch.lua +++ b/frontend/apps/reader/modules/readersearch.lua @@ -3,6 +3,7 @@ local ButtonDialog = require("ui/widget/buttondialog") local CheckButton = require("ui/widget/checkbutton") local Device = require("device") local InfoMessage = require("ui/widget/infomessage") +local InputContainer = require("ui/widget/container/inputcontainer") local InputDialog = require("ui/widget/inputdialog") local Menu = require("ui/widget/menu") local Notification = require("ui/widget/notification") @@ -10,7 +11,6 @@ local SpinWidget = require("ui/widget/spinwidget") local TextBoxWidget = require("ui/widget/textboxwidget") local UIManager = require("ui/uimanager") local Utf8Proc = require("ffi/utf8proc") -local WidgetContainer = require("ui/widget/container/widgetcontainer") local logger = require("logger") local _ = require("gettext") local C_ = _.pgettext @@ -19,7 +19,7 @@ local T = require("ffi/util").template local DGENERIC_ICON_SIZE = G_defaults:readSetting("DGENERIC_ICON_SIZE") -local ReaderSearch = WidgetContainer:extend{ +local ReaderSearch = InputContainer:extend{ direction = 0, -- 0 for search forward, 1 for search backward case_insensitive = true, -- default to case insensitive @@ -41,6 +41,8 @@ local ReaderSearch = WidgetContainer:extend{ } function ReaderSearch:init() + self:registerKeyEvents() + -- number of words before and after the search string in All search results self.findall_nb_context_words = G_reader_settings:readSetting("fulltext_search_nb_context_words") or 5 self.findall_results_per_page = G_reader_settings:readSetting("fulltext_search_results_per_page") or 10 @@ -81,6 +83,13 @@ SRELL_ERROR_CODES[110] = _("No preceding expression in repetition.") SRELL_ERROR_CODES[111] = _("Expression too complex, some hits will not be shown.") SRELL_ERROR_CODES[666] = _("Expression may lead to an extremely long search time.") +function ReaderSearch:registerKeyEvents() + if Device:hasKeyboard() then + self.key_events.ShowFulltextSearchInputBlank = { { "Alt", "Shift", "S" }, event = "ShowFulltextSearchInput", args = "" } + self.key_events.ShowFulltextSearchInputRecent = { { "Alt", "S" }, event = "ShowFulltextSearchInput" } + end +end + function ReaderSearch:addToMainMenu(menu_items) menu_items.fulltext_search_settings = { text = _("Fulltext search settings"), @@ -244,7 +253,7 @@ function ReaderSearch:searchCallback(reverse, text) end end -function ReaderSearch:onShowFulltextSearchInput() +function ReaderSearch:onShowFulltextSearchInput(search_string) local backward_text = "◁" local forward_text = "▷" if BD.mirroredUILayout() then @@ -253,7 +262,7 @@ function ReaderSearch:onShowFulltextSearchInput() self.input_dialog = InputDialog:new{ title = _("Enter text to search for"), width = math.floor(math.min(Screen:getWidth(), Screen:getHeight()) * 0.9), - input = self.last_search_text or self.ui.doc_settings:readSetting("fulltext_search_last_search_text"), + input = search_string or self.last_search_text or self.ui.doc_settings:readSetting("fulltext_search_last_search_text"), buttons = { { { @@ -310,6 +319,7 @@ function ReaderSearch:onShowFulltextSearchInput() UIManager:show(self.input_dialog) self.input_dialog:onShowKeyboard() + return true end function ReaderSearch:onShowSearchDialog(text, direction, regex, case_insensitive) diff --git a/frontend/apps/reader/modules/readerwikipedia.lua b/frontend/apps/reader/modules/readerwikipedia.lua index 99c92944f..6ca76ef37 100644 --- a/frontend/apps/reader/modules/readerwikipedia.lua +++ b/frontend/apps/reader/modules/readerwikipedia.lua @@ -1,5 +1,6 @@ local ConfirmBox = require("ui/widget/confirmbox") local DataStorage = require("datastorage") +local Device = require("device") local DictQuickLookup = require("ui/widget/dictquicklookup") local InfoMessage = require("ui/widget/infomessage") local InputDialog = require("ui/widget/inputdialog") @@ -28,6 +29,7 @@ local ReaderWikipedia = ReaderDictionary:extend{ } function ReaderWikipedia:init() + self:registerKeyEvents() self.wiki_languages = {} self.ui.menu:registerToMainMenu(self) if not wikipedia_history then @@ -35,6 +37,12 @@ function ReaderWikipedia:init() end end +function ReaderWikipedia:registerKeyEvents() + if Device:hasKeyboard() then + self.key_events.ShowWikipediaLookup = { { "Alt", "W" } } + end +end + function ReaderWikipedia:lookupInput() self.input_dialog = InputDialog:new{ title = _("Enter a word or phrase to look up"), diff --git a/frontend/ui/elements/common_settings_menu_table.lua b/frontend/ui/elements/common_settings_menu_table.lua index b783a6426..815e8a7d6 100644 --- a/frontend/ui/elements/common_settings_menu_table.lua +++ b/frontend/ui/elements/common_settings_menu_table.lua @@ -463,7 +463,8 @@ common_settings.back_in_reader = { genGenericMenuEntry(_("Go to previous read page"), "back_in_reader", "previous_read_page"), }, } -if Device:hasKeyboard() then +-- Kindle keyboard does not have a 'Backspace' key +if Device:hasKeyboard() and not Device:hasSymKey() then common_settings.backspace_as_back = { text = _("Backspace works as back button"), checked_func = function() diff --git a/frontend/ui/elements/menu_keyboard_layout.lua b/frontend/ui/elements/menu_keyboard_layout.lua index fcbce5f07..f3dfe7a0f 100644 --- a/frontend/ui/elements/menu_keyboard_layout.lua +++ b/frontend/ui/elements/menu_keyboard_layout.lua @@ -3,6 +3,7 @@ local CheckMark = require("ui/widget/checkmark") local Device = require("device") local FFIUtil = require("ffi/util") local Font = require("ui/font") +local InfoMessage = require("ui/widget/infomessage") local Language = require("ui/language") local Size = require("ui/size") local TextWidget = require("ui/widget/textwidget") @@ -68,7 +69,6 @@ local function genKeyboardLayoutsSubmenu() if #keyboard_layouts < 4 then table.insert(keyboard_layouts, lang) else -- no more space in the 'globe' popup - local InfoMessage = require("ui/widget/infomessage") UIManager:show(InfoMessage:new{ text = _("Up to four layouts can be enabled."), timeout = 2, @@ -163,6 +163,9 @@ local sub_item_table = { { text = _("Keyboard appearance settings"), keep_menu_open = true, + enabled_func = function() + return G_reader_settings:nilOrTrue("virtual_keyboard_enabled") + end, callback = function(touchmenu_instance) local InputDialog = require("ui/widget/inputdialog") input_dialog = InputDialog:new{ @@ -224,7 +227,26 @@ local sub_item_table = { end, }, } +if Device:hasKeyboard() or Device:hasScreenKB() then + -- we use same pos. 4 as below so we are always above "keyboard apperance settings" + table.insert(sub_item_table, 4, { + text = _("Show virtual keyboard"), + help_text = _("Enable this setting to always display the virtual keyboard within a text input field. When a field is selected (in focus), you can temporarily toggle the keyboard on/off by pressing 'Shift' + 'Home'."), + checked_func = function() + return G_reader_settings:nilOrTrue("virtual_keyboard_enabled") + end, + callback = function() + G_reader_settings:flipNilOrTrue("virtual_keyboard_enabled") + if G_reader_settings:isFalse("virtual_keyboard_enabled") then + UIManager:show(InfoMessage:new{ + text = _("When a text field is selected (in focus), you can temporarily bring up the virtual keyboard by pressing 'Shift' + 'Home'.") + }) + end + end, + }) +end if Device:isTouchDevice() then + -- same pos. 4 as above so if both conditions are met we are above "Show virtual keyboard" table.insert(sub_item_table, 4, { text = _("Swipe to input additional characters"), checked_func = function() diff --git a/frontend/ui/widget/configdialog.lua b/frontend/ui/widget/configdialog.lua index 6013fce03..8321c0fb4 100644 --- a/frontend/ui/widget/configdialog.lua +++ b/frontend/ui/widget/configdialog.lua @@ -29,6 +29,7 @@ local VerticalGroup = require("ui/widget/verticalgroup") local VerticalSpan = require("ui/widget/verticalspan") local logger = require("logger") local serpent = require("ffi/serpent") +local util = require("util") local _ = require("gettext") local Screen = Device.screen local T = require("ffi/util").template @@ -894,8 +895,15 @@ function ConfigDialog:init() } if Device:hasKeys() then -- set up keyboard events - local close_keys = Device:hasFewKeys() and { "Back", "Left" } or Device.input.group.Back - self.key_events.Close = { { close_keys } } + local back_group = util.tableDeepCopy(Device.input.group.Back) + if Device:hasFewKeys() then + table.insert(back_group, "Left") + self.key_events.Close = { { back_group } } + else + table.insert(back_group, "Menu") + table.insert(back_group, "AA") + self.key_events.Close = { { back_group } } + end end end diff --git a/frontend/ui/widget/inputdialog.lua b/frontend/ui/widget/inputdialog.lua index 461e065c3..dbe738ef3 100644 --- a/frontend/ui/widget/inputdialog.lua +++ b/frontend/ui/widget/inputdialog.lua @@ -222,6 +222,10 @@ function InputDialog:init() if self.fullscreen or self.add_nav_bar then self.deny_keyboard_hiding = true end + if (Device:hasKeyboard() or Device:hasScreenKB()) and G_reader_settings:isFalse("virtual_keyboard_enabled") then + self.keyboard_visible = false + self.skip_first_show_keyboard = true + end -- Title & description self.title_bar = TitleBar:new{ @@ -450,7 +454,7 @@ function InputDialog:init() end end - -- If we're fullscreen without a keyboard, make sure only the toggle button can show the keyboard... + -- If we're fullscreen without the virtual keyboard, make sure only the toggle button can bring back the keyboard... if self.fullscreen and not self.keyboard_visible then self:lockKeyboard(true) end @@ -561,6 +565,11 @@ function InputDialog:onCloseWidget() end function InputDialog:onShowKeyboard(ignore_first_hold_release) + -- Don't initiate virtual keyboard when user has a physical keyboard and G_setting(vk_enabled) unchecked. + if self.skip_first_show_keyboard then + self.skip_first_show_keyboard = nil + return + end -- NOTE: There's no VirtualKeyboard widget instantiated at all when readonly, -- and our input widget handles that itself, so we don't need any guards here. -- (In which case, isKeyboardVisible will return `nil`, same as if we had a VK instantiated but *never* shown). @@ -580,6 +589,10 @@ function InputDialog:isKeyboardVisible() end function InputDialog:lockKeyboard(toggle) + if (Device:hasKeyboard() or Device:hasScreenKB()) and G_reader_settings:isFalse("virtual_keyboard_enabled") then + -- do not lock the virtual keyboard when user is hiding it, we still *might* want to activate it via shortcuts ("Shift" + "Home") when in need of special characters or symbols + return + end return self._input_widget:lockKeyboard(toggle) end diff --git a/frontend/ui/widget/inputtext.lua b/frontend/ui/widget/inputtext.lua index 647dcfe8a..c6849bd58 100644 --- a/frontend/ui/widget/inputtext.lua +++ b/frontend/ui/widget/inputtext.lua @@ -300,6 +300,8 @@ local function initDPadEvents() -- Event called by the focusmanager if self.parent.onSwitchFocus then self.parent:onSwitchFocus(self) + elseif (Device:hasKeyboard() or Device:hasScreenKB()) and G_reader_settings:isFalse("virtual_keyboard_enabled") then + do end -- luacheck: ignore 541 else self:onShowKeyboard() end @@ -584,6 +586,17 @@ function InputText:focus() self._frame_textwidget.color = Blitbuffer.COLOR_BLACK end +-- NOTE: This key_map can be used for keyboards without numeric keys, such as on Kindles with keyboards. It is loosely 'inspired' by the symbol layer on the virtual keyboard but, +-- we have taken the liberty of making some adjustments since: +-- * K3 does not have numeric keys (top row) and, +-- * we want to prioritise the most-likely-used characters for "style tweaks" and note taking +-- (in English, sorry everybody else, there are just not enough keys) +local sym_key_map = { + ["Q"] = "!", ["W"] = "?", ["E"] = "-", ["R"] = "_", ["T"] = "%", ["Y"] = "=", ["U"] = "7", ["I"] = "8", ["O"] = "9", ["P"] = "0", + ["A"] = "<", ["S"] = ">", ["D"] = "(", ["F"] = ")", ["G"] = "#", ["H"] = "'", ["J"] = "4", ["K"] = "5", ["L"] = "6", + ["Z"] = "{", ["X"] = "}", ["C"] = "[", ["V"] = "]", ["B"] = "1", ["N"] = "2", ["M"] = "3", ["."] = ":", ["AA"] = ";", +} + -- Handle real keypresses from a physical keyboard, even if the virtual keyboard -- is shown. Mostly likely to be in the emulator, but could be Android + BT -- keyboard, or a "coder's keyboard" Android input method. @@ -609,9 +622,11 @@ function InputText:onKeyPress(key) self:leftChar() elseif key["Right"] then self:rightChar() - elseif key["Up"] then + -- NOTE: When we are not showing the virtual keyboard, let focusmanger handle up/down keys, as they are used to directly move around the widget + -- seemlessly in and out of text fields and onto virtual buttons like `[cancel] [search dict]`, no need to unfocus first. + elseif key["Up"] and G_reader_settings:nilOrTrue("virtual_keyboard_enabled") then self:upLine() - elseif key["Down"] then + elseif key["Down"] and G_reader_settings:nilOrTrue("virtual_keyboard_enabled") then self:downLine() elseif key["End"] then self:goToEnd() @@ -621,7 +636,8 @@ function InputText:onKeyPress(key) self:addChars("\n") elseif key["Tab"] then self:addChars(" ") - elseif key["Back"] then + -- as stated before, we also don't need to unfocus when there is no keyboard, one less key press to exit widgets, yay! + elseif key["Back"] and G_reader_settings:nilOrTrue("virtual_keyboard_enabled") then if self.focused then self:unfocus() end @@ -641,8 +657,12 @@ function InputText:onKeyPress(key) end if not handled and (key["ScreenKB"] or key["Shift"]) then handled = true - if key["Back"] then + if key["Back"] and Device:hasScreenKB() then self:delChar() + elseif key["Back"] and Device:hasSymKey() then + self:delToStartOfLine() + elseif key["Del"] and Device:hasSymKey() then + self:delWord() elseif key["Left"] then self:leftChar() elseif key["Right"] then @@ -664,6 +684,15 @@ function InputText:onKeyPress(key) handled = false end end + if not handled and Device:hasSymKey() then + local symkey = sym_key_map[key.key] + -- Do not match Shift + Sym + 'Alphabet keys' + if symkey and key.modifiers["Sym"] and not key.modifiers["Shift"] then + self:addChars(symkey) + else + handled = false + end + end if not handled and Device:hasDPad() then -- FocusManager may turn on alternative key maps. -- These key map maybe single text keys. @@ -680,6 +709,10 @@ function InputText:onKeyPress(key) -- if it is single text char, insert it local key_code = key.key -- is in upper case if not Device.isSDL() and #key_code == 1 then + if key["Shift"] and key["Alt"] and key["G"] then + -- Allow the screenshot keyboard-shortcut to work when focus is on InputText + return false + end if not key["Shift"] then key_code = string.lower(key_code) end diff --git a/frontend/ui/widget/multiinputdialog.lua b/frontend/ui/widget/multiinputdialog.lua index 92866df15..cfb5c86af 100644 --- a/frontend/ui/widget/multiinputdialog.lua +++ b/frontend/ui/widget/multiinputdialog.lua @@ -237,7 +237,11 @@ function MultiInputDialog:onSwitchFocus(inputbox) self._input_widget:focus() self.focused_field_idx = inputbox.idx - -- Make sure we have a (new) visible keyboard + if (Device:hasKeyboard() or Device:hasScreenKB()) and G_reader_settings:isFalse("virtual_keyboard_enabled") then + -- do not load virtual keyboard when user is hiding it. + return + end + -- Otherwise make sure we have a (new) visible keyboard self:onShowKeyboard() end diff --git a/frontend/ui/widget/textboxwidget.lua b/frontend/ui/widget/textboxwidget.lua index 12cbbf7ba..1ca6ade8a 100644 --- a/frontend/ui/widget/textboxwidget.lua +++ b/frontend/ui/widget/textboxwidget.lua @@ -1886,7 +1886,7 @@ function TextBoxWidget:moveCursorDown() end function TextBoxWidget:moveCursorHome() - self:moveCursorToCharPos(self.vertical_string_list[self.current_line_num].offset) + self:moveCursorToCharPos(self.vertical_string_list[self.current_line_num] and self.vertical_string_list[self.current_line_num].offset or #self.charlist) end function TextBoxWidget:moveCursorEnd()