mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Long story short: the LeaveStandby event is sent via `tickAfterNext`, so if we tear down the plugin right after calling it (in this case, that means that the very input event that wakes the device up from suspend is one that kills ReaderUI or FileManager), what's in UIManager's task queue isn't the actual function, but the anonymous nextTick wrapper constructed by `tickAfterNext` (c.f.,
https://github.com/koreader/koreader/issues/9112#issuecomment-1133999385).
Tweak `UIManager:tickAfterNext` to return a reference to said wrapper, so that we can store it and unschedule that one, too, in `AutoSuspend:onCloseWidget`.
Fix #9112 (many thanks to [@boredhominid](https://github.com/boredhominid) for his help in finding a repro for this ;)).
Re: #8638, as the extra debugging facilities (i.e., ebb81b9845) added during testing might help pinpoint the root issue for that one, too.
Also includes a minor simplification to `UIManager:_checkTasks`, and various other task queue related codepaths (e.g., `WakeupMgr`) ;).
216 lines
6.9 KiB
Lua
216 lines
6.9 KiB
Lua
--[[--
|
|
Widget that shows a confirmation alert with a message and Cancel/OK buttons.
|
|
|
|
Example:
|
|
|
|
UIManager:show(ConfirmBox:new{
|
|
text = _("Save the document?"),
|
|
ok_text = _("Save"), -- ok_text defaults to _("OK")
|
|
ok_callback = function()
|
|
-- save document
|
|
end,
|
|
})
|
|
|
|
It is strongly recommended to set a custom `ok_text` describing the action to be
|
|
confirmed, as demonstrated in the example above. No ok_text should be specified
|
|
if the resulting phrase would be longer than three words.
|
|
|
|
]]
|
|
|
|
local Blitbuffer = require("ffi/blitbuffer")
|
|
local ButtonTable = require("ui/widget/buttontable")
|
|
local CenterContainer = require("ui/widget/container/centercontainer")
|
|
local Device = require("device")
|
|
local Font = require("ui/font")
|
|
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 IconWidget = require("ui/widget/iconwidget")
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
local MovableContainer = require("ui/widget/container/movablecontainer")
|
|
local Size = require("ui/size")
|
|
local TextBoxWidget = require("ui/widget/textboxwidget")
|
|
local UIManager = require("ui/uimanager")
|
|
local VerticalGroup = require("ui/widget/verticalgroup")
|
|
local VerticalSpan = require("ui/widget/verticalspan")
|
|
local _ = require("gettext")
|
|
local Input = Device.input
|
|
local Screen = Device.screen
|
|
|
|
local ConfirmBox = InputContainer:new{
|
|
modal = true,
|
|
keep_dialog_open = false,
|
|
text = _("no text"),
|
|
face = Font:getFace("infofont"),
|
|
ok_text = _("OK"),
|
|
cancel_text = _("Cancel"),
|
|
ok_callback = function() end,
|
|
cancel_callback = function() end,
|
|
other_buttons = nil,
|
|
other_buttons_first = false, -- set to true to place other buttons above Cancel-OK row
|
|
margin = Size.margin.default,
|
|
padding = Size.padding.default,
|
|
dismissable = true, -- set to false if any button callback is required
|
|
flush_events_on_show = false, -- set to true when it might be displayed after
|
|
-- some processing, to avoid accidental dismissal
|
|
}
|
|
|
|
function ConfirmBox:init()
|
|
if self.dismissable then
|
|
if Device:isTouchDevice() then
|
|
self.ges_events.TapClose = {
|
|
GestureRange:new{
|
|
ges = "tap",
|
|
range = Geom:new{
|
|
x = 0, y = 0,
|
|
w = Screen:getWidth(),
|
|
h = Screen:getHeight(),
|
|
}
|
|
}
|
|
}
|
|
end
|
|
if Device:hasKeys() then
|
|
self.key_events.Close = { {Device.input.group.Back}, doc = "cancel" }
|
|
end
|
|
end
|
|
local text_widget = TextBoxWidget:new{
|
|
text = self.text,
|
|
face = self.face,
|
|
width = math.floor(math.min(Screen:getWidth(), Screen:getHeight()) * 2/3),
|
|
}
|
|
local content = HorizontalGroup:new{
|
|
align = "center",
|
|
IconWidget:new{
|
|
icon = "notice-question",
|
|
},
|
|
HorizontalSpan:new{ width = Size.span.horizontal_default },
|
|
text_widget,
|
|
}
|
|
|
|
local buttons = {{
|
|
text = self.cancel_text,
|
|
callback = function()
|
|
self.cancel_callback()
|
|
UIManager:close(self)
|
|
end,
|
|
}, {
|
|
text = self.ok_text,
|
|
callback = function()
|
|
self.ok_callback()
|
|
if self.keep_dialog_open then return end
|
|
UIManager:close(self)
|
|
end,
|
|
},}
|
|
buttons = { buttons } -- single row
|
|
|
|
if self.other_buttons ~= nil then
|
|
-- additional rows
|
|
local rownum = self.other_buttons_first and 0 or 1
|
|
for i, buttons_row in ipairs(self.other_buttons) do
|
|
local row = {}
|
|
table.insert(buttons, rownum + i, row)
|
|
for ___, button in ipairs(buttons_row) do
|
|
table.insert(row, {
|
|
text = button.text,
|
|
callback = function()
|
|
if button.callback ~= nil then
|
|
button.callback()
|
|
end
|
|
if self.keep_dialog_open then return end
|
|
UIManager:close(self)
|
|
end,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
|
|
local button_table = ButtonTable:new{
|
|
width = content:getSize().w,
|
|
button_font_face = "cfont",
|
|
button_font_size = 20,
|
|
buttons = buttons,
|
|
zero_sep = true,
|
|
show_parent = self,
|
|
}
|
|
|
|
local frame = FrameContainer:new{
|
|
background = Blitbuffer.COLOR_WHITE,
|
|
margin = self.margin,
|
|
radius = Size.radius.window,
|
|
padding = self.padding,
|
|
padding_bottom = 0, -- no padding below buttontable
|
|
VerticalGroup:new{
|
|
align = "left",
|
|
content,
|
|
-- Add same vertical space after than before content
|
|
VerticalSpan:new{ width = self.margin + self.padding },
|
|
button_table,
|
|
}
|
|
}
|
|
self.movable = MovableContainer:new{
|
|
frame,
|
|
}
|
|
self[1] = CenterContainer:new{
|
|
dimen = Screen:getSize(),
|
|
self.movable,
|
|
}
|
|
|
|
-- Reduce font size until widget fit screen height if needed
|
|
local cur_size = frame:getSize()
|
|
if cur_size and cur_size.h > 0.95 * Screen:getHeight() then
|
|
local orig_font = text_widget.face.orig_font
|
|
local orig_size = text_widget.face.orig_size
|
|
local real_size = text_widget.face.size
|
|
if orig_size > 10 then -- don't go too small
|
|
while true do
|
|
orig_size = orig_size - 1
|
|
self.face = Font:getFace(orig_font, orig_size)
|
|
-- scaleBySize() in Font:getFace() may give the same
|
|
-- real font size even if we decreased orig_size,
|
|
-- so check we really got a smaller real font size
|
|
if self.face.size < real_size then
|
|
break
|
|
end
|
|
end
|
|
-- re-init this widget
|
|
self:free()
|
|
self:init()
|
|
end
|
|
end
|
|
end
|
|
|
|
function ConfirmBox:onShow()
|
|
UIManager:setDirty(self, function()
|
|
return "ui", self[1][1].dimen
|
|
end)
|
|
if self.flush_events_on_show then
|
|
-- Discard queued and upcoming input events to avoid accidental dismissal
|
|
Input:inhibitInputUntil(true)
|
|
end
|
|
end
|
|
|
|
function ConfirmBox:onCloseWidget()
|
|
UIManager:setDirty(nil, function()
|
|
return "ui", self[1][1].dimen
|
|
end)
|
|
end
|
|
|
|
function ConfirmBox:onClose()
|
|
-- Call cancel_callback, parent may expect a choice
|
|
self.cancel_callback()
|
|
UIManager:close(self)
|
|
return true
|
|
end
|
|
|
|
function ConfirmBox:onTapClose(arg, ges)
|
|
if ges.pos:notIntersectWith(self[1][1].dimen) then
|
|
self:onClose()
|
|
end
|
|
-- Don't let it propagate to underlying widgets
|
|
return true
|
|
end
|
|
|
|
return ConfirmBox
|