Profiles: add QuickMenu (#9526)

reviewable/pr9534/r1
hius07 2 years ago committed by GitHub
parent 83a2965d6b
commit b81a407690
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -24,6 +24,7 @@ local Geom = require("ui/geometry")
local GestureRange = require("ui/gesturerange")
local IconWidget = require("ui/widget/iconwidget")
local InputContainer = require("ui/widget/container/inputcontainer")
local LeftContainer = require("ui/widget/container/leftcontainer")
local Size = require("ui/size")
local TextBoxWidget = require("ui/widget/textboxwidget")
local TextWidget = require("ui/widget/textwidget")
@ -39,6 +40,7 @@ local Button = InputContainer:new{
icon_width = Screen:scaleBySize(DGENERIC_ICON_SIZE), -- our icons are square
icon_height = Screen:scaleBySize(DGENERIC_ICON_SIZE),
icon_rotation_angle = 0,
align = "center", -- or "left"
preselect = false,
callback = nil,
enabled = true,
@ -78,8 +80,12 @@ function Button:init()
self.padding_v = self.padding
end
local is_left_aligned = self.align == "left"
local right_margin = is_left_aligned and (2 * Size.padding.large) or 0
if self.text then
local max_width = self.max_width and self.max_width - 2*self.padding_h - 2*self.margin - 2*self.bordersize or nil
local max_width = self.max_width
and self.max_width - 2*self.padding_h - 2*self.margin - 2*self.bordersize - right_margin or nil
self.label_widget = TextWidget:new{
text = self.text,
max_width = max_width,
@ -142,6 +148,23 @@ function Button:init()
self.width = widget_size.w
end
-- set FrameContainer content
if is_left_aligned then
self.label_container = LeftContainer:new{
dimen = Geom:new{
w = self.width - 4 * Size.padding.large,
h = widget_size.h
},
self.label_widget,
}
else
self.label_container = CenterContainer:new{
dimen = Geom:new{
w = self.width,
h = widget_size.h
},
self.label_widget,
}
end
self.frame = FrameContainer:new{
margin = self.margin,
show_parent = self.show_parent,
@ -152,13 +175,7 @@ function Button:init()
padding_bottom = self.padding_v,
padding_left = self.padding_h,
padding_right = self.padding_h,
CenterContainer:new{
dimen = Geom:new{
w = self.width,
h = widget_size.h
},
self.label_widget,
}
self.label_container
}
if self.preselect then
self.frame.invert = true

@ -22,6 +22,8 @@ local ButtonDialogTitle = InputContainer:new{
title_face = Font:getFace("x_smalltfont"),
title_padding = Size.padding.large,
title_margin = Size.margin.title,
width = nil,
width_factor = nil, -- number between 0 and 1, factor to the smallest of screen width and height
use_info_style = true, -- set to false to have bold font style of the title
info_face = Font:getFace("infofont"),
info_padding = Size.padding.default,
@ -32,6 +34,14 @@ local ButtonDialogTitle = InputContainer:new{
}
function ButtonDialogTitle:init()
self.screen_width = Screen:getWidth()
self.screen_height = Screen:getHeight()
if not self.width then
if not self.width_factor then
self.width_factor = 0.9 -- default if no width specified
end
self.width = math.floor(math.min(self.screen_width, self.screen_height) * self.width_factor)
end
if self.dismissable then
if Device:hasKeys() then
local close_keys = Device:hasFewKeys() and { "Back", "Left" } or Device.input.group.Back
@ -46,8 +56,8 @@ function ButtonDialogTitle:init()
range = Geom:new {
x = 0,
y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
w = self.screen_width,
h = self.screen_height,
}
}
}
@ -65,13 +75,14 @@ function ButtonDialogTitle:init()
bordersize = 0,
TextBoxWidget:new{
text = self.title,
width = math.floor(math.min(Screen:getWidth(), Screen:getHeight()) * 0.8),
width = math.floor(self.width * 0.9),
face = self.use_info_style and self.info_face or self.title_face,
alignment = self.title_align or "left",
},
},
VerticalSpan:new{ width = Size.span.vertical_default },
ButtonTable:new{
width = self.width,
buttons = self.buttons,
zero_sep = true,
show_parent = self,

@ -22,8 +22,6 @@ local ButtonTable = FocusManager:new{
padding = Size.padding.default,
zero_sep = false,
button_font_face = "cfont",
button_font_size = 20,
}
function ButtonTable:init()
@ -55,6 +53,7 @@ function ButtonTable:init()
icon = btn_entry.icon,
icon_width = btn_entry.icon_width,
icon_height = btn_entry.icon_height,
align = btn_entry.align,
enabled = btn_entry.enabled,
callback = btn_entry.callback,
hold_callback = btn_entry.hold_callback,
@ -65,8 +64,9 @@ function ButtonTable:init()
margin = 0,
padding = Size.padding.buttontable, -- a bit taller than standalone buttons, for easier tap
padding_h = 0, -- allow text to take more of the horizontal space
text_font_face = self.button_font_face,
text_font_size = self.button_font_size,
text_font_face = btn_entry.font_face,
text_font_size = btn_entry.font_size,
text_font_bold = btn_entry.font_bold,
show_parent = self.show_parent,
}
if btn_entry.id then

@ -9,6 +9,7 @@ local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local _ = require("gettext")
local T = FFIUtil.template
local util = require("util")
local autostart_done = false
@ -23,6 +24,7 @@ local Profiles = WidgetContainer:new{
function Profiles:init()
Dispatcher:init()
self.ui.menu:registerToMainMenu(self)
self:onDispatcherRegisterActions()
self:executeAutostart()
end
@ -41,6 +43,25 @@ function Profiles:onFlushSettings()
end
end
local function dispatcherRegisterProfile(name)
Dispatcher:registerAction("profile_exec_"..name,
{category="none", event="ProfileExecute", arg=name, title=T(_("Profile \u{F144} %1"), name), general=true})
Dispatcher:registerAction("profile_menu_"..name,
{category="none", event="ProfileShowMenu", arg=name, title=T(_("Profile \u{F0CA} %1"), name), general=true})
end
local function dispatcherRemoveProfile(name)
Dispatcher:removeAction("profile_exec_"..name)
Dispatcher:removeAction("profile_menu_"..name)
end
function Profiles:onDispatcherRegisterActions()
self:loadProfiles()
for name in pairs(self.data) do
dispatcherRegisterProfile(name)
end
end
function Profiles:addToMainMenu(menu_items)
menu_items.profiles = {
text = _("Profiles"),
@ -57,136 +78,333 @@ function Profiles:getSubMenuItems()
text = _("New"),
keep_menu_open = true,
callback = function(touchmenu_instance)
local name_input
name_input = InputDialog:new{
title = _("Enter profile name"),
input = "",
buttons = {{
{
text = _("Cancel"),
id = "close",
callback = function()
UIManager:close(name_input)
end,
},
{
text = _("Save"),
callback = function()
local name = name_input:getInputText()
if not self:newProfile(name) then
UIManager:show(InfoMessage:new{
text = T(_("There is already a profile called: %1"), name),
})
return
end
UIManager:close(name_input)
touchmenu_instance.item_table = self:getSubMenuItems()
touchmenu_instance.page = 1
touchmenu_instance:updateItems()
end,
},
}},
}
UIManager:show(name_input)
name_input:onShowKeyboard()
local function editCallback(new_name)
self.data[new_name] = {}
self.updated = true
dispatcherRegisterProfile(new_name)
touchmenu_instance.item_table = self:getSubMenuItems()
touchmenu_instance.page = 1
touchmenu_instance:updateItems()
end
self:editProfileName(editCallback)
end,
separator = true,
}
},
}
for k,v in FFIUtil.orderedPairs(self.data) do
for k, v in FFIUtil.orderedPairs(self.data) do
local edit_actions_sub_items = {}
Dispatcher:addSubMenu(self, edit_actions_sub_items, self.data, k)
local sub_items = {
{
text = _("Delete profile"),
keep_menu_open = false,
separator = true,
text = _("Execute"),
callback = function(touchmenu_instance)
touchmenu_instance:onClose()
self:onProfileExecute(k)
end,
},
{
text = _("Show as QuickMenu"),
callback = function()
UIManager:show(ConfirmBox:new{
text = _("Do you want to delete this profile?"),
ok_text = _("Yes"),
cancel_text = _("No"),
ok_callback = function()
self:deleteProfile(k)
end,
})
self:onProfileShowMenu(k)
end,
},
{
text = _("Show as QuickMenu on long-press"),
checked_func = function()
local settings = self.data[k].settings
return settings and settings.long_press_show_menu
end,
callback = function()
local settings = self.data[k].settings
if settings then
if settings.long_press_show_menu then
settings.long_press_show_menu = nil
if #settings == 0 then
self.data[k].settings = nil
end
else
settings.long_press_show_menu = true
end
else
self.data[k].settings = {["long_press_show_menu"] = true}
end
self.updated = true
end,
},
{
text = _("Autostart"),
help_text = _("Execute this profile when KOReader is started with 'file browser' or 'last file'."),
checked_func = function()
return self:isAutostartProfile(k)
return G_reader_settings:getSettingForExt("autostart_profiles", k)
end,
separator = true,
callback = function()
if self:isAutostartProfile(k) then
self:deleteAutostartProfile(k)
else
self:setAutostartProfile(k)
local new_value = not G_reader_settings:getSettingForExt("autostart_profiles", k) or nil
G_reader_settings:saveSettingForExt("autostart_profiles", new_value, k)
end,
separator = true,
},
{
text = _("Edit actions"),
sub_item_table = edit_actions_sub_items,
},
{
text = _("Sort actions"),
checked_func = function()
local settings = self.data[k].settings
return settings and settings.actions_order
end,
callback = function(touchmenu_instance)
self:sortActions(k, touchmenu_instance)
end,
hold_callback = function(touchmenu_instance)
if self.data[k].settings and self.data[k].settings.actions_order then
self.data[k].settings.actions_order = nil
if #self.data[k].settings == 0 then
self.data[k].settings = nil
end
self.updated = true
touchmenu_instance:updateItems()
end
end,
separator = true,
},
{
text = T(_("Rename: %1"), k),
keep_menu_open = true,
callback = function(touchmenu_instance)
local function editCallback(new_name)
self.data[new_name] = util.tableDeepCopy(v)
self.data[k] = nil
self.updated = true
self:renameAutostart(k, new_name)
dispatcherRemoveProfile(k)
dispatcherRegisterProfile(new_name)
touchmenu_instance.item_table = self:getSubMenuItems()
touchmenu_instance:updateItems()
end
self:editProfileName(editCallback, k)
end,
},
{
text = _("Copy"),
keep_menu_open = true,
callback = function(touchmenu_instance)
local function editCallback(new_name)
self.data[new_name] = util.tableDeepCopy(v)
self.updated = true
dispatcherRegisterProfile(new_name)
touchmenu_instance.item_table = self:getSubMenuItems()
touchmenu_instance:updateItems()
end
self:editProfileName(editCallback, k)
end,
},
{
text = _("Delete"),
keep_menu_open = true,
separator = true,
callback = function(touchmenu_instance)
UIManager:show(ConfirmBox:new{
text = _("Do you want to delete this profile?"),
ok_text = _("Delete"),
ok_callback = function()
self.data[k] = nil
self.updated = true
self:renameAutostart(k)
dispatcherRemoveProfile(k)
touchmenu_instance.item_table = self:getSubMenuItems()
touchmenu_instance:updateItems()
end,
})
end,
},
}
Dispatcher:addSubMenu(self, sub_items, self.data, k)
table.insert(sub_item_table, {
text = k,
hold_keep_menu_open = false,
sub_item_table = sub_items,
hold_callback = function()
Dispatcher:execute(self.data[k])
local settings = self.data[k].settings
if settings and settings.long_press_show_menu then
self:onProfileShowMenu(k)
else
self:onProfileExecute(k)
end
end,
})
end
return sub_item_table
end
function Profiles:newProfile(name)
if self.data[name] == nil then
self.data[name] = {}
self.updated = true
return true
function Profiles:onProfileExecute(name)
local profile = self.data[name]
if profile and profile.settings and profile.settings.actions_order then
self:syncOrder(name)
for _, action in ipairs(profile.settings.actions_order) do
Dispatcher:execute({[action] = profile[action]})
end
else
return false
Dispatcher:execute(profile)
end
end
function Profiles:deleteProfile(name)
self.data[name] = nil
self.updated = true
self:deleteAutostartProfile(name)
function Profiles:onProfileShowMenu(name)
if UIManager:getTopWidget() == name then return end
local profile = self.data[name]
local actions_list = self:getActionsList(name)
local quickmenu
local buttons = {}
for _, v in ipairs(actions_list) do
table.insert(buttons, {{
text = v.text,
align = "left",
font_face = "smallinfofont",
font_size = 22,
font_bold = false,
callback = function()
UIManager:close(quickmenu)
local action = v.label
Dispatcher:execute({[action] = profile[action]})
end,
}})
end
local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
quickmenu = ButtonDialogTitle:new{
name = name,
title = name,
title_align = "center",
width_factor = 0.8,
use_info_style = false,
buttons = buttons,
}
UIManager:show(quickmenu)
end
function Profiles:isAutostartProfile(name)
return G_reader_settings:has("autostart_profiles") and G_reader_settings:readSetting("autostart_profiles")[name] == true
function Profiles:sortActions(name, touchmenu_instance)
local profile = self.data[name]
local actions_list = self:getActionsList(name)
local SortWidget = require("ui/widget/sortwidget")
local sort_widget
sort_widget = SortWidget:new{
title = _("Sort actions"),
item_table = actions_list,
callback = function()
if profile.settings then
self.data[name].settings.actions_order = {}
else
self.data[name].settings = {["actions_order"] = {}}
end
for i, v in ipairs(sort_widget.item_table) do
self.data[name].settings.actions_order[i] = v.label
end
touchmenu_instance:updateItems()
self.updated = true
end
}
UIManager:show(sort_widget)
end
function Profiles:setAutostartProfile(name)
local autostart_table = G_reader_settings:has("autostart_profiles") and G_reader_settings:readSetting("autostart_profiles") or {}
autostart_table[name] = true
G_reader_settings:saveSetting("autostart_profiles", autostart_table)
function Profiles:editProfileName(editCallback, old_name)
local name_input
name_input = InputDialog:new{
title = _("Enter profile name"),
input = old_name,
buttons = {{
{
text = _("Cancel"),
callback = function()
UIManager:close(name_input)
end,
},
{
text = _("Save"),
callback = function()
local new_name = name_input:getInputText()
if new_name == "" or new_name == old_name then return end
UIManager:close(name_input)
if self.data[new_name] then
UIManager:show(InfoMessage:new{
text = T(_("Profile already exists: %1"), new_name),
})
else
editCallback(new_name)
end
end,
},
}},
}
UIManager:show(name_input)
name_input:onShowKeyboard()
end
function Profiles:deleteAutostartProfile(name)
local autostart_table = G_reader_settings:has("autostart_profiles") and G_reader_settings:readSetting("autostart_profiles") or {}
autostart_table[name] = nil
G_reader_settings:saveSetting("autostart_profiles", autostart_table)
function Profiles:getActionsList(name)
local profile = self.data[name]
local function getActionFullName (profile_name, action_name)
local location = {} -- make this as expected by Dispatcher:getNameFromItem()
if type(profile_name[action_name]) ~= "boolean" then
location[action_name] = {[action_name] = profile_name[action_name]}
end
return Dispatcher:getNameFromItem(action_name, location, action_name)
end
local actions_list = {}
if profile and profile.settings and profile.settings.actions_order then
self:syncOrder(name)
for _, action in ipairs(profile.settings.actions_order) do
table.insert(actions_list, {text = getActionFullName(profile, action), label = action})
end
else
for action in pairs(profile) do
if action ~= "settings" then
table.insert(actions_list, {text = getActionFullName(profile, action), label = action})
end
end
end
return actions_list
end
function Profiles:executeAutostart()
if not autostart_done then
self:loadProfiles()
local autostart_table = G_reader_settings:has("autostart_profiles") and G_reader_settings:readSetting("autostart_profiles") or {}
for autostart_profile_name, profile_enabled in pairs(autostart_table) do
if self.data[autostart_profile_name] and profile_enabled then
UIManager:nextTick(function()
Dispatcher:execute(self.data[autostart_profile_name])
end)
else
autostart_table[autostart_profile_name] = nil -- remove deleted profile form autostart_profile
G_reader_settings:saveSetting("autostart_profiles", autostart_table)
function Profiles:syncOrder(name)
local profile = self.data[name]
for i = #profile.settings.actions_order, 1, -1 do
if not profile[profile.settings.actions_order[i]] then
table.remove(self.data[name].settings.actions_order, i)
if not self.updated then
self.updated = true
end
end
autostart_done = true
end
for action in pairs(profile) do
if action ~= "settings" and not util.arrayContains(profile.settings.actions_order, action) then
table.insert(self.data[name].settings.actions_order, action)
if not self.updated then
self.updated = true
end
end
end
end
function Profiles:renameAutostart(old_name, new_name)
if G_reader_settings:getSettingForExt("autostart_profiles", old_name) then
G_reader_settings:saveSettingForExt("autostart_profiles", nil, old_name)
if new_name then
G_reader_settings:saveSettingForExt("autostart_profiles", true, new_name)
end
end
end
function Profiles:executeAutostart()
if autostart_done then return end
self:loadProfiles()
local autostart_table = G_reader_settings:readSetting("autostart_profiles") or {}
for autostart_profile_name, profile_enabled in pairs(autostart_table) do
if self.data[autostart_profile_name] and profile_enabled then
UIManager:nextTick(function()
Dispatcher:execute(self.data[autostart_profile_name])
end)
else
self:renameAutostart(autostart_profile_name) -- remove deleted profile from autostart_profile
end
end
autostart_done = true
end
return Profiles

Loading…
Cancel
Save