local CenterContainer = require("ui/widget/container/centercontainer") local ConfirmBox = require("ui/widget/confirmbox") local DataStorage = require("datastorage") local Device = require("device") local Font = require("ui/font") local InfoMessage = require("ui/widget/infomessage") local InputContainer = require("ui/widget/container/inputcontainer") local InputDialog = require("ui/widget/inputdialog") local Menu = require("ui/widget/menu") local MultiInputDialog = require("ui/widget/multiinputdialog") local Size = require("ui/size") local UIManager = require("ui/uimanager") local dump = require("dump") local isAndroid, android = pcall(require, "android") local logger = require("logger") local util = require("ffi/util") local _ = require("gettext") local Screen = require("device").screen local is_appimage = os.getenv("APPIMAGE") local function getDefaultsPath() local defaults_path = DataStorage:getDataDir() .. "/defaults.lua" if isAndroid then defaults_path = android.dir .. "/defaults.lua" elseif is_appimage then defaults_path = "defaults.lua" end return defaults_path end local defaults_path = getDefaultsPath() local persistent_defaults_path = DataStorage:getDataDir() .. "/defaults.persistent.lua" local SetDefaults = InputContainer:new{ defaults_name = {}, defaults_value = {}, results = {}, defaults_menu = {}, changed = {}, settings_changed = false, } function SetDefaults:ConfirmEdit() if not SetDefaults.EditConfirmed then UIManager:show(ConfirmBox:new{ text = _("Some changes will not work until the next restart. Be careful; the wrong settings might crash KOReader!\nAre you sure you want to continue?"), ok_callback = function() self.EditConfirmed = true self:init() end, }) else self:init() end end function SetDefaults:init() self.screen_width = Screen:getWidth() self.screen_height = Screen:getHeight() self.dialog_width = math.floor(math.min(self.screen_width, self.screen_height) * 0.95) self.results = {} local defaults = {} local load_defaults = loadfile(defaults_path) setfenv(load_defaults, defaults) load_defaults() local file = io.open(persistent_defaults_path, "r") if file ~= nil then file:close() load_defaults = loadfile(persistent_defaults_path) setfenv(load_defaults, defaults) load_defaults() end local idx = 1 for n, v in util.orderedPairs(defaults) do self.defaults_name[idx] = n self.defaults_value[idx] = v idx = idx + 1 end local menu_container = CenterContainer:new{ dimen = Screen:getSize(), } --- @fixme -- in this use case (an input dialog is closed and the menu container is -- opened immediately) we need to set the full screen dirty because -- otherwise only the input dialog part of the screen is refreshed. menu_container.onShow = function() UIManager:setDirty(nil, "ui") end self.defaults_menu = Menu:new{ width = self.screen_width - (Size.margin.fullscreen_popout * 2), height = self.screen_height - (Size.margin.fullscreen_popout * 2), cface = Font:getFace("smallinfofont"), show_parent = menu_container, _manager = self, } -- Prevent menu from closing when editing a value function self.defaults_menu:onMenuSelect(item) item.callback() end table.insert(menu_container, self.defaults_menu) self.defaults_menu.close_callback = function() logger.dbg("Closing defaults menu") self:saveBeforeExit() UIManager:close(menu_container) end local cancel_button = { text = _("Cancel"), enabled = true, callback = function() self:close() end, } for i=1, #self.defaults_name do self.changed[i] = false local setting_name = self.defaults_name[i] local setting_type = type(_G[setting_name]) if setting_type == "boolean" then local editBoolean = function() self.set_dialog = InputDialog:new{ title = setting_name, input = tostring(self.defaults_value[i]), buttons = { { cancel_button, { text = "true", enabled = true, callback = function() self.defaults_value[i] = true _G[setting_name] = true self.settings_changed = true self.changed[i] = true self.results[i].text = self:build_setting(i) self:close() self.defaults_menu:switchItemTable("Defaults", self.results, i) end }, { text = "false", enabled = true, callback = function() self.defaults_value[i] = false _G[setting_name] = false self.settings_changed = true self.changed[i] = true self.results[i].text = self:build_setting(i) self.defaults_menu:switchItemTable("Defaults", self.results, i) self:close() end }, }, }, input_type = setting_type, width = self.dialog_width, } UIManager:show(self.set_dialog) self.set_dialog:onShowKeyboard() end table.insert(self.results, { text = self:build_setting(i), callback = editBoolean }) elseif setting_type == "table" then local editTable = function() local fields = {} for k, v in util.orderedPairs(_G[setting_name]) do table.insert(fields, { text = tostring(k) .. " = " .. tostring(v), hint = "", padding = Screen:scaleBySize(2), margin = Screen:scaleBySize(2), }) end self.set_dialog = MultiInputDialog:new{ title = setting_name, fields = fields, buttons = { { cancel_button, { text = _("OK"), enabled = true, is_enter_default = true, callback = function() local new_table = {} for _, field in ipairs(MultiInputDialog:getFields()) do local key, value = field:match("^[^= ]+"), field:match("[^= ]+$") new_table[tonumber(key) or key] = tonumber(value) or value end _G[setting_name] = new_table self.defaults_value[i] = _G[setting_name] self.settings_changed = true self.changed[i] = true self.results[i].text = self:build_setting(i) self:close() self.defaults_menu:switchItemTable("Defaults", self.results, i) end, }, }, }, width = self.dialog_width, } UIManager:show(self.set_dialog) self.set_dialog:onShowKeyboard() end table.insert(self.results, { text = self:build_setting(i), callback = editTable }) else local editNumStr = function() self.set_dialog = InputDialog:new{ title = setting_name, input = tostring(self.defaults_value[i]), buttons = { { cancel_button, { text = _("OK"), is_enter_default = true, enabled = true, callback = function() local new_value = self.set_dialog:getInputValue() if _G[setting_name] ~= new_value then _G[setting_name] = new_value self.defaults_value[i] = new_value self.settings_changed = true self.changed[i] = true self.results[i].text = self:build_setting(i) end self:close() self.defaults_menu:switchItemTable("Defaults", self.results, i) end, }, }, }, input_type = setting_type, width = self.dialog_width, } UIManager:show(self.set_dialog) self.set_dialog:onShowKeyboard() end table.insert(self.results, { text = self:build_setting(i), callback = editNumStr }) end end self.defaults_menu:switchItemTable("Defaults", self.results) UIManager:show(menu_container) end function SetDefaults:close() UIManager:close(self.set_dialog) end function SetDefaults:ConfirmSave() UIManager:show(ConfirmBox:new{ text = _('Are you sure you want to save the settings to "defaults.persistent.lua"?'), ok_callback = function() self:saveSettings() end, }) end function SetDefaults:build_setting(j) local setting_name = self.defaults_name[j] local ret = setting_name .. " = " if type(_G[setting_name]) == "boolean" then return ret .. tostring(self.defaults_value[j]) elseif type(_G[setting_name]) == "table" then return ret .. "{...}" elseif tonumber(self.defaults_value[j]) then return ret .. tostring(tonumber(self.defaults_value[j])) else return ret .. "\"" .. tostring(self.defaults_value[j]) .. "\"" end end function SetDefaults:saveSettings() self.results = {} local persisted_defaults = {} local file = io.open(persistent_defaults_path, "r") if file ~= nil then file:close() local load_defaults = loadfile(persistent_defaults_path) setfenv(load_defaults, persisted_defaults) load_defaults() end local checked = {} for j=1, #self.defaults_name do if not self.changed[j] then checked[j] = true end end -- load default value for defaults local defaults = {} local load_defaults = loadfile(defaults_path) setfenv(load_defaults, defaults) load_defaults() -- handle case "found in persistent" and changed, replace/delete it for k, v in pairs(persisted_defaults) do for j=1, #self.defaults_name do if not checked[j] and k == self.defaults_name[j] then -- remove from persist if value got reverted back to the -- default one if defaults[k] == self.defaults_value[j] then persisted_defaults[k] = nil else persisted_defaults[k] = self.defaults_value[j] end checked[j] = true end end end -- handle case "not in persistent and different in non-persistent", add to -- persistent for j=1, #self.defaults_name do if not checked[j] then persisted_defaults[self.defaults_name[j]] = self.defaults_value[j] end end file = io.open(persistent_defaults_path, "w") if file then file:write("-- For configuration changes that persists between updates\n") for k, v in pairs(persisted_defaults) do local line = {} table.insert(line, k) table.insert(line, " = ") table.insert(line, dump(v)) table.insert(line, "\n") file:write(table.concat(line)) end file:close() UIManager:show(InfoMessage:new{ text = _("Default settings saved."), }) end self.settings_changed = false end function SetDefaults:saveBeforeExit(callback) local save_text = _("Save and quit") if Device:canRestart() then save_text = _("Save and restart") end if self.settings_changed then UIManager:show(ConfirmBox:new{ text = _("KOReader needs to be restarted to apply the new default settings."), ok_text = save_text, ok_callback = function() self.settings_changed = false self:saveSettings() if Device:canRestart() then UIManager:restartKOReader() else UIManager:quit() end end, cancel_text = _("Discard changes"), cancel_callback = function() logger.info("discard defaults") pcall(dofile, defaults_path) pcall(dofile, persistent_defaults_path) self.settings_changed = false end, }) end end return SetDefaults