From a9d4e97115b37a4a8a45aa3cbf84c04d974d0c65 Mon Sep 17 00:00:00 2001 From: yparitcher Date: Fri, 19 Jun 2020 17:38:53 -0400 Subject: [PATCH] [plugin] Dispatcher: for profiles & gestures (#6106) add a Dispatcher module that allows for dispatching multiple events at once. This will allow for profiles & for gestures that do multiple things. it has 2 methods: Execute which is given a kv table of settings to change and fires an event for each of them. addSubMenu adds a menu item to a menu to allow for modifying which events are called it also has settingsList which is a master table of all allowed setting and their corresponding info (it is mostly from ReaderGesture and needs a lot of work) to allow for a new setting all one has to do is add a entry to settingsList with a corresponding event and it will work out of the box. the profile plugin is right now still a stub, just to test Dispatcher. the plan is to finish it and eventually refactor ReaderGesture to rely on this. This also needs effort to move many functions out of reader gesture into events where they belong. --- frontend/dispatcher.lua | 278 +++++++++++++++++++++ frontend/ui/elements/reader_menu_order.lua | 1 + plugins/profiles.koplugin/_meta.lua | 6 + plugins/profiles.koplugin/main.lua | 133 ++++++++++ 4 files changed, 418 insertions(+) create mode 100644 frontend/dispatcher.lua create mode 100644 plugins/profiles.koplugin/_meta.lua create mode 100644 plugins/profiles.koplugin/main.lua diff --git a/frontend/dispatcher.lua b/frontend/dispatcher.lua new file mode 100644 index 000000000..408b0f327 --- /dev/null +++ b/frontend/dispatcher.lua @@ -0,0 +1,278 @@ +local CreOptions = require("ui/data/creoptions") +local Event = require("ui/event") +local Screen = require("device").screen +local UIManager = require("ui/uimanager") +local T = require("ffi/util").template +local _ = require("gettext") + +local Dispatcher = { + initialized = false, +} + +--[[-- +contains a list of a dispatchable settings +each setting contains: + category: one of none, toggle, absolutenumber, incrementalnumber, or string. + event: what to call. + title: for use in ui. +and optionally + min/max: for number + default + args: allowed values for string. + toggle: display name for args +--]]-- +local settingsList = { + --CreOptions + screen_mode = {category="string"}, + visible_pages = {category="string"}, + h_page_margins = {category="string"}, + sync_t_b_page_margins = {category="string"}, + t_page_margin = {category="absolutenumber"}, + b_page_margin = {category="absolutenumber"}, + view_mode = {category="string", title="View Mode (CRengine)"}, + block_rendering_mode = {category="string"}, + render_dpi = {category="string"}, + line_spacing = {category="absolutenumber"}, + font_size = {category="absolutenumber", title="Font Size (CRengine)"}, + font_weight = {category="string"}, + --font_gamma = {category="string"}, + font_hinting = {category="string"}, + font_kerning = {category="string"}, + status_line = {category="string"}, + embedded_css = {category="string"}, + embedded_fonts = {category="string"}, + smooth_scaling = {category="string"}, + nightmode_images = {category="string"}, +} + +--[[-- + add settings from CreOptions / KoptOptions +--]]-- +function Dispatcher:init() + local parseoptions = function(base, i) + for y=1,#base[i].options do + local option = base[i].options[y] + if settingsList[option.name] ~= nil then + if settingsList[option.name].event == nil then + settingsList[option.name].event = option.event + end + if settingsList[option.name].title == nil then + settingsList[option.name].title = option.name_text + end + if settingsList[option.name].category == "string" then + if settingsList[option.name].toggle == nil then + settingsList[option.name].toggle = option.toggle or option.labels or option.values + for z=1,#settingsList[option.name].toggle do + if type(settingsList[option.name].toggle[z]) == "table" then + settingsList[option.name].toggle[z] = settingsList[option.name].toggle[z][1] + end + end + end + if settingsList[option.name].args == nil then + settingsList[option.name].args = option.args or option.values + end + elseif settingsList[option.name].category == "absolutenumber" then + if settingsList[option.name].min == nil then + settingsList[option.name].min = option.args[1] + end + if settingsList[option.name].max == nil then + settingsList[option.name].max = option.args[#option.args] + end + if settingsList[option.name].default == nil then + settingsList[option.name].default = option.default_value + end + end + end + end + end + for i=1,#CreOptions do + parseoptions(CreOptions, i) + end + Dispatcher.initialized = true +end + +--[[-- +Add a submenu to edit which items are dispatched +arguments are: + 1) self + 2) the table representing the submenu (can be empty) + 3) the name of the parent of the settings table (must be a child of self) + 4) the name of the settings table +example usage: + Dispatcher.addSubMenu(self, sub_items, "profiles", "profile1") +--]]-- +function Dispatcher:addSubMenu(menu, location, settings) + if not Dispatcher.initialized then Dispatcher:init() end + table.insert(menu, { + text = _("None"), + separator = true, + checked_func = function() + return next(self[location][settings]) == nil + end, + callback = function(touchmenu_instance) + self[location][settings] = {} + if touchmenu_instance then touchmenu_instance:updateItems() end + end, + }) + for k, v in pairs(settingsList) do + if settingsList[k].category == "none" then + table.insert(menu, { + text = settingsList[k].title, + checked_func = function() + return self[location][settings] ~= nil and self[location][settings][k] ~= nil + end, + callback = function(touchmenu_instance) + if self[location][settings] ~= nil + and self[location][settings][k] then + self[location][settings][k] = nil + else + self[location][settings][k] = true + end + if touchmenu_instance then touchmenu_instance:updateItems() end + end, + }) + elseif settingsList[k].category == "toggle" then + table.insert(menu, { + text_func = function() + return T(settingsList[k].title, self[location][settings][k]) + end, + checked_func = function() + return self[location][settings] ~= nil and self[location][settings][k] ~= nil + end, + callback = function(touchmenu_instance) + self[location][settings][k] = not self[location][settings][k] + if touchmenu_instance then touchmenu_instance:updateItems() end + end, + hold_callback = function(touchmenu_instance) + self[location][settings][k] = nil + if touchmenu_instance then touchmenu_instance:updateItems() end + end, + }) + elseif settingsList[k].category == "absolutenumber" then + table.insert(menu, { + text_func = function() + return T(settingsList[k].title, self[location][settings][k] or "") + end, + checked_func = function() + return self[location][settings] ~= nil and self[location][settings][k] ~= nil + end, + callback = function(touchmenu_instance) + local SpinWidget = require("ui/widget/spinwidget") + local items = SpinWidget:new{ + width = Screen:getWidth() * 0.6, + value = self[location][settings][k] or settingsList[k].default or 0, + value_min = settingsList[k].min, + value_step = 1, + value_hold_step = 2, + value_max = settingsList[k].max, + default_value = 0, + title_text = T(settingsList[k].title, self[location][settings][k] or ""), + callback = function(spin) + self[location][settings][k] = spin.value + if touchmenu_instance then + touchmenu_instance:updateItems() + end + end + } + UIManager:show(items) + end, + hold_callback = function(touchmenu_instance) + self[location][settings][k] = nil + if touchmenu_instance then touchmenu_instance:updateItems() end + end, + }) + elseif settingsList[k].category == "incrementalnumber" then + table.insert(menu, { + text_func = function() + return T(settingsList[k].title, self[location][settings][k] or "") + end, + checked_func = function() + return self[location][settings] ~= nil and self[location][settings][k] ~= nil + end, + callback = function(touchmenu_instance) + local SpinWidget = require("ui/widget/spinwidget") + local items = SpinWidget:new{ + width = Screen:getWidth() * 0.6, + value = self[location][settings][k] or 0, + value_min = settingsList[k].min, + value_step = 1, + value_hold_step = 2, + value_max = settingsList[k].max, + default_value = 0, + title_text = T(settingsList[k].title, self[location][settings][k] or ""), + text = _([[If set to 0 and called by a gesture the amount of the gesture will be used]]), + callback = function(spin) + self[location][settings][k] = spin.value + if touchmenu_instance then + touchmenu_instance:updateItems() + end + end + } + UIManager:show(items) + end, + hold_callback = function(touchmenu_instance) + self[location][settings][k] = nil + if touchmenu_instance then + touchmenu_instance:updateItems() + end + end, + }) + elseif settingsList[k].category == "string" then + local sub_item_table = {} + for i=1,#settingsList[k].args do + table.insert(sub_item_table, { + text = tostring(settingsList[k].toggle[i]), + checked_func = function() + return self[location][settings] ~= nil + and self[location][settings][k] ~= nil + and self[location][settings][k] == settingsList[k].args[i] + end, + callback = function() + self[location][settings][k] = settingsList[k].args[i] + end, + }) + end + table.insert(menu, { + text_func = function() + return T(settingsList[k].title, self[location][settings][k]) + end, + checked_func = function() + return self[location][settings] ~= nil + and self[location][settings][k] ~= nil + end, + sub_item_table = sub_item_table, + keep_menu_open = true, + hold_callback = function(touchmenu_instance) + self[location][settings][k] = nil + if touchmenu_instance then + touchmenu_instance:updateItems() + end + end, + }) + end + end +end + +function Dispatcher:execute(settings, gesture) + for k, v in pairs(settings) do + if settingsList[k].conditions == nil or settingsList[k].conditions == true then + if settingsList[k].category == "none" then + self.ui:handleEvent(Event:new(settingsList[k].event)) + end + if settingsList[k].category == "toggle" + or settingsList[k].category == "absolutenumber" + or settingsList[k].category == "string" then + self.ui:handleEvent(Event:new(settingsList[k].event, v)) + end + if settingsList[k].category == "incrementalnumber" then + if v then + self.ui:handleEvent(Event:new(settingsList[k].event, v)) + else + self.ui:handleEvent(Event:new(settingsList[k].event, gesture)) + end + end + end + end +end + +return Dispatcher diff --git a/frontend/ui/elements/reader_menu_order.lua b/frontend/ui/elements/reader_menu_order.lua index f2d4ac996..3965a2790 100644 --- a/frontend/ui/elements/reader_menu_order.lua +++ b/frontend/ui/elements/reader_menu_order.lua @@ -124,6 +124,7 @@ local order = { "news_downloader", "send2ebook", "text_editor", + "profiles", "----------------------------", "more_tools", }, diff --git a/plugins/profiles.koplugin/_meta.lua b/plugins/profiles.koplugin/_meta.lua new file mode 100644 index 000000000..ccba372c4 --- /dev/null +++ b/plugins/profiles.koplugin/_meta.lua @@ -0,0 +1,6 @@ +local _ = require("gettext") +return { + name = 'profiles', + fullname = _("Profiles"), + description = _([[This plugin allows combining multiple settings to make switchable 'profiles'.]]), +} diff --git a/plugins/profiles.koplugin/main.lua b/plugins/profiles.koplugin/main.lua new file mode 100644 index 000000000..11ffcd11a --- /dev/null +++ b/plugins/profiles.koplugin/main.lua @@ -0,0 +1,133 @@ +local ConfirmBox = require("ui/widget/confirmbox") +local DataStorage = require("datastorage") +local Dispatcher = require("dispatcher") +local InfoMessage = require("ui/widget/infomessage") +local InputDialog = require("ui/widget/inputdialog") +local LuaSettings = require("luasettings") +local UIManager = require("ui/uimanager") +local WidgetContainer = require("ui/widget/container/widgetcontainer") +local _ = require("gettext") +local T = require("ffi/util").template + +local Profiles = WidgetContainer:new{ + name = "profiles", + is_doc_only = true, + profiles_file = DataStorage:getSettingsDir() .. "/profiles.lua", + profiles = nil, + data = nil, +} + +function Profiles:init() + self.ui.menu:registerToMainMenu(self) +end + +function Profiles:loadProfiles() + if self.profiles then + return + end + self.profiles = LuaSettings:open(self.profiles_file) + self.data = self.profiles.data +end + +function Profiles:onFlushSettings() + if self.profiles then + self.profiles:flush() + end +end + +function Profiles:addToMainMenu(menu_items) + menu_items.profiles = { + text = _("Profiles"), + sub_item_table_func = function() + return self:getSubMenuItems() + end, + } +end + +function Profiles:getSubMenuItems() + self:loadProfiles() + local sub_item_table = { + { + 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"), + 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() + end, + separator = true, + } + } + for k,v in pairs(self.data) do + local sub_items = { + { + text = _("Delete profile"), + keep_menu_open = false, + separator = true, + 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, + }) + end, + } + } + Dispatcher.addSubMenu(self, sub_items, "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, v) + end, + }) + end + return sub_item_table +end + +function Profiles:newProfile(name) + if self.data[name] == nil then + self.data[name] = {} + return true + else + return false + end +end + +function Profiles:deleteProfile(name) + self.data[name] = nil +end + +return Profiles