diff --git a/frontend/ui/widget/button.lua b/frontend/ui/widget/button.lua index 1d62233e9..722d740fd 100644 --- a/frontend/ui/widget/button.lua +++ b/frontend/ui/widget/button.lua @@ -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 diff --git a/frontend/ui/widget/buttondialogtitle.lua b/frontend/ui/widget/buttondialogtitle.lua index 6dd615f5d..7156a6b9d 100644 --- a/frontend/ui/widget/buttondialogtitle.lua +++ b/frontend/ui/widget/buttondialogtitle.lua @@ -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, diff --git a/frontend/ui/widget/buttontable.lua b/frontend/ui/widget/buttontable.lua index edce8175e..072b01a84 100644 --- a/frontend/ui/widget/buttontable.lua +++ b/frontend/ui/widget/buttontable.lua @@ -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 diff --git a/plugins/profiles.koplugin/main.lua b/plugins/profiles.koplugin/main.lua index b3708152d..8fc25f020 100644 --- a/plugins/profiles.koplugin/main.lua +++ b/plugins/profiles.koplugin/main.lua @@ -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