2
0
mirror of https://github.com/koreader/koreader synced 2024-11-16 06:12:56 +00:00
koreader/frontend/ui/widget/datetimewidget.lua
NiLuJe 45a4aac3d3
Notification: Fence the *display* update in an attempt to avoid upsetting some boards... (#10083)
Re: https://github.com/koreader/koreader/issues/9806#issuecomment-1416827447

Depends on https://github.com/koreader/koreader-base/pull/1576

Includes assorted cosmetics tweaks related to duplicate `setDirty` calls when instantiating widgets that already have an `onShow` handler doing it. (I left widgets doing it in `update` instead of `init` alone, on the assumption that callers *may* be relying on that behavior when updating widgets at runtime. This might actually never matter, and it certainly didn't for ScreenSaverWidget, which is why I removed it from there ;p).
2023-02-07 01:01:05 +01:00

443 lines
13 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

--[[--
Widget for setting the date or time.
Example for input a time:
local DateTimeWidget = require("ui/widget/datetimewidget")
local @{gettext|_} = require("gettext")
local time_widget = DateTimeWidget:new{
hour = 10,
min = 30,
ok_text = _("Set time"),
title_text = _("Set time"),
info_text = _("Some information"),
callback = function(time)
-- use time.hour and time.min here
end
}
UIManager:show(time_widget)
Example for input a date:
local DateTimeWidget = require("ui/widget/datetimewidget")
local @{gettext|_} = require("gettext")
local date_widget = DateTimeWidget:new{
year = 2021,
month = 12,
day = 31,
ok_text = _("Set date"),
title_text = _("Set date"),
callback = function(time)
-- use time.year, time.month, time.day here
end
}
UIManager:show(date_widget)
Example to input a duration in days, hours and minutes:
local DateTimeWidget = require("ui/widget/datetimewidget")
local @{gettext|_} = require("gettext")
local date_widget = DateTimeWidget:new{
day = 5,
hour = 12,
min = 0,
ok_text = _("Set"),
title_text = _("Set duration"),
callback = function(time)
-- use time.day, time.hour, time.min here
end
}
UIManager:show(date_widget)
--]]--
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 NumberPickerWidget = require("ui/widget/numberpickerwidget")
local Size = require("ui/size")
local TextWidget = require("ui/widget/textwidget")
local TitleBar = require("ui/widget/titlebar")
local UIManager = require("ui/uimanager")
local VerticalGroup = require("ui/widget/verticalgroup")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local _ = require("gettext")
local Screen = Device.screen
local T = require("ffi/util").template
local DateTimeWidget = FocusManager:extend{
title_face = Font:getFace("x_smalltfont"),
info_text = nil,
width = nil,
height = nil,
ok_text = _("Apply"),
cancel_text = _("Close"),
-- Optional extra button on bottom
extra_text = nil,
extra_callback = nil,
}
function DateTimeWidget:init()
self.nb_pickers = 0
if self.year then
self.nb_pickers = self.nb_pickers + 1
end
if self.month then
self.nb_pickers = self.nb_pickers + 1
end
if self.day then
self.nb_pickers = self.nb_pickers + 1
end
if self.hour then
self.nb_pickers = self.nb_pickers + 1
end
if self.min then
self.nb_pickers = self.nb_pickers + 1
end
if self.sec then
self.nb_pickers = self.nb_pickers + 1
end
self.layout = {}
self.screen_width = Screen:getWidth()
self.screen_height = Screen:getHeight()
local width_scale_factor = 0.6
if self.nb_pickers == 3 then
width_scale_factor = 0.8
elseif self.nb_pickers == 4 then
width_scale_factor = 0.85
elseif self.nb_pickers >=5 then
width_scale_factor = 0.95
end
self.width = self.width or math.floor(math.min(self.screen_width, self.screen_height) * width_scale_factor)
if Device:hasKeys() then
self.key_events.Close = { { Device.input.group.Back } }
end
if Device:isTouchDevice() then
self.ges_events.TapClose = {
GestureRange:new{
ges = "tap",
range = Geom:new{
w = self.screen_width,
h = self.screen_height,
}
},
}
end
-- Actually the widget layout
self:createLayout()
end
function DateTimeWidget:createLayout()
-- Empty table w/ the methods we use NOP'ed
local dummy_widget = {}
function dummy_widget:free() end
function dummy_widget:getValue() end
function dummy_widget:update() end
-- The following calculation is stolen from NumberPickerWidget
local number_picker_widgets_width = math.floor(math.min(self.screen_width, self.screen_height) * 0.2)
if self.nb_pickers > 3 then
number_picker_widgets_width = math.floor(number_picker_widgets_width * 3 / self.nb_pickers)
end
if self.year then
self.year_widget = NumberPickerWidget:new{
show_parent = self,
value = self.year,
value_min = self.year_min or 2021,
value_max = self.year_max or 2525,
value_step = 1,
value_hold_step = self.year_hold_step or 4,
width = number_picker_widgets_width,
}
self:mergeLayoutInHorizontal(self.year_widget)
else
self.year_widget = dummy_widget
end
if self.month then
self.month_widget = NumberPickerWidget:new{
show_parent = self,
value = self.month,
value_min = self.month_min or 1,
value_max = self.month_max or 12,
value_step = 1,
value_hold_step = self.month_hold_step or 3,
width = number_picker_widgets_width,
}
self:mergeLayoutInHorizontal(self.month_widget)
else
self.month_widget = dummy_widget
end
if self.day then
self.day_widget = NumberPickerWidget:new{
show_parent = self,
value = self.day,
value_min = self.day_min or 1,
value_max = self.day_max or 31,
value_step = 1,
value_hold_step = self.day_hold_step or 3,
width = number_picker_widgets_width,
}
self:mergeLayoutInHorizontal(self.day_widget)
else
self.day_widget = dummy_widget
end
if self.hour then
self.hour_widget = NumberPickerWidget:new{
show_parent = self,
value = self.hour,
value_min = self.hour_min or 0,
value_max = self.hour_max or 23,
value_step = 1,
value_hold_step = self.hour_hold_step or 4,
width = number_picker_widgets_width,
}
self:mergeLayoutInHorizontal(self.hour_widget)
else
self.hour_widget = dummy_widget
end
if self.min then
self.min_widget = NumberPickerWidget:new{
show_parent = self,
value = self.min,
value_min = self.min_min or 0,
value_max = self.min_max or 59,
value_step = 1,
value_hold_step = self.min_hold_step or 10,
width = number_picker_widgets_width,
}
self:mergeLayoutInHorizontal(self.min_widget)
else
self.min_widget = dummy_widget
end
if self.sec then
self.sec_widget = NumberPickerWidget:new{
show_parent = self,
value = self.sec,
value_min = self.sec_min or 0,
value_max = self.sec_max or 59,
value_step = 1,
value_hold_step = self.sec_hold_step or 10,
width = number_picker_widgets_width,
}
self:mergeLayoutInHorizontal(self.sec_widget)
else
self.sec_widget = dummy_widget
end
local separator_date = TextWidget:new{
text = "",
face = self.title_face,
bold = true,
}
local separator_time = TextWidget:new{
text = ":",
face = self.title_face,
bold = true,
}
local separator_date_time = TextWidget:new{
text = "/",
face = self.title_face,
bold = true,
}
local date_group = HorizontalGroup:new{
align = "center",
self.year_widget, -- 1
separator_date, -- 2
self.month_widget, -- 3
separator_date, -- 4
self.day_widget, -- 5
separator_date_time, -- 6
self.hour_widget, -- 7
separator_time, -- 8
self.min_widget, -- 9
separator_time, -- 10
self.sec_widget, -- 11
}
-- remove empty widgets plus trailling placeholder
for i = #date_group, 1, -2 do
if date_group[i] == dummy_widget then
table.remove(date_group, i)
table.remove(date_group, i-1)
end
end
-- clean up leading separator
if date_group[1] == separator_date or date_group[1] == separator_date_time or date_group[1] == separator_time then
table.remove(date_group, 1)
end
local title_bar = TitleBar:new{
width = self.width,
align = "left",
with_bottom_line = true,
title = self.title_text,
title_shrink_font_to_fit = true,
info_text = self.info_text,
show_parent = self,
}
local buttons = {}
if self.default_value then
table.insert(buttons, {
{
text = self.default_text or T(_("Default value: %1"), self.default_value),
callback = function()
if self.default_callback then
self.default_callback({
year = self.year_widget:getValue(),
month = self.month_widget:getValue(),
day = self.day_widget:getValue(),
hour = self.hour_widget:getValue(),
minute = self.min_widget:getValue(),
second = self.sec_widget:getValue(),
})
end
if not self.keep_shown_on_apply then -- assume extra wants it same as ok
self:onClose()
end
end,
},
})
end
if self.extra_text then
table.insert(buttons, {
{
text = self.extra_text,
callback = function()
self.extra_callback(self)
end,
},
})
end
table.insert(buttons, {
{
text = self.cancel_text,
callback = function()
if self.cancel_callback then
self.cancel_callback(self)
end
self:onClose()
end,
},
{
text = self.ok_text,
callback = function()
if self.callback then
self.year = self.year_widget:getValue()
self.month = self.month_widget:getValue()
self.day = self.day_widget:getValue()
self.hour = self.hour_widget:getValue()
self.min = self.min_widget:getValue()
self.sec = self.sec_widget:getValue()
self:callback(self)
end
self:onClose()
end,
},
})
local ok_cancel_buttons = ButtonTable:new{
width = self.width - 2*Size.padding.default,
buttons = buttons,
zero_sep = true,
show_parent = self,
}
self:mergeLayoutInVertical(ok_cancel_buttons)
self.date_frame = FrameContainer:new{
radius = Size.radius.window,
bordersize = Size.border.window,
padding = 0,
margin = 0,
background = Blitbuffer.COLOR_WHITE,
VerticalGroup:new{
align = "left",
title_bar,
CenterContainer:new{
dimen = Geom:new{
w = self.width,
h = math.floor(date_group:getSize().h * 1.2),
},
date_group
},
CenterContainer:new{
dimen = Geom:new{
w = self.width,
h = ok_cancel_buttons:getSize().h,
},
ok_cancel_buttons
}
}
}
self[1] = WidgetContainer:new{
align = "center",
dimen = Geom:new{
x = 0, y = 0,
w = self.screen_width,
h = self.screen_height,
},
FrameContainer:new{
bordersize = 0,
padding = Size.padding.default,
self.date_frame,
}
}
self:refocusWidget()
end
function DateTimeWidget:update(year, month, day, hour, min, sec)
self.year_widget.value = year
self.year_widget:update()
self.month_widget.value = month
self.month_widget:update()
self.day_widget.value = day
self.day_widget:update()
self.hour_widget.value = hour
self.hour_widget:update()
self.min_widget.value = min
self.min_widget:update()
self.sec_widget.value = sec
self.sec_widget:update()
end
function DateTimeWidget:onCloseWidget()
-- Let our main WidgetContainer free its child widgets
self[1]:free()
UIManager:setDirty(nil, function()
return "ui", self.date_frame.dimen
end)
end
function DateTimeWidget:onShow()
UIManager:setDirty(self, function()
return "ui", self.date_frame.dimen
end)
return true
end
function DateTimeWidget:onTapClose(arg, ges_ev)
if ges_ev.pos:notIntersectWith(self.date_frame.dimen) then
self:onClose()
end
return true
end
function DateTimeWidget:onClose()
UIManager:close(self)
return true
end
return DateTimeWidget