diff --git a/frontend/apps/cloudstorage/cloudstorage.lua b/frontend/apps/cloudstorage/cloudstorage.lua index c69a1607d..4e28dbbb2 100644 --- a/frontend/apps/cloudstorage/cloudstorage.lua +++ b/frontend/apps/cloudstorage/cloudstorage.lua @@ -641,6 +641,7 @@ function CloudStorage:createFolder(url) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(input_dialog) end, diff --git a/frontend/apps/cloudstorage/dropbox.lua b/frontend/apps/cloudstorage/dropbox.lua index 79c2134d9..e2bb25716 100644 --- a/frontend/apps/cloudstorage/dropbox.lua +++ b/frontend/apps/cloudstorage/dropbox.lua @@ -131,6 +131,7 @@ function DropBox:config(item, callback) { { text = _("Cancel"), + id = "close", callback = function() self.settings_dialog:onClose() UIManager:close(self.settings_dialog) diff --git a/frontend/apps/cloudstorage/ftp.lua b/frontend/apps/cloudstorage/ftp.lua index e1fba231c..73e9f39ca 100644 --- a/frontend/apps/cloudstorage/ftp.lua +++ b/frontend/apps/cloudstorage/ftp.lua @@ -125,6 +125,7 @@ Username and password are optional.]]) { { text = _("Cancel"), + id = "close", callback = function() self.settings_dialog:onClose() UIManager:close(self.settings_dialog) diff --git a/frontend/apps/cloudstorage/webdav.lua b/frontend/apps/cloudstorage/webdav.lua index 1bd64a2d4..1eef559af 100644 --- a/frontend/apps/cloudstorage/webdav.lua +++ b/frontend/apps/cloudstorage/webdav.lua @@ -110,6 +110,7 @@ The start folder is appended to the server path.]]) { { text = _("Cancel"), + id = "close", callback = function() self.settings_dialog:onClose() UIManager:close(self.settings_dialog) diff --git a/frontend/apps/filemanager/filemanager.lua b/frontend/apps/filemanager/filemanager.lua index 53026f86b..7572132ae 100644 --- a/frontend/apps/filemanager/filemanager.lua +++ b/frontend/apps/filemanager/filemanager.lua @@ -255,6 +255,7 @@ function FileManager:setupLayout() buttons = {{ { text = _("Cancel"), + id = "close", enabled = true, callback = function() UIManager:close(file_manager.rename_dialog) @@ -1015,6 +1016,7 @@ function FileManager:createFolder() { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(input_dialog) end, diff --git a/frontend/apps/filemanager/filemanagerfilesearcher.lua b/frontend/apps/filemanager/filemanagerfilesearcher.lua index f75959c33..e4f88fed7 100644 --- a/frontend/apps/filemanager/filemanagerfilesearcher.lua +++ b/frontend/apps/filemanager/filemanagerfilesearcher.lua @@ -125,6 +125,7 @@ function FileSearcher:onShowFileSearch(search_string) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(self.search_dialog) end, diff --git a/frontend/apps/filemanager/filemanagersetdefaults.lua b/frontend/apps/filemanager/filemanagersetdefaults.lua index 69a69b35d..092c9777c 100644 --- a/frontend/apps/filemanager/filemanagersetdefaults.lua +++ b/frontend/apps/filemanager/filemanagersetdefaults.lua @@ -112,6 +112,7 @@ function SetDefaults:init() local cancel_button = { text = _("Cancel"), + id = "close", enabled = true, callback = function() self:close() diff --git a/frontend/apps/filemanager/filemanagershortcuts.lua b/frontend/apps/filemanager/filemanagershortcuts.lua index bf171129a..a541b4030 100644 --- a/frontend/apps/filemanager/filemanagershortcuts.lua +++ b/frontend/apps/filemanager/filemanagershortcuts.lua @@ -82,6 +82,7 @@ function FileManagerShortcuts:addNewFolder() { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(add_folder_input) end, @@ -168,6 +169,7 @@ function FileManagerShortcuts:editFolderShortcut(item) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(edit_folder_input) end, diff --git a/frontend/apps/reader/modules/readerbookmark.lua b/frontend/apps/reader/modules/readerbookmark.lua index 96c8f2474..e600264ae 100644 --- a/frontend/apps/reader/modules/readerbookmark.lua +++ b/frontend/apps/reader/modules/readerbookmark.lua @@ -1055,6 +1055,7 @@ function ReaderBookmark:renameBookmark(item, from_highlight, is_new_note, new_te { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(self.input) if is_new_note then -- "Add note" cancelled, remove saved highlight @@ -1121,6 +1122,7 @@ function ReaderBookmark:onSearchBookmark(bm_menu) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(input_dialog) end, diff --git a/frontend/apps/reader/modules/readerdictionary.lua b/frontend/apps/reader/modules/readerdictionary.lua index 5d17ead7d..eafc7bf22 100644 --- a/frontend/apps/reader/modules/readerdictionary.lua +++ b/frontend/apps/reader/modules/readerdictionary.lua @@ -689,6 +689,7 @@ function ReaderDictionary:onShowDictionaryLookup() { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(self.dictionary_lookup_dialog) end, diff --git a/frontend/apps/reader/modules/readerfooter.lua b/frontend/apps/reader/modules/readerfooter.lua index 373c6a0a2..cf987246d 100644 --- a/frontend/apps/reader/modules/readerfooter.lua +++ b/frontend/apps/reader/modules/readerfooter.lua @@ -642,6 +642,7 @@ function ReaderFooter:set_custom_text(touchmenu_instance) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(text_dialog) end, diff --git a/frontend/apps/reader/modules/readergoto.lua b/frontend/apps/reader/modules/readergoto.lua index 2d5393310..25d6ebc0e 100644 --- a/frontend/apps/reader/modules/readergoto.lua +++ b/frontend/apps/reader/modules/readergoto.lua @@ -81,6 +81,7 @@ x for an absolute page number { { text = _("Cancel"), + id = "close", callback = function() self:close() end, diff --git a/frontend/apps/reader/modules/readersearch.lua b/frontend/apps/reader/modules/readersearch.lua index 32e57584e..042561365 100644 --- a/frontend/apps/reader/modules/readersearch.lua +++ b/frontend/apps/reader/modules/readersearch.lua @@ -114,6 +114,7 @@ function ReaderSearch:onShowFulltextSearchInput() { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(self.input_dialog) end, diff --git a/frontend/apps/reader/modules/readertoc.lua b/frontend/apps/reader/modules/readertoc.lua index 42cfeb109..bbc666bed 100644 --- a/frontend/apps/reader/modules/readertoc.lua +++ b/frontend/apps/reader/modules/readertoc.lua @@ -812,15 +812,20 @@ function ReaderToc:onShowToc() end function toc_menu:onMenuHold(item) - -- Match the items' width - local infomessage = InfoMessage:new{ - width = Screen:getWidth() - (Size.padding.fullscreen * (can_collapse and 4 or 3)), - alignment = "center", - show_icon = false, - text = item.text, - face = Font:getFace("infofont", self.items_font_size), - } - UIManager:show(infomessage) + if not Device:isTouchDevice() and (item.state and item.state.callback) then + -- non touch to expand toc + item.state.callback() + else + -- Match the items' width + local infomessage = InfoMessage:new{ + width = Screen:getWidth() - (Size.padding.fullscreen * (can_collapse and 4 or 3)), + alignment = "center", + show_icon = false, + text = item.text, + face = Font:getFace("infofont", self.items_font_size), + } + UIManager:show(infomessage) + end return true end diff --git a/frontend/apps/reader/modules/readeruserhyph.lua b/frontend/apps/reader/modules/readeruserhyph.lua index efd8959a0..0b0104747 100644 --- a/frontend/apps/reader/modules/readeruserhyph.lua +++ b/frontend/apps/reader/modules/readeruserhyph.lua @@ -188,6 +188,7 @@ function ReaderUserHyph:modifyUserEntry(word) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(input_dialog) end, diff --git a/frontend/apps/reader/modules/readerwikipedia.lua b/frontend/apps/reader/modules/readerwikipedia.lua index 00bf685e1..6f36b7e14 100644 --- a/frontend/apps/reader/modules/readerwikipedia.lua +++ b/frontend/apps/reader/modules/readerwikipedia.lua @@ -44,6 +44,7 @@ function ReaderWikipedia:lookupInput() { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(self.input_dialog) end, @@ -153,6 +154,7 @@ function ReaderWikipedia:addToMainMenu(menu_items) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(wikilang_input) end, diff --git a/frontend/apps/reader/readerui.lua b/frontend/apps/reader/readerui.lua index 32659965f..166114aef 100644 --- a/frontend/apps/reader/readerui.lua +++ b/frontend/apps/reader/readerui.lua @@ -661,6 +661,7 @@ function ReaderUI:unlockDocumentWithPassword(document, try_again) { { text = _("Cancel"), + id = "close", enabled = true, callback = function() self:closeDialog() diff --git a/frontend/device/kindle/device.lua b/frontend/device/kindle/device.lua index a81c35d3d..125526149 100644 --- a/frontend/device/kindle/device.lua +++ b/frontend/device/kindle/device.lua @@ -155,18 +155,23 @@ end function Kindle:setDateTime(year, month, day, hour, min, sec) if hour == nil or min == nil then return true end - local command + local commands = {} if year and month and day then - command = string.format("date -s '%d-%d-%d %d:%d:%d'", year, month, day, hour, min, sec) + table.insert(commands, string.format("date -s '%d-%d-%d %d:%d:%d'", year, month, day, hour, min, sec)) + --Kindle DX + --BusyBox v1.7.2 (2011-01-13 18:01:58 PST) multi-call binary + --Usage: date [OPTION]... [MMDDhhmm[[CC]YY][.ss]] [+FORMAT] + table.insert(commands, string.format("date -s '%02d%02d%02d%02d%04d.%02d'", month, day, hour, min, year, sec)) else - command = string.format("date -s '%d:%d'",hour, min) + table.insert(commands,string.format("date -s '%d:%d'",hour, min)) end - if os.execute(command) == 0 then - os.execute('hwclock -u -w') - return true - else - return false + for _, command in ipairs(commands) do + if os.execute(command) == 0 then + os.execute("hwclock -u -w") + return true + end end + return false end function Kindle:usbPlugIn() diff --git a/frontend/device/sdl/event_map_sdl2.lua b/frontend/device/sdl/event_map_sdl2.lua index d2a6679ec..1a3d4cf3e 100644 --- a/frontend/device/sdl/event_map_sdl2.lua +++ b/frontend/device/sdl/event_map_sdl2.lua @@ -65,6 +65,7 @@ return { [1073742050] = "Alt", -- left alt [1073742054] = "AA", -- right alt key [1073741925] = "ContextMenu", -- Context menu key + [1073741942] = "ContextMenu", -- Context menu key [0x400000E0] = "Ctrl", -- Left Ctrl [0x400000E4] = "Ctrl", -- Right Ctrl [1073742051] = "Win", -- Left Win/Cmd diff --git a/frontend/ui/screensaver.lua b/frontend/ui/screensaver.lua index cc133f22b..614b466d8 100644 --- a/frontend/ui/screensaver.lua +++ b/frontend/ui/screensaver.lua @@ -365,6 +365,7 @@ function Screensaver:setMessage() { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(self.input_dialog) end, diff --git a/frontend/ui/widget/bookstatuswidget.lua b/frontend/ui/widget/bookstatuswidget.lua index 03571064f..dc4b9911c 100644 --- a/frontend/ui/widget/bookstatuswidget.lua +++ b/frontend/ui/widget/bookstatuswidget.lua @@ -3,13 +3,13 @@ local Button = require("ui/widget/button") local CenterContainer = require("ui/widget/container/centercontainer") local Device = require("device") local Font = require("ui/font") +local FocusManager = require("ui/widget/focusmanager") local FrameContainer = require("ui/widget/container/framecontainer") local Geom = require("ui/geometry") local GestureRange = require("ui/gesturerange") 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 InputDialog = require("ui/widget/inputdialog") local InputText = require("ui/widget/inputtext") local LeftContainer = require("ui/widget/container/leftcontainer") @@ -39,7 +39,7 @@ local stats_book = {} ["status"] = "Reading" ["modified"] = "24.01.2016" },]] -local BookStatusWidget = InputContainer:new{ +local BookStatusWidget = FocusManager:new{ padding = Size.padding.fullscreen, settings = nil, thumbnail = nil, @@ -54,6 +54,7 @@ local BookStatusWidget = InputContainer:new{ } function BookStatusWidget:init() + self.layout = {} if self.settings then -- What a blank, full summary table should look like local new_summary = { @@ -100,11 +101,7 @@ function BookStatusWidget:init() } if Device:hasKeys() then - self.key_events = { - --don't get locked in on non touch devices - AnyKeyPressed = { { Device.input.group.Any }, - seqtext = "any key", doc = "close dialog" } - } + self.key_events.Close = { { Device.input.group.Back }, doc = "close dialog" } end if Device:isTouchDevice() then self.ges_events.Swipe = { @@ -252,28 +249,35 @@ function BookStatusWidget:setStar(num) self.stars_container:clear() local stars_group = HorizontalGroup:new{ align = "center" } + local row = {} if num then self.summary.rating = num self:saveSummary() for i = 1, num do - table.insert(stars_group, self.star:new{ + local star = self.star:new{ icon = "star.full", callback = function() self:setStar(i) end - }) + } + table.insert(stars_group, star) + table.insert(row, star) end else num = 0 end for i = num + 1, 5 do - table.insert(stars_group, self.star:new{ callback = function() self:setStar(i) end }) + local star = self.star:new{ callback = function() self:setStar(i) end } + table.insert(stars_group, star) + table.insert(row, star) end + self.layout[1] = row table.insert(self.stars_container, stars_group) -- Individual stars are Button, w/ flash_ui, they'll have their own flash. -- And we need to redraw the full widget, because we don't know the coordinates of stars_container :/. + self:refocusWidget() UIManager:setDirty(self, "ui", nil, true) return true end @@ -487,6 +491,7 @@ function BookStatusWidget:genSummaryGroup(width) readonly = self.readonly, hint = _("A few words about the book"), } + table.insert(self.layout, {self.input_note}) return VerticalGroup:new{ VerticalSpan:new{ width = Size.span.vertical_large }, @@ -562,6 +567,7 @@ function BookStatusWidget:generateSwitchGroup(width) readonly = self.readonly, } switch:setPosition(position) + self:mergeLayoutInVertical(switch) return VerticalGroup:new{ VerticalSpan:new{ width = Screen:scaleBySize(10) }, @@ -582,11 +588,6 @@ function BookStatusWidget:onConfigChoose(values, name, event, args, position) end) end - -function BookStatusWidget:onAnyKeyPressed() - return self:onClose() -end - function BookStatusWidget:onSwipe(arg, ges_ev) if ges_ev.direction == "south" then -- Allow easier closing with swipe down @@ -631,6 +632,7 @@ function BookStatusWidget:onSwitchFocus(inputbox) { { text = _("Cancel"), + id = "close", callback = function() self:closeInputDialog() end, @@ -653,6 +655,7 @@ end function BookStatusWidget:closeInputDialog() UIManager:close(self.note_dialog) + self.input_note:onUnfocus(); end return BookStatusWidget diff --git a/frontend/ui/widget/buttonprogresswidget.lua b/frontend/ui/widget/buttonprogresswidget.lua index f33ef2d50..fecdb9172 100644 --- a/frontend/ui/widget/buttonprogresswidget.lua +++ b/frontend/ui/widget/buttonprogresswidget.lua @@ -253,6 +253,7 @@ function ButtonProgressWidget:update() table.insert(self.layout[1], button) end + self:refocusWidget() UIManager:setDirty(self.show_parrent, function() return "ui", self.dimen end) diff --git a/frontend/ui/widget/buttontable.lua b/frontend/ui/widget/buttontable.lua index ac817c609..edce8175e 100644 --- a/frontend/ui/widget/buttontable.lua +++ b/frontend/ui/widget/buttontable.lua @@ -24,12 +24,10 @@ local ButtonTable = FocusManager:new{ zero_sep = false, button_font_face = "cfont", button_font_size = 20, - auto_focus_first_button = true, } function ButtonTable:init() self.width = self.width or math.floor(math.min(Screen:getWidth(), Screen:getHeight()) * 0.9) - self.selected = { x = 1, y = 1 } self.buttons_layout = {} self.button_by_id = {} self.container = VerticalGroup:new{ width = self.width } @@ -100,10 +98,7 @@ function ButtonTable:init() self:addHorizontalSep(true, false, false) if Device:hasDPad() then self.layout = self.buttons_layout - if self.auto_focus_first_button then - self.layout[1][1]:onFocus() - end - self.key_events.SelectByKeyPress = { {{"Press"}} } + self:refocusWidget() else self.key_events = {} -- deregister all key press event listeners end @@ -129,15 +124,6 @@ function ButtonTable:addHorizontalSep(vspan_before, add_line, vspan_after, black end end -function ButtonTable:onSelectByKeyPress() - local item = self:getFocusItem() - if item and item.enabled then - item.callback() - return true - end - return false -end - function ButtonTable:getButtonById(id) return self.button_by_id[id] -- nil if not found end diff --git a/frontend/ui/widget/checkbutton.lua b/frontend/ui/widget/checkbutton.lua index 69b46bee9..de2cbd222 100644 --- a/frontend/ui/widget/checkbutton.lua +++ b/frontend/ui/widget/checkbutton.lua @@ -16,7 +16,6 @@ Example: local Blitbuffer = require("ffi/blitbuffer") local CheckMark = require("ui/widget/checkmark") -local Device = require("device") local Font = require("ui/font") local FrameContainer = require("ui/widget/container/framecontainer") local GestureRange = require("ui/gesturerange") @@ -97,32 +96,30 @@ function CheckButton:initCheckButton(checked) self.dimen = self._frame:getSize() self[1] = self._frame - if Device:isTouchDevice() then - self.ges_events = { - TapCheckButton = { - GestureRange:new{ - ges = "tap", - range = self.dimen, - }, - doc = "Tap Button", + self.ges_events = { + TapCheckButton = { + GestureRange:new{ + ges = "tap", + range = self.dimen, }, - HoldCheckButton = { - GestureRange:new{ - ges = "hold", - range = self.dimen, - }, - doc = "Hold Button", + doc = "Tap Button", + }, + HoldCheckButton = { + GestureRange:new{ + ges = "hold", + range = self.dimen, + }, + doc = "Hold Button", + }, + -- Safe-guard for when used inside a MovableContainer + HoldReleaseCheckButton = { + GestureRange:new{ + ges = "hold_release", + range = self.dimen, }, - -- Safe-guard for when used inside a MovableContainer - HoldReleaseCheckButton = { - GestureRange:new{ - ges = "hold_release", - range = self.dimen, - }, - doc = "Hold Release Button", - } + doc = "Hold Release Button", } - end + } end function CheckButton:onTapCheckButton() @@ -225,4 +222,20 @@ function CheckButton:disable() end) end +function CheckButton:onFocus() + if not self.enabled then + return false + end + self._frame.invert = true + return true +end + +function CheckButton:onUnfocus() + if not self.enabled then + return false + end + self._frame.invert = false + return true +end + return CheckButton diff --git a/frontend/ui/widget/configdialog.lua b/frontend/ui/widget/configdialog.lua index 17776a43b..f83401e48 100644 --- a/frontend/ui/widget/configdialog.lua +++ b/frontend/ui/widget/configdialog.lua @@ -665,9 +665,7 @@ end function ConfigOption:_itemGroupToLayoutLine(option_items_group) local layout_line = {} -- Insert items (skpping item_spacing without a .name attribute), - -- skipping indices at the beginning of the line in the layout - -- to align it with the current selected tab - local j = self.config.panel_index + local j = 1 -- no nil in row head for i, v in ipairs(option_items_group) do if v.name then if v.layout and v.disableFocusManagement then -- it is a FocusManager @@ -680,7 +678,7 @@ function ConfigOption:_itemGroupToLayoutLine(option_items_group) j = j + 1 end end - v:disableFocusManagement() + v:disableFocusManagement(self.config) else layout_line[j] = v j = j + 1 @@ -883,9 +881,6 @@ function ConfigDialog:init() local close_keys = Device:hasFewKeys() and { "Back", "Left" } or Device.input.group.Back self.key_events.Close = { { close_keys }, doc = "close config menu" } end - if Device:hasDPad() then - self.key_events.Select = { {"Press"}, doc = "select current menu item" } - end end function ConfigDialog:updateConfigPanel(index) @@ -893,6 +888,7 @@ function ConfigDialog:updateConfigPanel(index) end function ConfigDialog:update() + self:moveFocusTo(1, 1) -- reset selected for re-created layout self.layout = {} if self.config_menubar then @@ -927,8 +923,7 @@ function ConfigDialog:update() self.dialog_frame.dimen = old_dimen -- Reset the focusmanager cursor - self.selected.y=#self.layout - self.selected.x=self.panel_index + self:moveFocusTo(self.panel_index, #self.layout, FocusManager.NOT_FOCUS) self[1] = BottomContainer:new{ dimen = Screen:getSize(), @@ -1463,8 +1458,4 @@ function ConfigDialog:onClose() return true end -function ConfigDialog:onSelect() - return self:sendTapEventToFocusedWidget() -end - return ConfigDialog diff --git a/frontend/ui/widget/confirmbox.lua b/frontend/ui/widget/confirmbox.lua index 5def72e92..ea0c8db51 100644 --- a/frontend/ui/widget/confirmbox.lua +++ b/frontend/ui/widget/confirmbox.lua @@ -35,7 +35,6 @@ local TextBoxWidget = require("ui/widget/textboxwidget") local UIManager = require("ui/uimanager") local VerticalGroup = require("ui/widget/verticalgroup") local VerticalSpan = require("ui/widget/verticalspan") -local logger = require("logger") local _ = require("gettext") local Screen = Device.screen @@ -72,9 +71,7 @@ function ConfirmBox:init() } end if Device:hasKeys() then - self.key_events = { - Close = { {Device.input.group.Back}, doc = "cancel" } - } + self.key_events.Close = { {Device.input.group.Back}, doc = "cancel" } end end local text_widget = TextBoxWidget:new{ @@ -214,15 +211,4 @@ function ConfirmBox:onTapClose(arg, ges) return true end -function ConfirmBox:onSelect() - logger.dbg("selected:", self.selected.x) - if self.selected.x == 1 then - self:ok_callback() - else - self:cancel_callback() - end - UIManager:close(self) - return true -end - return ConfirmBox diff --git a/frontend/ui/widget/container/framecontainer.lua b/frontend/ui/widget/container/framecontainer.lua index 1b5c22697..d11016382 100644 --- a/frontend/ui/widget/container/framecontainer.lua +++ b/frontend/ui/widget/container/framecontainer.lua @@ -62,22 +62,27 @@ end function FrameContainer:onFocus() if not self.focusable then - return + return false end self._origin_bordersize = self.bordersize self._origin_border_color = self.color self.bordersize = self.focus_border_size self.color = self.focus_border_color + self._focused = true return true end function FrameContainer:onUnfocus() if not self.focusable then - return + return false end - self.bordersize = self._origin_bordersize - self.color = self._origin_border_color - return true + if self._focused then + self.bordersize = self._origin_bordersize + self.color = self._origin_border_color + self._focused = nil + return true + end + return false end diff --git a/frontend/ui/widget/container/inputcontainer.lua b/frontend/ui/widget/container/inputcontainer.lua index 40de21ea5..5e8e65f18 100644 --- a/frontend/ui/widget/container/inputcontainer.lua +++ b/frontend/ui/widget/container/inputcontainer.lua @@ -282,6 +282,7 @@ function InputContainer:onInput(input, ignore_first_hold_release) { { text = input.cancel_text or _("Cancel"), + id = "close", callback = function() self:closeInputDialog() end, diff --git a/frontend/ui/widget/datetimewidget.lua b/frontend/ui/widget/datetimewidget.lua index 664759ee2..5c91af5a8 100644 --- a/frontend/ui/widget/datetimewidget.lua +++ b/frontend/ui/widget/datetimewidget.lua @@ -39,12 +39,12 @@ local Blitbuffer = require("ffi/blitbuffer") local ButtonTable = require("ui/widget/buttontable") local CenterContainer = require("ui/widget/container/centercontainer") local Device = require("device") +local FocusManager = require("ui/widget/focusmanager") local FrameContainer = require("ui/widget/container/framecontainer") local Geom = require("ui/geometry") local GestureRange = require("ui/gesturerange") local Font = require("ui/font") local HorizontalGroup = require("ui/widget/horizontalgroup") -local InputContainer = require("ui/widget/container/inputcontainer") local NumberPickerWidget = require("ui/widget/numberpickerwidget") local Size = require("ui/size") local TextBoxWidget = require("ui/widget/textboxwidget") @@ -56,7 +56,7 @@ local _ = require("gettext") local Screen = Device.screen local T = require("ffi/util").template -local DateTimeWidget = InputContainer:new{ +local DateTimeWidget = FocusManager:new{ title_face = Font:getFace("x_smalltfont"), info_text = nil, width = nil, @@ -66,7 +66,6 @@ local DateTimeWidget = InputContainer:new{ month = 1, year = 2021, hour = 12, - hour_max = 23, min = 0, ok_text = _("Apply"), cancel_text = _("Close"), @@ -76,14 +75,13 @@ local DateTimeWidget = InputContainer:new{ } function DateTimeWidget:init() + self.layout = {} self.screen_width = Screen:getWidth() self.screen_height = Screen:getHeight() self.width = self.width or math.floor(math.min(self.screen_width, self.screen_height) * (self.is_date and 0.8 or 0.6)) if Device:hasKeys() then - self.key_events = { - Close = { {Device.input.group.Back}, doc = "close date widget" } - } + self.key_events.Close = { {Device.input.group.Back}, doc = "close date widget" } end if Device:isTouchDevice() then self.ges_events = { @@ -100,11 +98,11 @@ function DateTimeWidget:init() end -- Actually the widget layout - self:layout() + self:createLayout() end local year_widget, month_hour_widget, day_min_widget -function DateTimeWidget:layout() +function DateTimeWidget:createLayout() year_widget = NumberPickerWidget:new{ show_parent = self, value = self.year, @@ -113,14 +111,18 @@ function DateTimeWidget:layout() value_step = 1, value_hold_step = self.year_hold_step or 4, } + if self.is_date then + self:mergeLayoutInHorizontal(year_widget) + end month_hour_widget = NumberPickerWidget:new{ show_parent = self, value = self.is_date and self.month or self.hour, value_min = self.hour_min or self.month_min or (self.is_date and 1 or 0), - value_max = self.hour_max or self.month_max or (self.is_date and 12 or 24), + value_max = self.hour_max or self.month_max or (self.is_date and 12 or 23), value_step = 1, value_hold_step = self.hour_hold_step or self.month_hold_step or 3, } + self:mergeLayoutInHorizontal(month_hour_widget) day_min_widget = NumberPickerWidget:new{ show_parent = self, value = self.is_date and self.day or self.min, @@ -131,6 +133,7 @@ function DateTimeWidget:layout() date_month_hour = month_hour_widget, date_year = year_widget, } + self:mergeLayoutInHorizontal(day_min_widget) local separator_space = TextBoxWidget:new{ text = self.is_date and "–" or ":", alignment = "center", @@ -221,6 +224,7 @@ function DateTimeWidget:layout() zero_sep = true, show_parent = self, } + self:mergeLayoutInVertical(ok_cancel_buttons) self.date_frame = FrameContainer:new{ radius = Size.radius.window, @@ -260,6 +264,7 @@ function DateTimeWidget:layout() self.date_frame, } } + self:refocusWidget() UIManager:setDirty(self, function() return "ui", self.date_frame.dimen end) diff --git a/frontend/ui/widget/dictquicklookup.lua b/frontend/ui/widget/dictquicklookup.lua index 14270628e..bfd633509 100644 --- a/frontend/ui/widget/dictquicklookup.lua +++ b/frontend/ui/widget/dictquicklookup.lua @@ -1227,6 +1227,7 @@ function DictQuickLookup:lookupInputWord(hint) { { text = _("Cancel"), + id = "close", callback = function() self:closeInputDialog() end, diff --git a/frontend/ui/widget/doublespinwidget.lua b/frontend/ui/widget/doublespinwidget.lua index 0624aa18a..7c7e491f2 100644 --- a/frontend/ui/widget/doublespinwidget.lua +++ b/frontend/ui/widget/doublespinwidget.lua @@ -66,7 +66,6 @@ function DoubleSpinWidget:init() end if Device:hasKeys() then self.key_events.Close = { {Device.input.group.Back}, doc = "close doublespin widget" } - self.key_events.Press = { {"Press"}, doc = "press button" } end if Device:isTouchDevice() then self.ges_events = { @@ -229,7 +228,6 @@ function DoubleSpinWidget:update(numberpicker_left_value, numberpicker_right_val buttons = buttons, zero_sep = true, show_parent = self, - auto_focus_first_button = false, } self:mergeLayoutInVertical(button_table) @@ -269,7 +267,7 @@ function DoubleSpinWidget:update(numberpicker_left_value, numberpicker_right_val }, self.movable, } - self:focusTopLeftWidget() + self:refocusWidget() UIManager:setDirty(self, function() return "ui", self.widget_frame.dimen end) @@ -313,8 +311,4 @@ function DoubleSpinWidget:onClose() return true end -function DoubleSpinWidget:onPress() - return self:sendTapEventToFocusedWidget() -end - return DoubleSpinWidget diff --git a/frontend/ui/widget/focusmanager.lua b/frontend/ui/widget/focusmanager.lua index 56a0a5967..66bd063ae 100644 --- a/frontend/ui/widget/focusmanager.lua +++ b/frontend/ui/widget/focusmanager.lua @@ -1,8 +1,10 @@ +local bit = require("bit") local Device = require("device") local Event = require("ui/event") local InputContainer = require("ui/widget/container/inputcontainer") local logger = require("logger") local UIManager = require("ui/uimanager") +local util = require("util") --[[ Wrapper Widget that manages focus for a whole dialog @@ -33,20 +35,153 @@ local FocusManager = InputContainer:new{ function FocusManager:init() if not self.selected then self.selected = { x = 1, y = 1 } + else + self.selected = self.selected -- make sure current FocusManager has its own selected field end - if Device:hasDPad() then - self.key_events = { - -- these will all generate the same event, just with different arguments - FocusUp = { {"Up"}, doc = "move focus up", event = "FocusMove", args = {0, -1} }, - FocusDown = { {"Down"}, doc = "move focus down", event = "FocusMove", args = {0, 1} }, - FocusLeft = { {"Left"}, doc = "move focus left", event = "FocusMove", args = {-1, 0} }, - FocusRight = { {"Right"}, doc = "move focus right", event = "FocusMove", args = {1, 0} }, - } - if Device:hasFewKeys() then - self.key_events.FocusLeft = nil + local event_keys = {} + -- these will all generate the same event, just with different arguments + table.insert(event_keys, {"FocusUp", { {"Up"}, doc = "move focus up", event = "FocusMove", args = {0, -1} } }) + table.insert(event_keys, {"FocusRight", { {"Right"}, doc = "move focus right", event = "FocusMove", args = {1, 0} } }) + table.insert(event_keys, {"FocusDown", { {"Down"}, doc = "move focus down", event = "FocusMove", args = {0, 1} } }) + table.insert(event_keys, {"Press", { {"Press"}, doc = "tap the widget", event="Press" }}) + local FEW_KEYS_END_INDEX = #event_keys -- Few keys device: only setup up, down, right and press + + table.insert(event_keys, {"FocusLeft", { {"Left"}, doc = "move focus left", event = "FocusMove", args = {-1, 0} } }) + local NORMAL_KEYS_END_INDEX = #event_keys + + -- Advanced Feature: following event handlers can be enabled via settings.reader.lua + -- Key combinations (Sym, Alt+Up, Tab, Shift+Tab and so on) are not used but shown as examples here + table.insert(event_keys, {"Hold", { {"Sym", "AA"}, doc = "tap and hold the widget", event="Hold" } }) + -- half rows/columns move, it is helpful for slow device like Kindle DX to move quickly + table.insert(event_keys, {"HalfFocusUp", { {"Alt", "Up"}, doc = "move focus half columns up", event = "FocusHalfMove", args = {"up"} } }) + table.insert(event_keys, {"HalfFocusRight", { {"Alt", "Right"}, doc = "move focus half rows right", event = "FocusHalfMove", args = {"right"} } }) + table.insert(event_keys, {"HalfFocusDown", { {"Alt", "Down"}, doc = "move focus half columns down", event = "FocusHalfMove", args = {"down"} } }) + table.insert(event_keys, {"HalfFocusLeft", { {"Alt", "Left"}, doc = "move focus half rows left", event = "FocusHalfMove", args = {"left"} } }) + -- for PC navigation behavior support + table.insert(event_keys, {"FocusNext", { {"Tab"}, doc = "move focus to next widget", event="FocusNext"} }) + table.insert(event_keys, {"FocusPrevious", { {"Shift", "Tab"}, doc = "move focus to previous widget", event="FocusPrevious"} }) + + self.key_events = {} + self.builtin_key_events = {} + self.extra_key_events = {} + for i = 1, FEW_KEYS_END_INDEX do + local key_name = event_keys[i][1] + self.key_events[key_name] = event_keys[i][2] + self.builtin_key_events[key_name] = event_keys[i][2] + end + if not Device:hasFewKeys() then + for i = FEW_KEYS_END_INDEX+1, NORMAL_KEYS_END_INDEX do + local key_name = event_keys[i][1] + self.key_events[key_name] = event_keys[i][2] + self.builtin_key_events[key_name] = event_keys[i][2] + end + local focus_manager_setting = G_reader_settings:child("focus_manager") + -- Enable advanced feature, like Hold, FocusNext, FocusPrevious + -- Can also add extra arrow keys like using A, W, D, S for Left, Up, Right, Down + local alternative_keymaps = focus_manager_setting:readSetting("alternative_keymaps") + if type(alternative_keymaps) == "table" then + for i = 1, #event_keys do + local key_name = event_keys[i][1] + local alternative_keymap = alternative_keymaps[key_name] + if alternative_keymap then + local handler_defition = util.tableDeepCopy(event_keys[i][2]) + handler_defition[1] = alternative_keymap -- replace sample key combinations + local new_event_key = "Alternative" .. key_name + self.key_events[new_event_key] = handler_defition + self.extra_key_events[new_event_key] = handler_defition + end + end + end + end + end +end + +function FocusManager:isAlternativeKey(key) + for _, seq in pairs(self.extra_key_events) do + for _, oneseq in ipairs(seq) do + if key:match(oneseq) then + return true + end end end + return false +end + +function FocusManager:onFocusHalfMove(args) + if not self.layout then + return false + end + local direction = unpack(args) + local x, y = self.selected.x, self.selected.y + local row = self.layout[self.selected.y] + local dx, dy = 0, 0 + if direction == "up" then + dy = - math.floor(#self.layout / 2) + if dy == 0 then + dy = -1 + elseif dy + y <= 0 then + dy = -y + 1 -- first row + end + elseif direction == "down" then + dy = math.floor(#self.layout / 2) + if dy == 0 then + dy = 1 + elseif dy + y > #self.layout then + dy = #self.layout - y -- last row + end + elseif direction == "left" then + dx = - math.floor(#row / 2) + if dx == 0 then + dx = -1 + elseif dx + x <= 0 then + dx = -x + 1 -- first column + end + elseif direction == "right" then + dx = math.floor(#row / 2) + if dx == 0 then + dx = 1 + elseif dx + x > #row then + dx = #row - y -- last column + end + end + return self:onFocusMove({dx, dy}) +end + +function FocusManager:onPress() + return self:sendTapEventToFocusedWidget() +end + +function FocusManager:onHold() + return self:sendHoldEventToFocusedWidget() +end + +-- for tab key +function FocusManager:onFocusNext() + if not self.layout then + return false + end + local x, y = self.selected.x, self.selected.y + local row = self.layout[y] + local dx, dy = 1, 0 + if not row[x + dx] then -- beyond end of column, go to next row + dx, dy = 0, 1 + end + return self:onFocusMove({dx, dy}) +end + +-- for backtab key +function FocusManager:onFocusPrevious() + if not self.layout then + return false + end + local x, y = self.selected.x, self.selected.y + local row = self.layout[y] + local dx, dy = -1, 0 + if not row[x + dx] then -- beyond start of column, go to previous row + dx, dy = 0, -1 + end + return self:onFocusMove({dx, dy}) end function FocusManager:onFocusMove(args) @@ -101,11 +236,50 @@ function FocusManager:onFocusMove(args) return true end +-- constant, used to reset focus widget after layout recreation +-- not send Unfocus event +FocusManager.NOT_UNFOCUS = 1 +-- not need to send Focus event +FocusManager.NOT_FOCUS = 2 + +--- Move focus to specified widget +function FocusManager:moveFocusTo(x, y, focus_flags) + focus_flags = focus_flags or 0 + if not self.layout then + return false + end + local current_item = nil + if self.layout[self.selected.y] then + current_item = self.layout[self.selected.y][self.selected.x] + end + local target_item = nil + if self.layout[y] then + target_item = self.layout[y][x] + end + if target_item then + logger.dbg("Move focus position to: " .. y .. ", " .. x) + self.selected.x = x + self.selected.y = y + -- widget create new layout on update, previous may be removed from new layout. + if Device:hasDPad() then + if not bit.band(focus_flags, FocusManager.NOT_UNFOCUS) and current_item and current_item ~= target_item then + current_item:handleEvent(Event:new("Unfocus")) + end + if not bit.band(focus_flags, FocusManager.NOT_FOCUS) then + target_item:handleEvent(Event:new("Focus")) + UIManager:setDirty(self.show_parent or self, "fast") + end + end + return true + end + return false +end + --- Go to the last valid item directly left or right of the current item. -- @return false if none could be found function FocusManager:_wrapAroundX(dx) local x = self.selected.x - while self.layout[x - dx] do + while self.layout[self.selected.y][x - dx] do x = x - dx end if x ~= self.selected.x then @@ -168,7 +342,7 @@ function FocusManager:getFocusItem() return self.layout[self.selected.y][self.selected.x] end -function FocusManager:sendTapEventToFocusedWidget() +function FocusManager:_sendGestureEventToFocusedWidget(gesture) local focused_widget = self:getFocusItem() if focused_widget then -- center of widget position @@ -177,8 +351,9 @@ function FocusManager:sendTapEventToFocusedWidget() point.y = point.y + point.h / 2 point.w = 0 point.h = 0 + logger.dbg("FocusManager: Send " .. gesture .. " to " .. point.x .. ", " .. point.y) UIManager:sendEvent(Event:new("Gesture", { - ges = "tap", + ges = gesture, pos = point, })) return true @@ -186,14 +361,26 @@ function FocusManager:sendTapEventToFocusedWidget() return false end -function FocusManager:mergeLayoutInVertical(child) +function FocusManager:sendTapEventToFocusedWidget() + return self:_sendGestureEventToFocusedWidget("tap") +end + +function FocusManager:sendHoldEventToFocusedWidget() + return self:_sendGestureEventToFocusedWidget("hold") +end + +function FocusManager:mergeLayoutInVertical(child, pos) if not child.layout then return end + if not pos then + pos = #self.layout + 1 -- end of row + end for _, row in ipairs(child.layout) do - table.insert(self.layout, row) + table.insert(self.layout, pos, row) + pos = pos + 1 end - child:disableFocusManagement() + child:disableFocusManagement(self) end function FocusManager:mergeLayoutInHorizontal(child) @@ -210,18 +397,39 @@ function FocusManager:mergeLayoutInHorizontal(child) table.insert(prow, widget) end end - child:disableFocusManagement() + child:disableFocusManagement(self) end -function FocusManager:disableFocusManagement() +function FocusManager:disableFocusManagement(parent) + self._parent = parent + -- unfocus current widget in current child container + -- parent container will call refocusWidget to focus another one + local row = self.layout[self.selected.y] + if row and row[self.selected.x] then + row[self.selected.x]:handleEvent(Event:new("Unfocus")) + end self.layout = nil -- turn off focus feature end ---- Container call this method after init to let first widget render in focus style -function FocusManager:focusTopLeftWidget() - if Device:hasDPad() then - -- trigger selected widget in focused style - self:onFocusMove({0, 0}) +-- constant for refocusWidget method to ease code reading +FocusManager.RENDER_IN_NEXT_TICK = true + +--- Container calls this method to re-set focus widget style +--- Some container regenerate layout on update and lose focus style +function FocusManager:refocusWidget(nextTick) + if not self._parent then + if not nextTick then + self:moveFocusTo(self.selected.x, self.selected.y) + else + -- sometimes refocusWidget called in widget's action callback + -- widget may force repaint after callback, like Button with vsync = true + -- then focus style will be lost, set focus style to next tick to make sure focus style painted + UIManager:nextTick(function() + self:moveFocusTo(self.selected.x, self.selected.y) + end) + end + else + self._parent:refocusWidget(nextTick) end end diff --git a/frontend/ui/widget/frontlightwidget.lua b/frontend/ui/widget/frontlightwidget.lua index c47d9382e..802fc6c5a 100644 --- a/frontend/ui/widget/frontlightwidget.lua +++ b/frontend/ui/widget/frontlightwidget.lua @@ -2,13 +2,13 @@ local Blitbuffer = require("ffi/blitbuffer") local Button = require("ui/widget/button") local CenterContainer = require("ui/widget/container/centercontainer") local Device = require("device") +local FocusManager = require("ui/widget/focusmanager") local FrameContainer = require("ui/widget/container/framecontainer") local Geom = require("ui/geometry") local GestureRange = require("ui/gesturerange") local Font = require("ui/font") local HorizontalGroup = require("ui/widget/horizontalgroup") local HorizontalSpan = require("ui/widget/horizontalspan") -local InputContainer = require("ui/widget/container/inputcontainer") local Math = require("optmath") local NaturalLight = require("ui/widget/naturallightwidget") local ProgressWidget = require("ui/widget/progresswidget") @@ -23,7 +23,7 @@ local WidgetContainer = require("ui/widget/container/widgetcontainer") local _ = require("gettext") local Screen = Device.screen -local FrontLightWidget = InputContainer:new{ +local FrontLightWidget = FocusManager:new{ width = nil, height = nil, -- This should stay active during natural light configuration @@ -78,9 +78,7 @@ function FrontLightWidget:init() show_parent = self, } if Device:hasKeys() then - self.key_events = { - Close = { {Device.input.group.Back}, doc = "close frontlight" } - } + self.key_events.Close = { {Device.input.group.Back}, doc = "close frontlight" } end if Device:isTouchDevice() then self.ges_events = { @@ -221,6 +219,7 @@ function FrontLightWidget:setProgress(num, step, num_warmth) item_level, button_plus, } + self.layout[1] = {button_minus, button_plus} local button_table_down = HorizontalGroup:new{ align = "center", button_min, @@ -229,6 +228,7 @@ function FrontLightWidget:setProgress(num, step, num_warmth) empty_space, button_max, } + self.layout[2] = {button_min, button_toggle, button_max} if self.natural_light then -- Only insert 'brightness' caption if we also add 'warmth' -- widgets below. @@ -259,12 +259,13 @@ function FrontLightWidget:setProgress(num, step, num_warmth) end, } table.insert(vertical_group, self.configure_button) + self.layout[5] = {self.configure_button} end end table.insert(self.fl_container, vertical_group) -- Reset container height to what it actually contains self.fl_container.dimen.h = vertical_group:getSize().h - + self:refocusWidget() UIManager:setDirty(self, function() return "ui", self.light_frame.dimen end) @@ -370,12 +371,14 @@ function FrontLightWidget:addWarmthWidgets(num_warmth, step, vertical_group) item_level, button_plus, } + self.layout[3] = {button_minus, button_plus} local button_table_down = HorizontalGroup:new{ align = "center", button_min, empty_space, button_max, } + self.layout[4] = {button_min, button_max} table.insert(vertical_group, text_warmth) table.insert(button_group_up, button_table_up) @@ -388,7 +391,6 @@ function FrontLightWidget:addWarmthWidgets(num_warmth, step, vertical_group) table.insert(vertical_group, padding_span) table.insert(vertical_group, button_group_down) table.insert(vertical_group, padding_span) - end function FrontLightWidget:setFrontLightIntensity(num) @@ -409,6 +411,7 @@ function FrontLightWidget:setFrontLightIntensity(num) end function FrontLightWidget:update() + self.layout = {} local title_bar = TitleBar:new{ title = _("Frontlight"), width = self.width, diff --git a/frontend/ui/widget/inputdialog.lua b/frontend/ui/widget/inputdialog.lua index 35de6285b..ec6b8f019 100644 --- a/frontend/ui/widget/inputdialog.lua +++ b/frontend/ui/widget/inputdialog.lua @@ -22,6 +22,7 @@ Example: { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(sample_input) end, @@ -99,12 +100,12 @@ local ButtonTable = require("ui/widget/buttontable") local CenterContainer = require("ui/widget/container/centercontainer") local CheckButton = require("ui/widget/checkbutton") local Device = require("device") +local FocusManager = require("ui/widget/focusmanager") local Font = require("ui/font") local FrameContainer = require("ui/widget/container/framecontainer") local Geom = require("ui/geometry") local GestureRange = require("ui/gesturerange") local InfoMessage = require("ui/widget/infomessage") -local InputContainer = require("ui/widget/container/inputcontainer") local InputText = require("ui/widget/inputtext") local MovableContainer = require("ui/widget/container/movablecontainer") local MultiConfirmBox = require("ui/widget/multiconfirmbox") @@ -118,7 +119,7 @@ local Screen = Device.screen local T = require("ffi/util").template local _ = require("gettext") -local InputDialog = InputContainer:new{ +local InputDialog = FocusManager:new{ is_always_active = true, title = "", input = "", @@ -197,6 +198,7 @@ local InputDialog = InputContainer:new{ } function InputDialog:init() + self.layout = {{}} self.screen_width = Screen:getWidth() self.screen_height = Screen:getHeight() if self.fullscreen then @@ -370,13 +372,12 @@ function InputDialog:init() top_line_num = self._top_line_num, charpos = self._charpos, } + table.insert(self.layout[1], self._input_widget) if self.allow_newline then -- remove any enter_callback self._input_widget.enter_callback = nil end - if Device:hasDPad() then - --little hack to piggyback on the layout of the button_table to handle the new InputText - table.insert(self.button_table.layout, 1, {self._input_widget}) - end + self:mergeLayoutInVertical(self.button_table) + self:refocusWidget() -- Complementary setup for some of our added buttons if self.save_callback then local save_button = self.button_table:getButtonById("save") @@ -440,6 +441,9 @@ function InputDialog:init() }, } end + if Device:hasKeys() then + self.key_events.CloseDialog = { {Device.input.group.Back}, doc = "close dialog" } + end if self._added_widgets then for _, widget in ipairs(self._added_widgets) do self:addWidget(widget, true) @@ -448,6 +452,7 @@ function InputDialog:init() end function InputDialog:addWidget(widget, re_init) + table.insert(self.layout, #self.layout, {widget}) if not re_init then -- backup widget for re-init widget = CenterContainer:new{ dimen = Geom:new{ @@ -554,6 +559,15 @@ function InputDialog:onKeyboardHeightChanged() UIManager:setDirty("all", "flashui") end +function InputDialog:onCloseDialog() + local close_button = self.button_table:getButtonById("close") + if close_button and close_button.enabled then + close_button.callback() + return true + end + return false +end + function InputDialog:onClose() -- Remember current view & position in case of re-init self._top_line_num = self._input_widget.top_line_num @@ -769,6 +783,7 @@ function InputDialog:_addScrollButtons(nav_bar) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(input_dialog) self.keyboard_hidden = keyboard_hidden_state @@ -856,6 +871,7 @@ function InputDialog:_addScrollButtons(nav_bar) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(input_dialog) self.keyboard_hidden = keyboard_hidden_state diff --git a/frontend/ui/widget/inputtext.lua b/frontend/ui/widget/inputtext.lua index b55a4dc0e..c1269516a 100644 --- a/frontend/ui/widget/inputtext.lua +++ b/frontend/ui/widget/inputtext.lua @@ -1,6 +1,7 @@ local Blitbuffer = require("ffi/blitbuffer") local CheckButton = require("ui/widget/checkbutton") local Device = require("device") +local FocusManager = require("ui/widget/focusmanager") local FrameContainer = require("ui/widget/container/framecontainer") local Font = require("ui/font") local Geom = require("ui/geometry") @@ -19,6 +20,7 @@ local _ = require("gettext") local Screen = Device.screen local Keyboard +local FocusManagerInstance = FocusManager:new{} local InputText = InputContainer:new{ text = "", @@ -274,7 +276,6 @@ if Device:isTouchDevice() or Device:hasDPad() then -- used for taking a screenshot) return false end - end if Device:hasDPad() then if not InputText.initEventListener then @@ -283,14 +284,17 @@ if Device:isTouchDevice() or Device:hasDPad() then function InputText:onFocus() -- Event called by the focusmanager - self.key_events.ShowKeyboard = { {"Press"}, doc = "show keyboard" } + if self.parent.onSwitchFocus then + self.parent:onSwitchFocus(self) + else + self:onShowKeyboard() + end self:focus() return true end function InputText:onUnfocus() -- Event called by the focusmanager - self.key_events = {} self:unfocus() return true end @@ -542,7 +546,11 @@ end -- is shown. Mostly likely to be in the emulator, but could be Android + BT -- keyboard, or a "coder's keyboard" Android input method. function InputText:onKeyPress(key) - + -- only handle key on focused status, otherwise there are more than one InputText + -- the first one always handle key pressed + if not self.focused then + return false + end local handled = true if not key["Ctrl"] and not key["Shift"] and not key["Alt"] then @@ -567,6 +575,10 @@ function InputText:onKeyPress(key) self:addChars("\n") elseif key["Tab"] then self:addChars(" ") + elseif key["Back"] then + if self.focused then + self:unfocus() + end else handled = false end @@ -581,15 +593,46 @@ function InputText:onKeyPress(key) else handled = false end - + if not handled and Device:hasDPad() then + -- FocusManager may turn on alternative key maps. + -- These key map maybe single text keys. + -- It will cause unexpected focus move instead of enter text to InputText + local is_alternative_key = FocusManagerInstance:isAlternativeKey(key) + if not is_alternative_key and Device:isSDL() then + -- SDL already insert char via TextInput event + -- Stop event propagate to FocusManager + return true + end + -- 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 not key["Shift"] then + key_code = string.lower(key_code) + end + for modifier, flag in pairs(key.modifiers) do + if modifier ~= "Shift" and flag then -- Other modifier: not a single char insert + return true + end + end + self:addChars(key_code) + return true + end + if is_alternative_key then + return true -- Stop event propagate to FocusManager to void focus move + end + end return handled end -- Handle text coming directly as text from the Device layer (eg. soft keyboard -- or via SDL's keyboard mapping). function InputText:onTextInput(text) - self:addChars(text) - return true + -- for more than one InputText, let the focused one add chars + if self.focused then + self:addChars(text) + return true + end + return false end function InputText:onShowKeyboard(ignore_first_hold_release) diff --git a/frontend/ui/widget/keyboardlayoutdialog.lua b/frontend/ui/widget/keyboardlayoutdialog.lua index e35e79943..cf92c5d98 100644 --- a/frontend/ui/widget/keyboardlayoutdialog.lua +++ b/frontend/ui/widget/keyboardlayoutdialog.lua @@ -6,9 +6,9 @@ local Blitbuffer = require("ffi/blitbuffer") local ButtonTable = require("ui/widget/buttontable") local CenterContainer = require("ui/widget/container/centercontainer") local FFIUtil = require("ffi/util") +local FocusManager = require("ui/widget/focusmanager") local FrameContainer = require("ui/widget/container/framecontainer") local Geom = require("ui/geometry") -local InputContainer = require("ui/widget/container/inputcontainer") local Language = require("ui/language") local MovableContainer = require("ui/widget/container/movablecontainer") local RadioButtonTable = require("ui/widget/radiobuttontable") @@ -20,9 +20,10 @@ local VerticalGroup = require("ui/widget/verticalgroup") local VerticalSpan = require("ui/widget/verticalspan") local util = require("util") local _ = require("gettext") -local Screen = require("device").screen +local Device = require("device") +local Screen = Device.screen -local KeyboardLayoutDialog = InputContainer:new{ +local KeyboardLayoutDialog = FocusManager:new{ is_always_active = true, modal = true, stop_events_propagation = true, @@ -31,6 +32,7 @@ local KeyboardLayoutDialog = InputContainer:new{ } function KeyboardLayoutDialog:init() + self.layout = {} self.width = self.width or math.floor(math.min(Screen:getWidth(), Screen:getHeight()) * 0.8) self.title_bar = TitleBar:new{ width = self.width, @@ -68,6 +70,7 @@ function KeyboardLayoutDialog:init() table.insert(buttons, { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(self.parent.keyboard_layout_dialog) end, @@ -93,6 +96,7 @@ function KeyboardLayoutDialog:init() parent = self, show_parent = self, } + self:mergeLayoutInVertical(self.radio_button_table) -- Buttons Table self.button_table = ButtonTable:new{ @@ -101,6 +105,7 @@ function KeyboardLayoutDialog:init() zero_sep = true, show_parent = self, } + self:mergeLayoutInVertical(self.button_table) local max_radio_button_container_height = math.floor(Screen:getHeight()*0.9 - self.title_bar:getHeight() @@ -163,6 +168,9 @@ function KeyboardLayoutDialog:init() ignore_if_over = "height", self.movable, } + if Device:hasKeys() then + self.key_events.CloseDialog = { {Device.input.group.Back}, doc = "close dialog" } + end end function KeyboardLayoutDialog:onShow() @@ -177,4 +185,13 @@ function KeyboardLayoutDialog:onCloseWidget() end) end +function KeyboardLayoutDialog:onCloseDialog() + local close_button = self.button_table:getButtonById("close") + if close_button and close_button.enabled then + close_button.callback() + return true + end + return false +end + return KeyboardLayoutDialog diff --git a/frontend/ui/widget/keyvaluepage.lua b/frontend/ui/widget/keyvaluepage.lua index 3255819ab..abdbc2bbb 100644 --- a/frontend/ui/widget/keyvaluepage.lua +++ b/frontend/ui/widget/keyvaluepage.lua @@ -25,6 +25,7 @@ local BottomContainer = require("ui/widget/container/bottomcontainer") local Button = require("ui/widget/button") local Device = require("device") local Font = require("ui/font") +local FocusManager = require("ui/widget/focusmanager") local FrameContainer = require("ui/widget/container/framecontainer") local Geom = require("ui/geometry") local GestureRange = require("ui/gesturerange") @@ -65,7 +66,7 @@ local KeyValueItem = InputContainer:new{ } function KeyValueItem:init() - self.dimen = Geom:new{w = self.width, h = self.height} + self.dimen = Geom:new{ w = self.width, h = self.height } -- self.value may contain some control characters (\n \t...) that would -- be rendered as a square. Replace them with a shorter and nicer '|'. @@ -162,33 +163,35 @@ function KeyValueItem:init() -- For debugging positioning: -- value_widget = FrameContainer:new{ padding=0, margin=0, bordersize=1, value_widget } - if Device:isTouchDevice() then - self.ges_events.Tap = { - GestureRange:new{ - ges = "tap", - range = self.dimen, - } + self.ges_events.Tap = { + GestureRange:new{ + ges = "tap", + range = self.dimen, } - self.ges_events.Hold = { - GestureRange:new{ - ges = "hold", - range = self.dimen, - } + } + self.ges_events.Hold = { + GestureRange:new{ + ges = "hold", + range = self.dimen, } - end - + } + local content_dimen = self.dimen:copy() + content_dimen.h = content_dimen.h - Size.border.thin * 2 -- reduced by 2 border sizes + content_dimen.w = content_dimen.w - Size.border.thin * 2 -- reduced by 2 border sizes self[1] = FrameContainer:new{ padding = frame_padding, padding_top = 0, padding_bottom = 0, bordersize = 0, + focusable = true, + focus_border_size = Size.border.thin, background = Blitbuffer.COLOR_WHITE, HorizontalGroup:new{ - dimen = self.dimen:copy(), + dimen = content_dimen, LeftContainer:new{ dimen = { w = key_w, - h = self.height + h = content_dimen.h }, key_widget, }, @@ -198,7 +201,7 @@ function KeyValueItem:init() LeftContainer:new{ dimen = { w = value_w, - h = self.height + h = content_dimen.h }, value_widget, } @@ -270,7 +273,7 @@ function KeyValueItem:onShowKeyValue() end -local KeyValuePage = InputContainer:new{ +local KeyValuePage = FocusManager:new{ title = "", width = nil, height = nil, @@ -293,11 +296,9 @@ function KeyValuePage:init() end if Device:hasKeys() then - self.key_events = { - Close = { {Input.group.Back}, doc = "close page" }, - NextPage = {{Input.group.PgFwd}, doc = "next page"}, - PrevPage = {{Input.group.PgBack}, doc = "prev page"}, - } + self.key_events.Close = {{Input.group.Back}, doc = "close page" } + self.key_events.NextPage = {{Input.group.PgFwd}, doc = "next page"} + self.key_events.PrevPage = {{Input.group.PgBack}, doc = "prev page"} end if Device:isTouchDevice() then self.ges_events.Swipe = { @@ -535,6 +536,7 @@ end -- make sure self.item_margin and self.item_height are set before calling this function KeyValuePage:_populateItems() + self.layout = {} self.page_info:resetLayout() self.return_button:resetLayout() self.main_content:clear() @@ -545,7 +547,7 @@ function KeyValuePage:_populateItems() if entry == nil then break end if type(entry) == "table" then - table.insert(self.main_content, KeyValueItem:new{ + local kv_item = KeyValueItem:new{ height = self.item_height, width = self.item_width, font_size = self.items_font_size, @@ -561,7 +563,9 @@ function KeyValuePage:_populateItems() kv_pairs_idx = kv_pairs_idx, kv_page = self, show_parent = self, - }) + } + table.insert(self.main_content, kv_item) + table.insert(self.layout, { kv_item }) if entry.separator then table.insert(self.main_content, LineWidget:new{ background = Blitbuffer.COLOR_LIGHT_GRAY, @@ -615,7 +619,7 @@ function KeyValuePage:_populateItems() self.page_info_first_chev:hide() self.page_info_last_chev:hide() end - + self:moveFocusTo(1, 1, FocusManager.NOT_UNFOCUS) UIManager:setDirty(self, function() return "ui", self.dimen end) diff --git a/frontend/ui/widget/menu.lua b/frontend/ui/widget/menu.lua index 1cef93b4c..c93a0d114 100644 --- a/frontend/ui/widget/menu.lua +++ b/frontend/ui/widget/menu.lua @@ -122,24 +122,22 @@ function MenuItem:init() self.detail = self.text -- we need this table per-instance, so we declare it here - if Device:isTouchDevice() then - self.ges_events = { - TapSelect = { - GestureRange:new{ - ges = "tap", - range = self.dimen, - }, - doc = "Select Menu Item", + self.ges_events = { + TapSelect = { + GestureRange:new{ + ges = "tap", + range = self.dimen, }, - HoldSelect = { - GestureRange:new{ - ges = "hold", - range = self.dimen, - }, - doc = "Hold Menu Item", + doc = "Select Menu Item", + }, + HoldSelect = { + GestureRange:new{ + ges = "hold", + range = self.dimen, }, - } - end + doc = "Hold Menu Item", + }, + } local max_item_height = self.dimen.h - 2 * self.linesize @@ -889,37 +887,35 @@ function Menu:init() ------------------------------------------ -- start to set up input event callback -- ------------------------------------------ - if Device:isTouchDevice() then - -- watch for outer region if it's a self contained widget - if self.is_popout then - self.ges_events.TapCloseAllMenus = { - GestureRange:new{ - ges = "tap", - range = Geom:new{ - x = 0, y = 0, - w = Screen:getWidth(), - h = Screen:getHeight(), - } + -- watch for outer region if it's a self contained widget + if self.is_popout then + self.ges_events.TapCloseAllMenus = { + GestureRange:new{ + ges = "tap", + range = Geom:new{ + x = 0, y = 0, + w = Screen:getWidth(), + h = Screen:getHeight(), } } - end - -- delegate swipe gesture to GestureManager in filemanager - if not self.filemanager then - self.ges_events.Swipe = { - GestureRange:new{ - ges = "swipe", - range = self.dimen, - } + } + end + -- delegate swipe gesture to GestureManager in filemanager + if not self.filemanager then + self.ges_events.Swipe = { + GestureRange:new{ + ges = "swipe", + range = self.dimen, } - self.ges_events.MultiSwipe = { - GestureRange:new{ - ges = "multiswipe", - range = self.dimen, - } + } + self.ges_events.MultiSwipe = { + GestureRange:new{ + ges = "multiswipe", + range = self.dimen, } - end - self.ges_events.Close = self.on_close_ges + } end + self.ges_events.Close = self.on_close_ges if not Device:hasKeyboard() then -- remove menu item shortcut for K4 @@ -947,9 +943,6 @@ function Menu:init() if self.is_enable_shortcut then self.key_events.SelectByShortCut = { {self.item_shortcuts} } end - self.key_events.Select = { - {"Press"}, doc = "select current menu item" - } self.key_events.Right = { {"Right"}, doc = "hold menu item" } @@ -997,7 +990,7 @@ function Menu:updatePageInfo(select_number) if self.item_group[1] then if Device:hasDPad() then -- reset focus manager accordingly - self.selected = { x = 1, y = select_number } + self:moveFocusTo(1, select_number) end -- update page information self.page_info_text:setText(FFIUtil.template(_("Page %1 of %2"), self.page, self.page_num)) @@ -1299,20 +1292,8 @@ function Menu:onGotoPage(page) return true end -function Menu:onSelect() - local item = self.item_table[(self.page-1)*self.perpage+self.selected.y] - if item then - self:onMenuSelect(item) - end - return true -end - function Menu:onRight() - local item = self.item_table[(self.page-1)*self.perpage+self.selected.y] - if item then - self:onMenuHold(item) - end - return true + return self:sendHoldEventToFocusedWidget() end function Menu:onClose() diff --git a/frontend/ui/widget/multiconfirmbox.lua b/frontend/ui/widget/multiconfirmbox.lua index b0967c0ec..348d629b2 100644 --- a/frontend/ui/widget/multiconfirmbox.lua +++ b/frontend/ui/widget/multiconfirmbox.lua @@ -34,7 +34,6 @@ local TextBoxWidget = require("ui/widget/textboxwidget") local UIManager = require("ui/uimanager") local VerticalGroup = require("ui/widget/verticalgroup") local VerticalSpan = require("ui/widget/verticalspan") -local logger = require("logger") local _ = require("gettext") local Screen = require("device").screen @@ -72,9 +71,7 @@ function MultiConfirmBox:init() } end if Device:hasKeys() then - self.key_events = { - Close = { {Device.input.group.Back}, doc = "cancel" } - } + self.key_events.Close = { {Device.input.group.Back}, doc = "cancel" } end end local content = HorizontalGroup:new{ @@ -173,17 +170,4 @@ function MultiConfirmBox:onTapClose(arg, ges) return false end -function MultiConfirmBox:onSelect() - logger.dbg("selected:", self.selected.x) - if self.selected.x == 1 then - self:choice1_callback() - elseif self.selected.x == 2 then - self:choice2_callback() - elseif self.selected.x == 0 then - self:cancle_callback() - end - UIManager:close(self) - return true -end - return MultiConfirmBox diff --git a/frontend/ui/widget/multiinputdialog.lua b/frontend/ui/widget/multiinputdialog.lua index b6c68fa0e..2fb90de33 100644 --- a/frontend/ui/widget/multiinputdialog.lua +++ b/frontend/ui/widget/multiinputdialog.lua @@ -32,6 +32,7 @@ Example for input of two strings and a number: { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(sample_input) end @@ -132,10 +133,7 @@ function MultiInputDialog:init() auto_para_direction = field.auto_para_direction or self.auto_para_direction, alignment_strict = field.alignment_strict or self.alignment_strict, } - if Device:hasDPad() then - -- little hack to piggyback on the layout of the button_table to handle the new InputText - table.insert(self.button_table.layout, #self.button_table.layout, {input_field[k]}) - end + table.insert(self.layout, #self.layout, {input_field[k]}) if field.description then input_description[k] = FrameContainer:new{ padding = self.description_padding, @@ -164,10 +162,6 @@ function MultiInputDialog:init() }) end - if Device:hasDPad() then - -- remove the not needed hack in inputdialog - table.remove(self.button_table.layout, 1) - end -- Add same vertical space after than before InputText table.insert(VerticalGroupData,CenterContainer:new{ dimen = Geom:new{ diff --git a/frontend/ui/widget/networksetting.lua b/frontend/ui/widget/networksetting.lua index 6b7c821c0..3d7202c9a 100644 --- a/frontend/ui/widget/networksetting.lua +++ b/frontend/ui/widget/networksetting.lua @@ -294,6 +294,7 @@ function NetworkItem:onEditNetwork() { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(password_input) end, @@ -336,6 +337,7 @@ function NetworkItem:onAddNetwork() { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(password_input) end, diff --git a/frontend/ui/widget/numberpickerwidget.lua b/frontend/ui/widget/numberpickerwidget.lua index 9ebd937c3..1fed812eb 100644 --- a/frontend/ui/widget/numberpickerwidget.lua +++ b/frontend/ui/widget/numberpickerwidget.lua @@ -136,6 +136,7 @@ function NumberPickerWidget:init() { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(input_dialog) end, @@ -186,6 +187,9 @@ function NumberPickerWidget:init() show_parent = self.show_parent, callback = callback_input, } + if callback_input then + table.insert(self.layout, 2, {self.text_value}) + end local widget_spinner = VerticalGroup:new{ align = "center", @@ -210,9 +214,7 @@ function NumberPickerWidget:init() } self.dimen = self.frame:getSize() self[1] = self.frame - if Device:hasDPad() then - self.key_events.Press = { {"Press"}, doc = "press button" } - end + self:refocusWidget() UIManager:setDirty(self.show_parent, function() return "ui", self.dimen end) @@ -229,6 +231,7 @@ function NumberPickerWidget:update() self.text_value:setText(tostring(self.formatted_value), self.width) + self:refocusWidget() UIManager:setDirty(self.show_parent, function() return "ui", self.dimen end) @@ -289,8 +292,4 @@ function NumberPickerWidget:getValue() return self.value, self.value_index end -function NumberPickerWidget:onPress() - return self:sendTapEventToFocusedWidget() -end - return NumberPickerWidget diff --git a/frontend/ui/widget/openwithdialog.lua b/frontend/ui/widget/openwithdialog.lua index 8ceb5fcb2..996655b94 100644 --- a/frontend/ui/widget/openwithdialog.lua +++ b/frontend/ui/widget/openwithdialog.lua @@ -42,6 +42,7 @@ function OpenWithDialog:init() end end } + self:mergeLayoutInVertical(self.radio_button_table, #self.layout) -- before bottom buttons self._input_widget = self.radio_button_table local vertical_span = VerticalSpan:new{ @@ -85,11 +86,13 @@ function OpenWithDialog:init() text = _("Always use this engine for this file"), parent = self, } + table.insert(self.layout, #self.layout, {self._check_file_button}) -- before bottom buttons self:addWidget(self._check_file_button) self._check_global_button = self._check_global_button or CheckButton:new{ text = _("Always use this engine for file type"), parent = self, } + table.insert(self.layout, #self.layout, {self._check_global_button}) -- before bottom buttons self:addWidget(self._check_global_button) self.dialog_frame = FrameContainer:new{ @@ -110,6 +113,7 @@ function OpenWithDialog:init() }, self.movable, } + self:refocusWidget() end function OpenWithDialog:onCloseWidget() diff --git a/frontend/ui/widget/radiobuttontable.lua b/frontend/ui/widget/radiobuttontable.lua index 13d9fc7e6..a4d1bad65 100644 --- a/frontend/ui/widget/radiobuttontable.lua +++ b/frontend/ui/widget/radiobuttontable.lua @@ -31,7 +31,6 @@ local RadioButtonTable = FocusManager:new{ } function RadioButtonTable:init() - self.selected = { x = 1, y = 1 } self.radio_buttons_layout = {} self.container = VerticalGroup:new{ width = self.width } table.insert(self, self.container) @@ -120,7 +119,7 @@ function RadioButtonTable:init() if Device:hasDPad() or Device:hasKeyboard() then self.layout = self.radio_buttons_layout - self.key_events.SelectByKeyPress = { {{"Press"}} } + self:refocusWidget() else self.key_events = {} -- deregister all key press event listeners end @@ -146,15 +145,6 @@ function RadioButtonTable:addHorizontalSep(vspan_before, add_line, vspan_after, end end -function RadioButtonTable:onSelectByKeyPress() - local item = self:getFocusItem() - if item then - item.callback() - return true - end - return false -end - function RadioButtonTable:_checkButton(button) -- nothing to do if button.checked then return end diff --git a/frontend/ui/widget/skimtowidget.lua b/frontend/ui/widget/skimtowidget.lua index eedccc106..f928b94ee 100644 --- a/frontend/ui/widget/skimtowidget.lua +++ b/frontend/ui/widget/skimtowidget.lua @@ -45,7 +45,6 @@ function SkimToWidget:init() end self.buttons_layout = {} - self.selected = { x = 1, y = 2 } local frame_width = math.floor(math.min(screen_width, screen_height) * 0.95) local frame_border_size = Size.border.window @@ -296,8 +295,7 @@ function SkimToWidget:init() { button_minus_ten, button_minus, self.current_page_text, button_plus, button_plus_ten }, } self.layout = self.buttons_layout - self.layout[2][1]:onFocus() - self.key_events.SelectByKeyPress = { { "Press" }, doc = "select focused item" } + self:moveFocusTo(1, 2) end if Device:hasKeyboard() then self.key_events.QKey = { { "Q" }, event = "FirstRowKeyPress", args = 0 } @@ -323,6 +321,7 @@ function SkimToWidget:update() self.progress_bar.percentage = self.curr_page / self.page_count self.current_page_text:setText(self.current_page_text:text_func(), self.current_page_text.width) self.button_bookmark_toggle:setText(self.button_bookmark_toggle:text_func(), self.button_bookmark_toggle.width) + self:refocusWidget(FocusManager.RENDER_IN_NEXT_TICK) end function SkimToWidget:addOriginToLocationStack(add_current) @@ -378,15 +377,6 @@ function SkimToWidget:onAnyKeyPressed() return true end -function SkimToWidget:onSelectByKeyPress() - local item = self:getFocusItem() - if item then - item.callback() - return true - end - return false -end - function SkimToWidget:onFirstRowKeyPress(percent) local page = Math.round(percent * self.page_count) self:addOriginToLocationStack() diff --git a/frontend/ui/widget/spinwidget.lua b/frontend/ui/widget/spinwidget.lua index aecd73c30..c3fae130e 100644 --- a/frontend/ui/widget/spinwidget.lua +++ b/frontend/ui/widget/spinwidget.lua @@ -65,7 +65,6 @@ function SpinWidget:init() end if Device:hasKeys() then self.key_events.Close = { {Device.input.group.Back}, doc = "close spin widget" } - self.key_events.Press = { {"Press"}, doc = "press button" } end if Device:isTouchDevice() then self.ges_events = { @@ -196,7 +195,6 @@ function SpinWidget:update(numberpicker_value, numberpicker_value_index) buttons = buttons, zero_sep = true, show_parent = self, - auto_focus_first_button = false, } self:mergeLayoutInVertical(ok_cancel_buttons) local vgroup = VerticalGroup:new{ @@ -236,7 +234,7 @@ function SpinWidget:update(numberpicker_value, numberpicker_value_index) }, self.movable, } - self:focusTopLeftWidget() + self:refocusWidget() UIManager:setDirty(self, function() return "ui", self.spin_frame.dimen end) @@ -280,8 +278,4 @@ function SpinWidget:onClose() return true end -function SpinWidget:onPress() - return self:sendTapEventToFocusedWidget() -end - return SpinWidget diff --git a/frontend/ui/widget/touchmenu.lua b/frontend/ui/widget/touchmenu.lua index 061db8b11..ca2ae99a6 100644 --- a/frontend/ui/widget/touchmenu.lua +++ b/frontend/ui/widget/touchmenu.lua @@ -7,7 +7,6 @@ local Button = require("ui/widget/button") local CenterContainer = require("ui/widget/container/centercontainer") local CheckMark = require("ui/widget/checkmark") local Device = require("device") -local Event = require("ui/event") local FocusManager = require("ui/widget/focusmanager") local Font = require("ui/font") local FrameContainer = require("ui/widget/container/framecontainer") @@ -490,7 +489,6 @@ function TouchMenu:init() end self.key_events.NextPage = { {Input.group.PgFwd}, doc = "next page" } self.key_events.PrevPage = { {Input.group.PgBack}, doc = "previous page" } - self.key_events.Press = { {"Press"}, doc = "chose selected item" } local icons = {} for _, v in ipairs(self.tab_item_table) do @@ -713,7 +711,7 @@ function TouchMenu:updateItems() -- recalculate dimen based on new layout self.dimen.w = self.width self.dimen.h = self.item_group:getSize().h + self.bordersize*2 + self.padding -- (no padding at top) - self.selected = { x = self.cur_tab, y = 1 } -- reset the position of the focusmanager + self:moveFocusTo(self.cur_tab, 1, FocusManager.NOT_FOCUS) -- reset the position of the focusmanager -- NOTE: We use a slightly ugly hack to detect a brand new menu vs. a tab switch, -- in order to optionally flash on initial menu popup... @@ -942,13 +940,4 @@ function TouchMenu:onBack() self:backToUpperMenu() end -function TouchMenu:onPress() - local item = self:getFocusItem() - if item then - item:handleEvent(Event:new("TapSelect")) - return true - end - return false -end - return TouchMenu diff --git a/frontend/ui/widget/virtualkeyboard.lua b/frontend/ui/widget/virtualkeyboard.lua index 20c877890..59fe4d79e 100644 --- a/frontend/ui/widget/virtualkeyboard.lua +++ b/frontend/ui/widget/virtualkeyboard.lua @@ -2,7 +2,6 @@ local Blitbuffer = require("ffi/blitbuffer") local BottomContainer = require("ui/widget/container/bottomcontainer") local CenterContainer = require("ui/widget/container/centercontainer") local Device = require("device") -local Event = require("ui/event") local FocusManager = require("ui/widget/focusmanager") local Font = require("ui/font") local FrameContainer = require("ui/widget/container/framecontainer") @@ -245,40 +244,38 @@ function VirtualKey:init() h = self.height, } --self.dimen = self[1]:getSize() - if Device:isTouchDevice() then - self.ges_events = { - TapSelect = { - GestureRange:new{ - ges = "tap", - range = self.dimen, - }, + self.ges_events = { + TapSelect = { + GestureRange:new{ + ges = "tap", + range = self.dimen, }, - HoldSelect = { - GestureRange:new{ - ges = "hold", - range = self.dimen, - }, + }, + HoldSelect = { + GestureRange:new{ + ges = "hold", + range = self.dimen, }, - HoldReleaseKey = { - GestureRange:new{ - ges = "hold_release", - range = self.dimen, - }, + }, + HoldReleaseKey = { + GestureRange:new{ + ges = "hold_release", + range = self.dimen, }, - PanReleaseKey = { - GestureRange:new{ - ges = "pan_release", - range = self.dimen, - }, + }, + PanReleaseKey = { + GestureRange:new{ + ges = "pan_release", + range = self.dimen, }, - SwipeKey = { - GestureRange:new{ - ges = "swipe", - range = self.dimen, - }, + }, + SwipeKey = { + GestureRange:new{ + ges = "swipe", + range = self.dimen, }, - } - end + }, + } if (self.keyboard.shiftmode_keys[self.label] ~= nil 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 @@ -479,15 +476,6 @@ function VirtualKeyPopup:onCloseWidget() end) end -function VirtualKeyPopup:onPressKey() - local item = self:getFocusItem() - if item then - item:handleEvent(Event:new("TapSelect")) - return true - end - return false -end - function VirtualKeyPopup:init() local parent_key = self.parent_key local key_chars = parent_key.key_chars @@ -680,9 +668,6 @@ function VirtualKeyPopup:init() self.tap_interval_override = G_reader_settings:readSetting("ges_tap_interval_on_keyboard", 0) self.tap_interval_override = TimeVal:new{ usec = self.tap_interval_override } - if Device:hasDPad() then - self.key_events.PressKey = { {"Press"}, doc = "select key" } - end if Device:hasKeys() then self.key_events.Close = { {Device.input.group.Back}, doc = "close keyboard" } end @@ -803,16 +788,13 @@ function VirtualKeyboard:init() self:initLayer(self.keyboard_layer) self.tap_interval_override = G_reader_settings:readSetting("ges_tap_interval_on_keyboard", 0) self.tap_interval_override = TimeVal:new{ usec = self.tap_interval_override } - if Device:hasDPad() then - self.key_events.PressKey = { {"Press"}, doc = "select key" } - end if Device:hasKeys() then self.key_events.Close = { {"Back"}, doc = "close keyboard" } end if keyboard.wrapInputBox then self.uwrap_func = keyboard.wrapInputBox(self.inputbox) or self.uwrap_func end - if Device:hasDPad() and Device:hasKeyboard() and Device:isTouchDevice() then + 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 @@ -820,14 +802,39 @@ function VirtualKeyboard:init() -- 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 - self.key_events.FocusLeft = nil - self.key_events.FocusRight = nil - self.key_events.FocusUp = nil - self.key_events.FocusDown = nil - self.key_events.PressKey = nil -- added above + 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") @@ -856,16 +863,12 @@ end function VirtualKeyboard:onClose() UIManager:close(self) - return true -end - -function VirtualKeyboard:onPressKey() - local item = self:getFocusItem() - if item then - item:handleEvent(Event:new("TapSelect")) - return true + 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 false + return true end function VirtualKeyboard:_refresh(want_flash, fullscreen) diff --git a/plugins/SSH.koplugin/main.lua b/plugins/SSH.koplugin/main.lua index d5eb32933..6bb5130aa 100644 --- a/plugins/SSH.koplugin/main.lua +++ b/plugins/SSH.koplugin/main.lua @@ -124,6 +124,7 @@ function SSH:show_port_dialog(touchmenu_instance) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(self.port_dialog) end, diff --git a/plugins/autowarmth.koplugin/main.lua b/plugins/autowarmth.koplugin/main.lua index 7820f89e5..e896d7e5e 100644 --- a/plugins/autowarmth.koplugin/main.lua +++ b/plugins/autowarmth.koplugin/main.lua @@ -451,6 +451,7 @@ function AutoWarmth:getLocationMenu() { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(location_name_dialog) end, diff --git a/plugins/calibre.koplugin/main.lua b/plugins/calibre.koplugin/main.lua index 8a30349bd..0912ed060 100644 --- a/plugins/calibre.koplugin/main.lua +++ b/plugins/calibre.koplugin/main.lua @@ -307,6 +307,7 @@ function Calibre:getWirelessMenuTable() { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(url_dialog) end, diff --git a/plugins/calibre.koplugin/search.lua b/plugins/calibre.koplugin/search.lua index 62ca723c1..ff9805814 100644 --- a/plugins/calibre.koplugin/search.lua +++ b/plugins/calibre.koplugin/search.lua @@ -207,6 +207,7 @@ function CalibreSearch:ShowSearch() { { text = _("Cancel"), + id = "close", enabled = true, callback = function() self.search_dialog:onClose() diff --git a/plugins/calibre.koplugin/wireless.lua b/plugins/calibre.koplugin/wireless.lua index cb3461ce7..eadd09f9c 100644 --- a/plugins/calibre.koplugin/wireless.lua +++ b/plugins/calibre.koplugin/wireless.lua @@ -422,6 +422,7 @@ function CalibreWireless:setPassword() buttons = {{ { text = _("Cancel"), + id = "close", callback = function() UIManager:close(password_dialog) end, diff --git a/plugins/coverbrowser.koplugin/listmenu.lua b/plugins/coverbrowser.koplugin/listmenu.lua index e59d75098..b124e01a7 100644 --- a/plugins/coverbrowser.koplugin/listmenu.lua +++ b/plugins/coverbrowser.koplugin/listmenu.lua @@ -135,24 +135,22 @@ function ListMenuItem:init() self.detail = self.text -- we need this table per-instance, so we declare it here - if Device:isTouchDevice() then - self.ges_events = { - TapSelect = { - GestureRange:new{ - ges = "tap", - range = self.dimen, - }, - doc = "Select Menu Item", + self.ges_events = { + TapSelect = { + GestureRange:new{ + ges = "tap", + range = self.dimen, }, - HoldSelect = { - GestureRange:new{ - ges = "hold", - range = self.dimen, - }, - doc = "Hold Menu Item", + doc = "Select Menu Item", + }, + HoldSelect = { + GestureRange:new{ + ges = "hold", + range = self.dimen, }, - } - end + doc = "Hold Menu Item", + }, + } -- We now build the minimal widget container that won't change after udpate() diff --git a/plugins/coverimage.koplugin/main.lua b/plugins/coverimage.koplugin/main.lua index 425e65607..f75a8d64c 100644 --- a/plugins/coverimage.koplugin/main.lua +++ b/plugins/coverimage.koplugin/main.lua @@ -350,6 +350,7 @@ function CoverImage:choosePathFile(touchmenu_instance, key, folder_only, new_fil buttons = {{ { text = _("Cancel"), + id = "close", callback = function() UIManager:close(file_input) end, diff --git a/plugins/exporter.koplugin/main.lua b/plugins/exporter.koplugin/main.lua index 232fd8452..bf7b93a5c 100644 --- a/plugins/exporter.koplugin/main.lua +++ b/plugins/exporter.koplugin/main.lua @@ -137,6 +137,7 @@ function Exporter:addToMainMenu(menu_items) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(url_dialog) end @@ -176,6 +177,7 @@ function Exporter:addToMainMenu(menu_items) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(auth_dialog) end @@ -246,6 +248,7 @@ For more information, please visit https://github.com/koreader/koreader/wiki/Hig { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(auth_dialog) end diff --git a/plugins/gestures.koplugin/main.lua b/plugins/gestures.koplugin/main.lua index 4411eb421..8bc9e49ce 100644 --- a/plugins/gestures.koplugin/main.lua +++ b/plugins/gestures.koplugin/main.lua @@ -329,6 +329,7 @@ function Gestures:multiswipeRecorder(touchmenu_instance) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(multiswipe_recorder) end, diff --git a/plugins/newsdownloader.koplugin/main.lua b/plugins/newsdownloader.koplugin/main.lua index 029affbac..6db0ed90f 100644 --- a/plugins/newsdownloader.koplugin/main.lua +++ b/plugins/newsdownloader.koplugin/main.lua @@ -597,6 +597,7 @@ function NewsDownloader:editFeedAttribute(id, key, value) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(input_dialog) UIManager:show(kv) diff --git a/plugins/opds.koplugin/opdsbrowser.lua b/plugins/opds.koplugin/opdsbrowser.lua index 34b2dc598..56d39a901 100644 --- a/plugins/opds.koplugin/opdsbrowser.lua +++ b/plugins/opds.koplugin/opdsbrowser.lua @@ -154,6 +154,7 @@ function OPDSBrowser:addNewCatalog() { { text = _("Cancel"), + id = "close", callback = function() self.add_server_dialog:onClose() UIManager:close(self.add_server_dialog) @@ -204,6 +205,7 @@ function OPDSBrowser:editCalibreServer() { { text = _("Cancel"), + id = "close", callback = function() self.add_server_dialog:onClose() UIManager:close(self.add_server_dialog) @@ -740,6 +742,7 @@ function OPDSBrowser:showDownloads(item) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(input_dialog) end, @@ -818,6 +821,7 @@ function OPDSBrowser:browseSearchable(browse_url, username, password) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(self.search_server_dialog) end, @@ -912,6 +916,7 @@ function OPDSBrowser:editOPDSServer(item) { { text = _("Cancel"), + id = "close", callback = function() self.edit_server_dialog:onClose() UIManager:close(self.edit_server_dialog) diff --git a/plugins/perceptionexpander.koplugin/main.lua b/plugins/perceptionexpander.koplugin/main.lua index 791c9be8e..9e91378e6 100644 --- a/plugins/perceptionexpander.koplugin/main.lua +++ b/plugins/perceptionexpander.koplugin/main.lua @@ -136,6 +136,7 @@ function PerceptionExpander:showSettingsDialog() { { text = _("Cancel"), + id = "close", callback = function() self.settings_dialog:onClose() UIManager:close(self.settings_dialog) diff --git a/plugins/profiles.koplugin/main.lua b/plugins/profiles.koplugin/main.lua index 67e242b55..b3708152d 100644 --- a/plugins/profiles.koplugin/main.lua +++ b/plugins/profiles.koplugin/main.lua @@ -64,6 +64,7 @@ function Profiles:getSubMenuItems() buttons = {{ { text = _("Cancel"), + id = "close", callback = function() UIManager:close(name_input) end, diff --git a/plugins/statistics.koplugin/calendarview.lua b/plugins/statistics.koplugin/calendarview.lua index 363444389..2273bcbe7 100644 --- a/plugins/statistics.koplugin/calendarview.lua +++ b/plugins/statistics.koplugin/calendarview.lua @@ -4,6 +4,7 @@ local BottomContainer = require("ui/widget/container/bottomcontainer") local Button = require("ui/widget/button") local CenterContainer = require("ui/widget/container/centercontainer") local Device = require("device") +local FocusManager = require("ui/widget/focusmanager") local Font = require("ui/font") local FrameContainer = require("ui/widget/container/framecontainer") local Geom = require("ui/geometry") @@ -94,20 +95,18 @@ function CalendarDay:init() if self.filler then return end - if self.callback and Device:isTouchDevice() then - self.ges_events.Tap = { - GestureRange:new{ - ges = "tap", - range = self.dimen, - } + self.ges_events.Tap = { + GestureRange:new{ + ges = "tap", + range = self.dimen, } - self.ges_events.Hold = { - GestureRange:new{ - ges = "hold", - range = self.dimen, - } + } + self.ges_events.Hold = { + GestureRange:new{ + ges = "hold", + range = self.dimen, } - end + } self.daynum_w = TextWidget:new{ text = " " .. tostring(self.daynum), @@ -144,6 +143,8 @@ function CalendarDay:init() bordersize = self.border, width = self.width, height = self.height, + focusable = true, + focus_border_color = Blitbuffer.COLOR_GRAY, OverlapGroup:new{ dimen = { w = inner_w }, self.daynum_w, @@ -366,7 +367,7 @@ end -- Fetched from db, cached as local as it might be expensive local MIN_MONTH = nil -local CalendarView = InputContainer:new{ +local CalendarView = FocusManager:new{ reader_statistics = nil, monthTranslation = nil, shortDayOfWeekTranslation = nil, @@ -395,11 +396,9 @@ function CalendarView:init() end if Device:hasKeys() then - self.key_events = { - Close = { {Input.group.Back}, doc = "close page" }, - NextMonth = {{Input.group.PgFwd}, doc = "next page"}, - PrevMonth = {{Input.group.PgBack}, doc = "prev page"}, - } + self.key_events.Close = {{Input.group.Back}, doc = "close page" } + self.key_events.NextMonth = {{Input.group.PgFwd}, doc = "next page"} + self.key_events.PrevMonth = {{Input.group.PgBack}, doc = "prev page"} end if Device:isTouchDevice() then self.ges_events.Swipe = { @@ -620,6 +619,7 @@ function CalendarView:init() end function CalendarView:_populateItems() + self.layout = {} self.page_info:resetLayout() self.main_content:clear() @@ -650,6 +650,7 @@ function CalendarView:_populateItems() local cur_date = os.date("*t", cur_ts) local this_month = cur_date.month local cur_week + local layout_row while true do cur_date = os.date("*t", cur_ts) if cur_date.month ~= this_month then @@ -672,6 +673,8 @@ function CalendarView:_populateItems() font_size = self.span_font_size, show_parent = self, } + layout_row = {} + table.insert(self.layout, layout_row) table.insert(self.weeks, cur_week) table.insert(self.main_content, cur_week) if cur_date.wday ~= self.start_day_of_week then @@ -702,7 +705,7 @@ function CalendarView:_populateItems() hour = 0, }) local is_future = day_s > today_s - cur_week:addDay(CalendarDay:new{ + local calendar_day = CalendarDay:new{ show_histo = self.show_hourly_histogram, histo_height = self.span_height, font_face = self.font_face, @@ -733,13 +736,15 @@ function CalendarView:_populateItems() callback_return = function() end, -- to just have that return button shown }) end - }) + } + cur_week:addDay(calendar_day) + table.insert(layout_row, calendar_day) cur_ts = cur_ts + 86400 -- add one day end for _, week in ipairs(self.weeks) do week:update() end - + self:moveFocusTo(1, 1, FocusManager.NOT_UNFOCUS) UIManager:setDirty(self, function() return "ui", self.dimen end) diff --git a/plugins/texteditor.koplugin/main.lua b/plugins/texteditor.koplugin/main.lua index c4a743395..55edc36ed 100644 --- a/plugins/texteditor.koplugin/main.lua +++ b/plugins/texteditor.koplugin/main.lua @@ -350,6 +350,7 @@ Do you want to proceed?]]), buttons = {{ { text = _("Cancel"), + id = "close", callback = function() UIManager:close(file_input) end, diff --git a/plugins/wallabag.koplugin/main.lua b/plugins/wallabag.koplugin/main.lua index 51db315f0..f4642948d 100644 --- a/plugins/wallabag.koplugin/main.lua +++ b/plugins/wallabag.koplugin/main.lua @@ -902,6 +902,7 @@ function Wallabag:setFilterTag(touchmenu_instance) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(self.tag_dialog) end, @@ -933,6 +934,7 @@ function Wallabag:setIgnoreTags(touchmenu_instance) { { text = _("Cancel"), + id = "close", callback = function() UIManager:close(self.ignore_tags_dialog) end, @@ -1000,6 +1002,7 @@ Restart KOReader after editing the config file.]]), BD.dirpath(DataStorage:getSe { { text = _("Cancel"), + id = "close", callback = function() self.settings_dialog:onClose() UIManager:close(self.settings_dialog) @@ -1048,6 +1051,7 @@ function Wallabag:editClientSettings() { { text = _("Cancel"), + id = "close", callback = function() self.client_settings_dialog:onClose() UIManager:close(self.client_settings_dialog) diff --git a/spec/unit/focusmanager_spec.lua b/spec/unit/focusmanager_spec.lua index 5e6f14f6e..a5318b712 100644 --- a/spec/unit/focusmanager_spec.lua +++ b/spec/unit/focusmanager_spec.lua @@ -1,14 +1,24 @@ describe("FocusManager module", function() local FocusManager - local layout - local Up,Down,Left,Right - Up = function(self) self:onFocusMove({0, -1}) end - Down = function(self) self:onFocusMove({0, 1}) end - Left = function(self) self:onFocusMove({-1, 0}) end - Right = function(self) self:onFocusMove({1, 0}) end + local layout, big_layout + local Key + local Input + local Up = function(self) self:onFocusMove({0, -1}) end + local Down = function(self) self:onFocusMove({0, 1}) end + local Left = function(self) self:onFocusMove({-1, 0}) end + local Right = function(self) self:onFocusMove({1, 0}) end + local Next = function(self) self:onFocusNext() end + local Previous = function(self) self:onFocusPrevious() end + local HalfMoveUp = function(self) self:onFocusHalfMove({"up"}) end + local HalfMoveDown = function(self) self:onFocusHalfMove({"down"}) end + local HalfMoveLeft = function(self) self:onFocusHalfMove({"left"}) end + local HalfMoveRight = function(self) self:onFocusHalfMove({"right"}) end + local MoveTo = function(self, x, y) self:moveFocusTo(x, y) end setup(function() require("commonrequire") FocusManager = require("ui/widget/focusmanager") + Key = require("device/key") + Input = require("device/input") local Widget = require("ui/widget/textwidget") local w = Widget:new{} layout= { @@ -16,7 +26,13 @@ describe("FocusManager module", function() {nil,w,nil}, {nil,w,nil}, } - + big_layout = { + {w, w, w, w, w}, + {w, w, w, w, w}, + {w, w, w, w, w}, + {w, w, w, w, w}, + {w, w, w, w, w}, + } end) it("should go right", function() local focusmanager = FocusManager:new{} @@ -88,4 +104,129 @@ describe("FocusManager module", function() Right(focusmanager) assert.are.same({y = 2,x = 2}, focusmanager.selected) end) + it("should move next right", function() + local focusmanager = FocusManager:new{} + focusmanager.layout = layout + focusmanager.selected = {y = 1,x = 2} + Next(focusmanager) + assert.are.same({y = 1,x = 3}, focusmanager.selected) + end) + it("should move next row at end of row", function() + local focusmanager = FocusManager:new{} + focusmanager.layout = layout + focusmanager.selected = {y = 1,x = 3} + Next(focusmanager) + assert.are.same({y = 2,x = 2}, focusmanager.selected) + end) + it("should move next left", function() + local focusmanager = FocusManager:new{} + focusmanager.layout = layout + focusmanager.selected = {y = 1,x = 2} + Previous(focusmanager) + assert.are.same({y = 1,x = 1}, focusmanager.selected) + end) + it("should move previous at start of row", function() + local focusmanager = FocusManager:new{} + focusmanager.layout = layout + focusmanager.selected = {y = 3,x = 2} + Previous(focusmanager) + assert.are.same({y = 2,x = 2}, focusmanager.selected) + end) + it("should move half rows or columns", function() + local focusmanager = FocusManager:new{} + focusmanager.layout = big_layout + focusmanager.selected = {x = 1, y = 1} + HalfMoveRight(focusmanager) + assert.are.same({y = 1,x = 3}, focusmanager.selected) + HalfMoveDown(focusmanager) + assert.are.same({y = 3,x = 3}, focusmanager.selected) + HalfMoveLeft(focusmanager) + assert.are.same({y = 3,x = 1}, focusmanager.selected) + HalfMoveUp(focusmanager) + assert.are.same({y = 1,x = 1}, focusmanager.selected) + end) + it("should move to specified position", function() + local focusmanager = FocusManager:new{} + focusmanager.layout = big_layout + focusmanager.selected = {x = 1, y = 1} + MoveTo(focusmanager, 3, 4) + assert.are.same({y = 4,x = 3}, focusmanager.selected) + end) + it("should set layout to nil", function() + local focusmanager = FocusManager:new{} + focusmanager.layout = layout + focusmanager:disableFocusManagement() + assert.is_nil(focusmanager.layout) + end) + it("should merge into rows", function() + local w = layout[1][1] + local fm1 = FocusManager:new{} + fm1.layout = { + {w, w, w} + } + local fm2 = FocusManager:new{} + fm2.layout = { + {w, w}, + } + fm1:mergeLayoutInVertical(fm2) + local expected = { + {w, w, w}, + {w, w} + } + assert.are.same(expected, fm1.layout) + end) + it("should merge into rows at specified position", function() + local w = layout[1][1] + local fm1 = FocusManager:new{} + fm1.layout = { + {w, w, w}, + {w, w, w}, + } + local fm2 = FocusManager:new{} + fm2.layout = { + {w, w}, + } + fm1:mergeLayoutInVertical(fm2, 2) + local expected = { + {w, w, w}, + {w, w}, + {w, w, w}, + } + assert.are.same(expected, fm1.layout) + end) + it("should merge into columns", function() + local w = layout[1][1] + local fm1 = FocusManager:new{} + fm1.layout = { + {w}, + {w}, + } + local fm2 = FocusManager:new{} + fm2.layout = { + {w, w}, + {w}, + } + fm1:mergeLayoutInHorizontal(fm2, 2) + local expected = { + {w, w, w}, + {w, w}, + } + assert.are.same(expected, fm1.layout) + end) + it("alternative key", function() + local focusmanager = FocusManager:new{} + focusmanager.extra_key_events = { + Hold = { {"Sym", "AA"}, doc = "tap and hold the widget", event="Hold" }, + HalfFocusUp = { {"Alt", "Up"}, doc = "move focus half columns up", event = "FocusHalfMove", args = {"up"} }, + } + local m = Input.modifiers; + m.Sym = true + assert.is_true(focusmanager:isAlternativeKey(Key:new("AA", m))) + m.Sym = false + m.Alt = true + assert.is_true(focusmanager:isAlternativeKey(Key:new("Up", m))) + m.Alt = false + assert.is_false(focusmanager:isAlternativeKey(Key:new("AA", m))) + assert.is_false(focusmanager:isAlternativeKey(Key:new("Up", m))) + end) end)