2017-04-04 07:57:14 +00:00
|
|
|
--[[--
|
2017-04-29 14:30:16 +00:00
|
|
|
Widget that displays an informational message.
|
2012-06-10 15:52:09 +00:00
|
|
|
|
2017-04-29 14:30:16 +00:00
|
|
|
It vanishes on key press or after a given timeout.
|
2017-04-02 06:50:24 +00:00
|
|
|
|
|
|
|
Example:
|
2022-10-02 18:24:00 +00:00
|
|
|
local InfoMessage = require("ui/widget/infomessage")
|
2017-04-02 06:50:24 +00:00
|
|
|
local UIManager = require("ui/uimanager")
|
2017-04-04 07:57:14 +00:00
|
|
|
local _ = require("gettext")
|
2017-09-11 08:32:39 +00:00
|
|
|
local Screen = require("device").screen
|
2017-04-02 06:50:24 +00:00
|
|
|
local sample
|
|
|
|
sample = InfoMessage:new{
|
|
|
|
text = _("Some message"),
|
|
|
|
-- Usually the hight of a InfoMessage is self-adaptive. If this field is actively set, a
|
|
|
|
-- scrollbar may be shown. This variable is usually helpful to display a large chunk of text
|
|
|
|
-- which may exceed the height of the screen.
|
2017-09-11 08:32:39 +00:00
|
|
|
height = Screen:scaleBySize(400),
|
2017-04-02 06:50:24 +00:00
|
|
|
-- Set to false to hide the icon, and also the span between the icon and text.
|
|
|
|
show_icon = false,
|
|
|
|
timeout = 5, -- This widget will vanish in 5 seconds.
|
|
|
|
}
|
2022-10-02 18:24:00 +00:00
|
|
|
UIManager:show(sample)
|
2012-06-10 15:52:09 +00:00
|
|
|
]]
|
2017-04-04 07:57:14 +00:00
|
|
|
|
|
|
|
local Blitbuffer = require("ffi/blitbuffer")
|
|
|
|
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")
|
2020-12-19 11:18:30 +00:00
|
|
|
local IconWidget = require("ui/widget/iconwidget")
|
2017-04-04 07:57:14 +00:00
|
|
|
local ImageWidget = require("ui/widget/imagewidget")
|
|
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
2018-01-29 20:27:24 +00:00
|
|
|
local MovableContainer = require("ui/widget/container/movablecontainer")
|
2017-04-04 07:57:14 +00:00
|
|
|
local ScrollTextWidget = require("ui/widget/scrolltextwidget")
|
2017-09-13 14:56:20 +00:00
|
|
|
local Size = require("ui/size")
|
2017-04-04 07:57:14 +00:00
|
|
|
local TextBoxWidget = require("ui/widget/textboxwidget")
|
|
|
|
local UIManager = require("ui/uimanager")
|
|
|
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
|
|
|
local _ = require("gettext")
|
|
|
|
local Input = Device.input
|
|
|
|
local Screen = Device.screen
|
|
|
|
|
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 InfoMessage = InputContainer:extend{
|
2014-10-30 08:01:01 +00:00
|
|
|
modal = true,
|
2023-11-05 05:24:18 +00:00
|
|
|
face = nil,
|
|
|
|
monospace_font = false,
|
2014-03-13 13:52:43 +00:00
|
|
|
text = "",
|
|
|
|
timeout = nil, -- in seconds
|
2023-11-22 17:58:31 +00:00
|
|
|
_timeout_func = nil,
|
2017-04-02 06:50:24 +00:00
|
|
|
width = nil, -- The width of the InfoMessage. Keep it nil to use default value.
|
|
|
|
height = nil, -- The height of the InfoMessage. If this field is set, a scrollbar may be shown.
|
2017-05-08 07:43:34 +00:00
|
|
|
-- The image shows at the left of the InfoMessage. Image data will be freed
|
|
|
|
-- by InfoMessage, caller should not manage its lifecycle
|
|
|
|
image = nil,
|
2017-04-02 06:50:24 +00:00
|
|
|
image_width = nil, -- The image width if image is used. Keep it nil to use original width.
|
|
|
|
image_height = nil, -- The image height if image is used. Keep it nil to use original height.
|
|
|
|
-- Whether the icon should be shown. If it is false, self.image will be ignored.
|
|
|
|
show_icon = true,
|
2020-12-19 11:18:30 +00:00
|
|
|
icon = "notice-info",
|
|
|
|
alpha = nil, -- if image or icon have an alpha channel (default to true for icons, false for images
|
2022-07-11 11:50:28 +00:00
|
|
|
dismissable = true,
|
2021-01-11 17:14:17 +00:00
|
|
|
dismiss_callback = nil,
|
2021-04-16 20:27:52 +00:00
|
|
|
-- Passed to TextBoxWidget
|
2021-04-15 17:07:34 +00:00
|
|
|
alignment = "left",
|
2020-10-08 10:58:13 +00:00
|
|
|
-- In case we'd like to use it to display some text we know a few more things about:
|
|
|
|
lang = nil,
|
|
|
|
para_direction_rtl = nil,
|
|
|
|
auto_para_direction = nil,
|
2020-12-03 16:37:46 +00:00
|
|
|
-- Don't call setDirty when closing the widget
|
|
|
|
no_refresh_on_close = nil,
|
2020-12-22 17:45:29 +00:00
|
|
|
-- Only have it painted after this delay (dismissing still works before it's shown)
|
|
|
|
show_delay = nil,
|
2021-01-01 13:34:55 +00:00
|
|
|
-- Set to true when it might be displayed after some processing, to avoid accidental dismissal
|
|
|
|
flush_events_on_show = false,
|
2012-06-10 15:52:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function InfoMessage:init()
|
2023-11-05 05:24:18 +00:00
|
|
|
if not self.face then
|
|
|
|
self.face = Font:getFace(self.monospace_font and "infont" or "infofont")
|
|
|
|
end
|
|
|
|
|
2022-07-11 11:50:28 +00:00
|
|
|
if self.dismissable then
|
|
|
|
if Device:hasKeys() then
|
2022-10-27 00:01:51 +00:00
|
|
|
self.key_events.AnyKeyPressed = { { Input.group.Any } }
|
2022-07-11 11:50:28 +00:00
|
|
|
end
|
|
|
|
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(),
|
|
|
|
}
|
2014-03-13 13:52:43 +00:00
|
|
|
}
|
|
|
|
}
|
2022-07-11 11:50:28 +00:00
|
|
|
end
|
2014-03-13 13:52:43 +00:00
|
|
|
end
|
2017-04-02 06:50:24 +00:00
|
|
|
|
2016-01-03 07:44:23 +00:00
|
|
|
local image_widget
|
2017-04-02 06:50:24 +00:00
|
|
|
if self.show_icon then
|
2019-08-23 17:53:53 +00:00
|
|
|
--- @todo remove self.image support, only used in filemanagersearch
|
2017-05-08 07:43:34 +00:00
|
|
|
-- this requires self.image's lifecycle to be managed by ImageWidget
|
|
|
|
-- instead of caller, which is easy to introduce bugs
|
2017-04-02 06:50:24 +00:00
|
|
|
if self.image then
|
|
|
|
image_widget = ImageWidget:new{
|
|
|
|
image = self.image,
|
|
|
|
width = self.image_width,
|
|
|
|
height = self.image_height,
|
2020-12-19 11:18:30 +00:00
|
|
|
alpha = self.alpha ~= nil and self.alpha or false, -- default to false
|
2017-04-02 06:50:24 +00:00
|
|
|
}
|
|
|
|
else
|
2020-12-19 11:18:30 +00:00
|
|
|
image_widget = IconWidget:new{
|
|
|
|
icon = self.icon,
|
|
|
|
alpha = self.alpha == nil and true or self.alpha, -- default to true
|
2017-04-02 06:50:24 +00:00
|
|
|
}
|
|
|
|
end
|
|
|
|
else
|
|
|
|
image_widget = WidgetContainer:new()
|
|
|
|
end
|
|
|
|
|
|
|
|
local text_width
|
|
|
|
if self.width == nil then
|
2020-06-14 00:21:41 +00:00
|
|
|
text_width = math.floor(Screen:getWidth() * 2/3)
|
2017-04-02 06:50:24 +00:00
|
|
|
else
|
|
|
|
text_width = self.width - image_widget:getSize().w
|
|
|
|
if text_width < 0 then
|
|
|
|
text_width = 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local text_widget
|
|
|
|
if self.height then
|
|
|
|
text_widget = ScrollTextWidget:new{
|
|
|
|
text = self.text,
|
|
|
|
face = self.face,
|
|
|
|
width = text_width,
|
|
|
|
height = self.height,
|
2021-04-15 17:07:34 +00:00
|
|
|
alignment = self.alignment,
|
2017-04-02 06:50:24 +00:00
|
|
|
dialog = self,
|
2020-10-08 10:58:13 +00:00
|
|
|
lang = self.lang,
|
|
|
|
para_direction_rtl = self.para_direction_rtl,
|
|
|
|
auto_para_direction = self.auto_para_direction,
|
2014-08-27 03:07:25 +00:00
|
|
|
}
|
|
|
|
else
|
2017-04-02 06:50:24 +00:00
|
|
|
text_widget = TextBoxWidget:new{
|
|
|
|
text = self.text,
|
|
|
|
face = self.face,
|
|
|
|
width = text_width,
|
2021-04-15 17:07:34 +00:00
|
|
|
alignment = self.alignment,
|
2020-10-08 10:58:13 +00:00
|
|
|
lang = self.lang,
|
|
|
|
para_direction_rtl = self.para_direction_rtl,
|
|
|
|
auto_para_direction = self.auto_para_direction,
|
2014-08-27 03:07:25 +00:00
|
|
|
}
|
|
|
|
end
|
2019-01-15 19:38:25 +00:00
|
|
|
local frame = FrameContainer:new{
|
|
|
|
background = Blitbuffer.COLOR_WHITE,
|
2020-12-19 07:25:00 +00:00
|
|
|
radius = Size.radius.window,
|
2019-01-15 19:38:25 +00:00
|
|
|
HorizontalGroup:new{
|
|
|
|
align = "center",
|
|
|
|
image_widget,
|
|
|
|
HorizontalSpan:new{ width = (self.show_icon and Size.span.horizontal_default or 0) },
|
|
|
|
text_widget,
|
2014-03-13 13:52:43 +00:00
|
|
|
}
|
|
|
|
}
|
2019-01-15 19:38:25 +00:00
|
|
|
self.movable = MovableContainer:new{
|
|
|
|
frame,
|
|
|
|
}
|
2018-01-29 20:27:24 +00:00
|
|
|
self[1] = CenterContainer:new{
|
|
|
|
dimen = Screen:getSize(),
|
|
|
|
self.movable,
|
|
|
|
}
|
2019-01-15 19:38:25 +00:00
|
|
|
if not self.height then
|
|
|
|
-- 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
|
2020-12-22 17:45:29 +00:00
|
|
|
|
|
|
|
if self.show_delay then
|
|
|
|
-- Don't have UIManager setDirty us yet
|
|
|
|
self.invisible = true
|
|
|
|
end
|
2012-06-10 15:52:09 +00:00
|
|
|
end
|
|
|
|
|
2014-12-01 14:39:41 +00:00
|
|
|
function InfoMessage:onCloseWidget()
|
2023-11-22 17:58:31 +00:00
|
|
|
-- If we were closed early, drop the scheduled timeout
|
|
|
|
if self._timeout_func then
|
|
|
|
UIManager:unschedule(self._timeout_func)
|
|
|
|
self._timeout_func = nil
|
|
|
|
end
|
|
|
|
|
Revamp "flash_ui" handling (#7118)
* Wherever possible, do an actual dumb invert on the Screen BB instead of repainting the widget, *then* inverting it (which is what the "invert" flag does).
* Instead of playing with nextTick/tickAfterNext delays, explicitly fence stuff with forceRePaint
* And, in the few cases where said Mk. 7 quirk kicks in, make the fences more marked by using a well-placed WAIT_FOR_UPDATE_COMPLETE
* Fix an issue in Button where show/hide & enable/disable where actually all toggles, which meant that duplicate calls or timing issues would do the wrong thing. (This broke dimming some icons, and mistakenly dropped the background from FM chevrons, for example).
* Speaking of, fix Button's hide/show to actually restore the background properly (there was a stupid typo in the variable name)
* Still in Button, fix the insanity of the double repaint on rounded buttons. Turns out it made sense, after all (and was related to said missing background, and bad interaction with invert & text with no background).
* KeyValuePage suffered from a similar issue with broken highlights (all black) because of missing backgrounds.
* In ConfigDialog, only instanciate IconButtons once (because every tab switch causes a full instantiation; and the initial display implies a full instanciation and an initial tab switch). Otherwise, both instances linger, and catch taps, and as such, double highlights.
* ConfigDialog: Restore the "don't repaint ReaderUI" when switching between similarly sized tabs (re #6131). I never could reproduce that on eInk, and I can't now on the emulator, so I'm assuming @poire-z fixed it during the swap to SVG icons.
* KeyValuePage: Only instanciate Buttons once (again, this is a widget that goes through a full init every page). Again, caused highlight/dimming issues because buttons were stacked.
* Menu: Ditto.
* TouchMenu: Now home of the gnarliest unhilight heuristics, because of the sheer amount of different things that can happen (and/or thanks to stuff not flagged covers_fullscreen properly ;p).
* Bump base
https://github.com/koreader/koreader-base/pull/1280
https://github.com/koreader/koreader-base/pull/1282
https://github.com/koreader/koreader-base/pull/1283
https://github.com/koreader/koreader-base/pull/1284
* Bump android-luajit-launcher
https://github.com/koreader/android-luajit-launcher/pull/284
https://github.com/koreader/android-luajit-launcher/pull/285
https://github.com/koreader/android-luajit-launcher/pull/286
https://github.com/koreader/android-luajit-launcher/pull/287
2021-01-10 00:51:09 +00:00
|
|
|
if self._delayed_show_action then
|
|
|
|
UIManager:unschedule(self._delayed_show_action)
|
|
|
|
self._delayed_show_action = nil
|
|
|
|
end
|
2023-11-22 17:58:31 +00:00
|
|
|
if self.dismiss_callback then
|
|
|
|
self.dismiss_callback()
|
|
|
|
-- NOTE: Dirty hack for Trapper, which needs to pull a Lazarus on dead widgets while preserving the callback's integrity ;).
|
|
|
|
if not self.is_infomessage then
|
|
|
|
self.dismiss_callback = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-12-22 17:45:29 +00:00
|
|
|
if self.invisible then
|
|
|
|
-- Still invisible, no setDirty needed
|
2021-05-31 20:19:24 +00:00
|
|
|
return
|
2020-12-22 17:45:29 +00:00
|
|
|
end
|
2020-12-03 16:37:46 +00:00
|
|
|
if self.no_refresh_on_close then
|
2021-05-31 20:19:24 +00:00
|
|
|
return
|
2020-12-03 16:37:46 +00:00
|
|
|
end
|
|
|
|
|
2014-12-01 14:39:41 +00:00
|
|
|
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)
|
2014-11-30 22:25:23 +00:00
|
|
|
end
|
|
|
|
|
2012-06-10 15:52:09 +00:00
|
|
|
function InfoMessage:onShow()
|
2020-12-22 17:45:29 +00:00
|
|
|
-- triggered by the UIManager after we got successfully show()'n (not yet painted)
|
|
|
|
if self.show_delay and self.invisible then
|
|
|
|
-- Let us be shown after this delay
|
|
|
|
self._delayed_show_action = function()
|
|
|
|
self._delayed_show_action = nil
|
|
|
|
self.invisible = false
|
|
|
|
self:onShow()
|
|
|
|
end
|
|
|
|
UIManager:scheduleIn(self.show_delay, self._delayed_show_action)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
-- set our region to be dirty, so UImanager will call our paintTo()
|
2014-12-01 14:39:41 +00:00
|
|
|
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
|
2023-11-22 17:58:31 +00:00
|
|
|
-- schedule a close on timeout, if any
|
2014-03-13 13:52:43 +00:00
|
|
|
if self.timeout then
|
2023-11-22 17:58:31 +00:00
|
|
|
self._timeout_func = function()
|
|
|
|
self._timeout_func = nil
|
2021-01-11 17:14:17 +00:00
|
|
|
UIManager:close(self)
|
2023-11-22 17:58:31 +00:00
|
|
|
end
|
|
|
|
UIManager:scheduleIn(self.timeout, self._timeout_func)
|
2014-03-13 13:52:43 +00:00
|
|
|
end
|
|
|
|
return true
|
2012-06-10 15:52:09 +00:00
|
|
|
end
|
|
|
|
|
2020-12-22 17:45:29 +00:00
|
|
|
function InfoMessage:getVisibleArea()
|
|
|
|
if not self.invisible then
|
2023-02-07 00:01:05 +00:00
|
|
|
return self.movable.dimen
|
2020-12-22 17:45:29 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function InfoMessage:paintTo(bb, x, y)
|
|
|
|
if self.invisible then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
InputContainer.paintTo(self, bb, x, y)
|
|
|
|
end
|
|
|
|
|
2013-03-17 04:15:21 +00:00
|
|
|
function InfoMessage:onTapClose()
|
2023-11-22 17:58:31 +00:00
|
|
|
UIManager:close(self)
|
2017-12-17 17:27:24 +00:00
|
|
|
if self.readonly ~= true then
|
|
|
|
return true
|
|
|
|
end
|
2013-03-17 04:15:21 +00:00
|
|
|
end
|
2022-10-23 20:36:09 +00:00
|
|
|
InfoMessage.onAnyKeyPressed = InfoMessage.onTapClose
|
2013-10-18 20:38:07 +00:00
|
|
|
|
|
|
|
return InfoMessage
|