2016-06-26 00:53:08 +00:00
|
|
|
--[[--
|
2021-12-01 11:37:18 +00:00
|
|
|
Widget that shows a confirmation alert with a message and Cancel/OK buttons.
|
2016-06-26 00:53:08 +00:00
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
UIManager:show(ConfirmBox:new{
|
|
|
|
text = _("Save the document?"),
|
|
|
|
ok_text = _("Save"), -- ok_text defaults to _("OK")
|
|
|
|
ok_callback = function()
|
|
|
|
-- save document
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
|
2017-04-04 13:31:13 +00:00
|
|
|
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.
|
|
|
|
|
2016-06-26 00:53:08 +00:00
|
|
|
]]
|
|
|
|
|
2017-04-29 08:38:09 +00:00
|
|
|
local Blitbuffer = require("ffi/blitbuffer")
|
|
|
|
local ButtonTable = require("ui/widget/buttontable")
|
2013-10-18 20:38:07 +00:00
|
|
|
local CenterContainer = require("ui/widget/container/centercontainer")
|
2017-04-29 08:38:09 +00:00
|
|
|
local Device = require("device")
|
|
|
|
local Font = require("ui/font")
|
2014-07-02 14:52:17 +00:00
|
|
|
local FrameContainer = require("ui/widget/container/framecontainer")
|
2017-04-29 08:38:09 +00:00
|
|
|
local Geom = require("ui/geometry")
|
|
|
|
local GestureRange = require("ui/gesturerange")
|
2014-07-02 14:52:17 +00:00
|
|
|
local HorizontalGroup = require("ui/widget/horizontalgroup")
|
2017-04-29 08:38:09 +00:00
|
|
|
local HorizontalSpan = require("ui/widget/horizontalspan")
|
2020-12-19 11:18:30 +00:00
|
|
|
local IconWidget = require("ui/widget/iconwidget")
|
2017-04-29 08:38:09 +00:00
|
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
2018-01-29 20:27:24 +00:00
|
|
|
local MovableContainer = require("ui/widget/container/movablecontainer")
|
2017-09-13 14:56:20 +00:00
|
|
|
local Size = require("ui/size")
|
2013-10-22 15:11:31 +00:00
|
|
|
local TextBoxWidget = require("ui/widget/textboxwidget")
|
|
|
|
local UIManager = require("ui/uimanager")
|
2017-04-29 08:38:09 +00:00
|
|
|
local VerticalGroup = require("ui/widget/verticalgroup")
|
2017-10-08 15:53:25 +00:00
|
|
|
local VerticalSpan = require("ui/widget/verticalspan")
|
2013-10-22 15:11:31 +00:00
|
|
|
local _ = require("gettext")
|
2022-05-23 11:52:52 +00:00
|
|
|
local Input = Device.input
|
2017-04-29 08:38:09 +00:00
|
|
|
local Screen = Device.screen
|
2012-06-10 15:52:09 +00:00
|
|
|
|
Clarify our OOP semantics across the codebase (#9586)
Basically:
* Use `extend` for class definitions
* Use `new` for object instantiations
That includes some minor code cleanups along the way:
* Updated `Widget`'s docs to make the semantics clearer.
* Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283)
* Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass).
* Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events.
* Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier.
* Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references.
* ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak).
* Terminal: Make sure the shell is killed on plugin teardown.
* InputText: Fix Home/End/Del physical keys to behave sensibly.
* InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...).
* OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of.
* ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed!
* Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
|
|
|
local ConfirmBox = InputContainer:extend{
|
2014-10-30 08:01:01 +00:00
|
|
|
modal = true,
|
2021-08-12 22:45:10 +00:00
|
|
|
keep_dialog_open = false,
|
2014-03-13 13:52:43 +00:00
|
|
|
text = _("no text"),
|
2017-04-29 08:38:09 +00:00
|
|
|
face = Font:getFace("infofont"),
|
2022-11-25 09:18:54 +00:00
|
|
|
icon = "notice-question",
|
2014-03-13 13:52:43 +00:00
|
|
|
ok_text = _("OK"),
|
|
|
|
cancel_text = _("Cancel"),
|
|
|
|
ok_callback = function() end,
|
|
|
|
cancel_callback = function() end,
|
2017-05-16 09:11:11 +00:00
|
|
|
other_buttons = nil,
|
2021-08-12 22:45:10 +00:00
|
|
|
other_buttons_first = false, -- set to true to place other buttons above Cancel-OK row
|
2024-01-16 17:06:15 +00:00
|
|
|
no_ok_button = false,
|
2017-09-13 14:56:20 +00:00
|
|
|
margin = Size.margin.default,
|
|
|
|
padding = Size.padding.default,
|
2017-10-08 15:53:25 +00:00
|
|
|
dismissable = true, -- set to false if any button callback is required
|
2021-01-01 13:34:55 +00:00
|
|
|
flush_events_on_show = false, -- set to true when it might be displayed after
|
|
|
|
-- some processing, to avoid accidental dismissal
|
2012-06-10 15:52:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function ConfirmBox:init()
|
2017-10-08 15:53:25 +00:00
|
|
|
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(),
|
|
|
|
}
|
2017-01-15 20:55:06 +00:00
|
|
|
}
|
|
|
|
}
|
2017-10-08 15:53:25 +00:00
|
|
|
end
|
|
|
|
if Device:hasKeys() then
|
2022-10-27 00:01:51 +00:00
|
|
|
self.key_events.Close = { { Device.input.group.Back } }
|
2017-10-08 15:53:25 +00:00
|
|
|
end
|
2017-01-15 20:55:06 +00:00
|
|
|
end
|
2023-05-02 05:25:34 +00:00
|
|
|
|
|
|
|
self.text_widget_width = math.floor(math.min(Screen:getWidth(), Screen:getHeight()) * 2/3)
|
2020-02-24 14:25:06 +00:00
|
|
|
local text_widget = TextBoxWidget:new{
|
|
|
|
text = self.text,
|
|
|
|
face = self.face,
|
2023-05-02 05:25:34 +00:00
|
|
|
width = self.text_widget_width,
|
|
|
|
}
|
|
|
|
self.text_group = VerticalGroup:new{
|
|
|
|
align = "left",
|
|
|
|
text_widget,
|
2020-02-24 14:25:06 +00:00
|
|
|
}
|
2023-05-02 05:25:34 +00:00
|
|
|
if self._added_widgets then
|
|
|
|
table.insert(self.text_group, VerticalSpan:new{ width = Size.padding.large })
|
|
|
|
for _, widget in ipairs(self._added_widgets) do
|
|
|
|
table.insert(self.text_group, widget)
|
|
|
|
end
|
|
|
|
end
|
2014-07-02 14:52:17 +00:00
|
|
|
local content = HorizontalGroup:new{
|
|
|
|
align = "center",
|
2020-12-19 11:18:30 +00:00
|
|
|
IconWidget:new{
|
2022-11-25 09:18:54 +00:00
|
|
|
icon = self.icon,
|
|
|
|
alpha = true,
|
2014-07-02 14:52:17 +00:00
|
|
|
},
|
2017-09-13 14:56:20 +00:00
|
|
|
HorizontalSpan:new{ width = Size.span.horizontal_default },
|
2023-05-02 05:25:34 +00:00
|
|
|
self.text_group,
|
2014-03-13 13:52:43 +00:00
|
|
|
}
|
2017-05-16 09:11:11 +00:00
|
|
|
|
2023-05-02 05:25:34 +00:00
|
|
|
local buttons = {{ -- single row
|
|
|
|
{
|
|
|
|
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,
|
|
|
|
},
|
|
|
|
}}
|
2024-01-16 17:06:15 +00:00
|
|
|
if self.no_ok_button then
|
|
|
|
table.remove(buttons[1], 2)
|
|
|
|
end
|
2017-05-16 09:11:11 +00:00
|
|
|
|
2023-05-02 05:25:34 +00:00
|
|
|
if self.other_buttons then -- additional rows
|
2021-08-12 22:45:10 +00:00
|
|
|
local rownum = self.other_buttons_first and 0 or 1
|
|
|
|
for i, buttons_row in ipairs(self.other_buttons) do
|
2020-02-24 14:25:06 +00:00
|
|
|
local row = {}
|
2023-05-02 05:25:34 +00:00
|
|
|
for _, button in ipairs(buttons_row) do
|
2020-02-24 14:25:06 +00:00
|
|
|
table.insert(row, {
|
|
|
|
text = button.text,
|
|
|
|
callback = function()
|
2023-05-02 05:25:34 +00:00
|
|
|
if button.callback then
|
2020-02-24 14:25:06 +00:00
|
|
|
button.callback()
|
|
|
|
end
|
2021-08-12 22:45:10 +00:00
|
|
|
if self.keep_dialog_open then return end
|
2020-02-24 14:25:06 +00:00
|
|
|
UIManager:close(self)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end
|
2023-05-02 05:25:34 +00:00
|
|
|
table.insert(buttons, rownum + i, row)
|
2017-05-16 09:11:11 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-07-02 14:52:17 +00:00
|
|
|
local button_table = ButtonTable:new{
|
|
|
|
width = content:getSize().w,
|
2020-02-24 14:25:06 +00:00
|
|
|
buttons = buttons,
|
2014-07-02 14:52:17 +00:00
|
|
|
zero_sep = true,
|
2014-05-01 10:37:12 +00:00
|
|
|
show_parent = self,
|
2014-03-13 13:52:43 +00:00
|
|
|
}
|
2012-06-10 15:52:09 +00:00
|
|
|
|
2020-02-24 14:25:06 +00:00
|
|
|
local frame = FrameContainer:new{
|
|
|
|
background = Blitbuffer.COLOR_WHITE,
|
2020-12-19 07:25:00 +00:00
|
|
|
radius = Size.radius.window,
|
2020-02-24 14:25:06 +00:00
|
|
|
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,
|
2024-01-29 21:37:32 +00:00
|
|
|
unmovable = self.unmovable,
|
2020-02-24 14:25:06 +00:00
|
|
|
}
|
2014-03-13 13:52:43 +00:00
|
|
|
self[1] = CenterContainer:new{
|
|
|
|
dimen = Screen:getSize(),
|
2020-02-24 14:25:06 +00:00
|
|
|
self.movable,
|
2014-03-13 13:52:43 +00:00
|
|
|
}
|
2020-02-24 14:25:06 +00:00
|
|
|
|
|
|
|
-- 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
|
2023-05-02 05:25:34 +00:00
|
|
|
if self._added_widgets then
|
|
|
|
self:_preserveAddedWidgets()
|
|
|
|
end
|
2020-02-24 14:25:06 +00:00
|
|
|
self:free()
|
|
|
|
self:init()
|
|
|
|
end
|
|
|
|
end
|
2014-12-01 14:39:41 +00:00
|
|
|
end
|
|
|
|
|
2023-05-02 05:25:34 +00:00
|
|
|
function ConfirmBox:addWidget(widget)
|
|
|
|
if self._added_widgets then
|
|
|
|
self:_preserveAddedWidgets()
|
|
|
|
else
|
|
|
|
self._added_widgets = {}
|
|
|
|
end
|
|
|
|
table.insert(self._added_widgets, widget)
|
|
|
|
self:free()
|
|
|
|
self:init()
|
|
|
|
end
|
|
|
|
|
|
|
|
function ConfirmBox:_preserveAddedWidgets()
|
|
|
|
-- remove added widgets to preserve their subwidgets from being free'ed
|
|
|
|
for i = 1, #self._added_widgets do
|
|
|
|
table.remove(self.text_group)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function ConfirmBox:getAddedWidgetAvailableWidth()
|
|
|
|
return self.text_widget_width
|
|
|
|
end
|
|
|
|
|
2014-12-01 14:39:41 +00:00
|
|
|
function ConfirmBox:onShow()
|
|
|
|
UIManager:setDirty(self, function()
|
2023-02-07 00:01:05 +00:00
|
|
|
return "ui", self.movable.dimen
|
2014-12-01 14:39:41 +00:00
|
|
|
end)
|
2021-01-01 13:34:55 +00:00
|
|
|
if self.flush_events_on_show then
|
AutoSuspend: Don't send LeaveStandby events from a zombie plugin instance (#9124)
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., ebb81b98451e2a8f54c46f51e861c19fdfb40499) 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`) ;).
2022-05-25 21:36:41 +00:00
|
|
|
-- Discard queued and upcoming input events to avoid accidental dismissal
|
2022-05-23 11:52:52 +00:00
|
|
|
Input:inhibitInputUntil(true)
|
2021-01-01 13:34:55 +00:00
|
|
|
end
|
2014-12-01 14:39:41 +00:00
|
|
|
end
|
2014-07-02 14:52:17 +00:00
|
|
|
|
2014-12-01 14:39:41 +00:00
|
|
|
function ConfirmBox:onCloseWidget()
|
|
|
|
UIManager:setDirty(nil, function()
|
2023-02-07 00:01:05 +00:00
|
|
|
return "ui", self.movable.dimen
|
2014-12-01 14:39:41 +00:00
|
|
|
end)
|
2012-06-10 15:52:09 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function ConfirmBox:onClose()
|
2017-01-15 20:55:06 +00:00
|
|
|
-- Call cancel_callback, parent may expect a choice
|
|
|
|
self.cancel_callback()
|
2014-03-13 13:52:43 +00:00
|
|
|
UIManager:close(self)
|
|
|
|
return true
|
2012-06-10 15:52:09 +00:00
|
|
|
end
|
|
|
|
|
2017-01-15 20:55:06 +00:00
|
|
|
function ConfirmBox:onTapClose(arg, ges)
|
2023-02-07 00:01:05 +00:00
|
|
|
if ges.pos:notIntersectWith(self.movable.dimen) then
|
2017-01-15 20:55:06 +00:00
|
|
|
self:onClose()
|
|
|
|
end
|
2020-01-02 13:00:57 +00:00
|
|
|
-- Don't let it propagate to underlying widgets
|
|
|
|
return true
|
2017-01-15 20:55:06 +00:00
|
|
|
end
|
|
|
|
|
2013-10-18 20:38:07 +00:00
|
|
|
return ConfirmBox
|