mirror of
https://github.com/koreader/koreader.git
synced 2025-12-18 12:02:09 +01:00
326 lines
15 KiB
Lua
326 lines
15 KiB
Lua
--[[--
|
|
This module provides a unified interface for managing presets across different KOReader modules.
|
|
It handles creation, loading, updating, and deletion of presets, as well as menu generation.
|
|
|
|
Usage:
|
|
local Presets = require("ui/presets")
|
|
|
|
-- 1. In your module's init() method, set up a preset object:
|
|
self.preset_obj = {
|
|
presets = G_reader_settings:readSetting("my_module_presets", {}), -- or custom storage
|
|
cycle_index = G_reader_settings:readSetting("my_module_presets_cycle_index"), -- optional, only needed if cycling through presets
|
|
dispatcher_name = "load_my_module_preset", -- must match dispatcher.lua entry
|
|
saveCycleIndex = function(this) -- Save cycle index to persistent storage
|
|
G_reader_settings:saveSetting("my_module_presets_cycle_index", this.cycle_index)
|
|
end,
|
|
buildPreset = function() return self:buildPreset() end, -- Closure to build a preset from current state
|
|
loadPreset = function(preset) self:loadPreset(preset) end, -- Closure to apply a preset to the module
|
|
}
|
|
|
|
-- 2. Implement required methods in your module:
|
|
function MyModule:buildPreset()
|
|
return {
|
|
-- Return a table with the settings you want to save in the preset
|
|
setting1 = self.setting1,
|
|
setting2 = self.setting2,
|
|
enabled_features = self.enabled_features,
|
|
}
|
|
end
|
|
|
|
function MyModule:loadPreset(preset)
|
|
-- Apply the preset settings to your module
|
|
self.setting1 = preset.setting1
|
|
self.setting2 = preset.setting2
|
|
self.enabled_features = preset.enabled_features
|
|
-- Update UI or perform other necessary changes
|
|
self:refresh()
|
|
end
|
|
|
|
-- 3. Create menu items for presets: (Alternatively, you could call Presets.genPresetMenuItemTable directly from touchmenu_instance)
|
|
function MyModule:genPresetMenuItemTable(touchmenu_instance)
|
|
return Presets.genPresetMenuItemTable(
|
|
self.preset_obj, -- preset object
|
|
_("Create new preset from current settings"), -- optional: custom text for UI menu
|
|
function() return self:hasValidSettings() end, -- optional: function to enable/disable creating presets
|
|
)
|
|
end
|
|
|
|
-- 4. Load a preset by name (for dispatcher/event handling):
|
|
function MyModule:onLoadMyModulePreset(preset_name)
|
|
return Presets.onLoadPreset(
|
|
self.preset_obj,
|
|
preset_name,
|
|
true -- show notification
|
|
)
|
|
end
|
|
|
|
-- 5. Cycle through presets (for dispatcher/event handling):
|
|
function MyModule:onCycleMyModulePresets()
|
|
return Presets.cycleThroughPresets(
|
|
self.preset_obj,
|
|
true -- show notification
|
|
)
|
|
end
|
|
|
|
-- 6. Get list of available presets (for dispatcher):
|
|
function MyModule.getPresets() -- Note: This is a static method on MyModule
|
|
local config = {
|
|
presets = G_reader_settings:readSetting("my_module_presets", {})
|
|
}
|
|
return Presets.getPresets(config)
|
|
end
|
|
|
|
-- 7. Add to dispatcher.lua:
|
|
load_my_module_preset = {
|
|
category = "string",
|
|
event = "LoadMyModulePreset",
|
|
title = _("Load my module preset"),
|
|
args_func = MyModule.getPresets,
|
|
reader = true
|
|
},
|
|
cycle_my_module_preset = {
|
|
category = "none",
|
|
event = "CycleMyModulePresets",
|
|
title = _("Cycle through my module presets"),
|
|
reader = true
|
|
},
|
|
|
|
Required preset_obj fields:
|
|
- presets: table containing saved presets
|
|
- cycle_index: current index for cycling through presets (optional, defaults to 0)
|
|
- dispatcher_name: string matching the dispatcher action name (for dispatcher integration)
|
|
- saveCycleIndex(this): function to save cycle index (optional, only needed if cycling is used)
|
|
|
|
Required module methods:
|
|
- buildPreset(): returns a table with the current settings to save as a preset
|
|
- loadPreset(preset): applies the settings from the preset table to the module
|
|
|
|
The preset system handles:
|
|
- Creating, updating, deleting, and renaming presets through UI dialogs
|
|
- Generating menu items with hold actions for preset management
|
|
- Saving/loading presets to/from G_reader_settings (or custom storage)
|
|
- Cycling through presets with wrap-around
|
|
- User notifications when presets are loaded/updated/created
|
|
- Integration with Dispatcher for gesture/hotkey/profile support
|
|
- Broadcasting events to update dispatcher when presets change
|
|
- Input validation and duplicate name prevention
|
|
--]]
|
|
|
|
local ConfirmBox = require("ui/widget/confirmbox")
|
|
local Event = require("ui/event")
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
local InputDialog = require("ui/widget/inputdialog")
|
|
local Notification = require("ui/widget/notification")
|
|
local UIManager = require("ui/uimanager")
|
|
local ffiUtil = require("ffi/util")
|
|
local T = require("ffi/util").template
|
|
local _ = require("gettext")
|
|
|
|
local Presets = {}
|
|
|
|
function Presets.editPresetName(options, preset_obj, on_success_callback)
|
|
local input_dialog
|
|
input_dialog = InputDialog:new{
|
|
title = options.title or _("Enter preset name"),
|
|
input = options.initial_value or "",
|
|
buttons = {
|
|
{
|
|
{
|
|
text = _("Cancel"),
|
|
id = "close",
|
|
callback = function()
|
|
UIManager:close(input_dialog)
|
|
end,
|
|
},
|
|
{
|
|
text = options.confirm_button_text or _("Create"),
|
|
is_enter_default = true,
|
|
callback = function()
|
|
local entered_preset_name = input_dialog:getInputText()
|
|
if entered_preset_name == "" or entered_preset_name:match("^%s*$") then
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Invalid preset name. Please choose a different name."),
|
|
timeout = 2,
|
|
})
|
|
return false
|
|
end
|
|
if options.initial_value and entered_preset_name == options.initial_value then
|
|
UIManager:close(input_dialog)
|
|
return false
|
|
end
|
|
if preset_obj.presets[entered_preset_name] then
|
|
UIManager:show(InfoMessage:new{
|
|
text = T(_("A preset named '%1' already exists. Please choose a different name."), entered_preset_name),
|
|
timeout = 2,
|
|
})
|
|
return false
|
|
end
|
|
|
|
-- If all validation passes, call the success callback
|
|
on_success_callback(entered_preset_name)
|
|
UIManager:close(input_dialog)
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
UIManager:show(input_dialog)
|
|
input_dialog:onShowKeyboard()
|
|
end
|
|
|
|
function Presets.genPresetMenuItemTable(preset_obj, text, enabled_func)
|
|
local presets = preset_obj.presets
|
|
local items = {
|
|
{
|
|
text = text or _("Create new preset from current settings"),
|
|
keep_menu_open = true,
|
|
enabled_func = enabled_func,
|
|
callback = function(touchmenu_instance)
|
|
Presets.editPresetName({}, preset_obj,
|
|
function(entered_preset_name)
|
|
local preset_data = preset_obj.buildPreset()
|
|
preset_obj.presets[entered_preset_name] = preset_data
|
|
touchmenu_instance.item_table = Presets.genPresetMenuItemTable(preset_obj, text, enabled_func)
|
|
touchmenu_instance:updateItems()
|
|
end
|
|
)
|
|
end,
|
|
separator = true,
|
|
},
|
|
}
|
|
for preset_name in ffiUtil.orderedPairs(presets) do
|
|
table.insert(items, {
|
|
text = preset_name,
|
|
keep_menu_open = true,
|
|
callback = function()
|
|
preset_obj.loadPreset(presets[preset_name])
|
|
-- There's no guarantee that it'll be obvious to the user that the preset was loaded so, we show a notification.
|
|
UIManager:show(InfoMessage:new{
|
|
text = T(_("Preset '%1' loaded successfully."), preset_name),
|
|
timeout = 2,
|
|
})
|
|
end,
|
|
hold_callback = function(touchmenu_instance, item)
|
|
UIManager:show(ConfirmBox:new{
|
|
text = T(_("What would you like to do with preset '%1'?"), preset_name),
|
|
icon = "notice-question",
|
|
ok_text = _("Update"),
|
|
ok_callback = function()
|
|
UIManager:show(ConfirmBox:new{
|
|
text = T(_("Are you sure you want to overwrite preset '%1' with current settings?"), preset_name),
|
|
ok_callback = function()
|
|
presets[preset_name] = preset_obj.buildPreset()
|
|
UIManager:show(InfoMessage:new{
|
|
text = T(_("Preset '%1' was updated with current settings"), preset_name),
|
|
timeout = 2,
|
|
})
|
|
end,
|
|
})
|
|
end,
|
|
other_buttons_first = true,
|
|
other_buttons = {
|
|
{
|
|
{
|
|
text = _("Delete"),
|
|
callback = function()
|
|
UIManager:show(ConfirmBox:new{
|
|
text = T(_("Are you sure you want to delete preset '%1'?"), preset_name),
|
|
ok_text = _("Delete"),
|
|
ok_callback = function()
|
|
presets[preset_name] = nil
|
|
local action_key = preset_obj.dispatcher_name
|
|
if action_key then
|
|
UIManager:broadcastEvent(Event:new("DispatcherActionValueChanged", {
|
|
name = action_key,
|
|
old_value = preset_name,
|
|
new_value = nil -- delete the action
|
|
}))
|
|
end
|
|
table.remove(touchmenu_instance.item_table, item.idx)
|
|
touchmenu_instance:updateItems()
|
|
end,
|
|
})
|
|
end,
|
|
},
|
|
{
|
|
text = _("Rename"),
|
|
callback = function()
|
|
Presets.editPresetName({
|
|
title = _("Enter new preset name"),
|
|
initial_value = preset_name,
|
|
confirm_button_text = _("Rename"),
|
|
}, preset_obj,
|
|
function(new_name)
|
|
presets[new_name] = presets[preset_name]
|
|
presets[preset_name] = nil
|
|
local action_key = preset_obj.dispatcher_name
|
|
if action_key then
|
|
UIManager:broadcastEvent(Event:new("DispatcherActionValueChanged", {
|
|
name = action_key,
|
|
old_value = preset_name,
|
|
new_value = new_name
|
|
}))
|
|
end
|
|
touchmenu_instance.item_table = Presets.genPresetMenuItemTable(preset_obj, text, enabled_func)
|
|
touchmenu_instance:updateItems()
|
|
end) -- editPresetName
|
|
end, -- rename callback
|
|
},
|
|
},
|
|
}, -- end of other_buttons
|
|
}) -- end of ConfirmBox
|
|
end, -- hold_callback
|
|
}) -- end of table.insert
|
|
end -- for each preset
|
|
return items
|
|
end
|
|
|
|
|
|
function Presets.onLoadPreset(preset_obj, preset_name, show_notification)
|
|
local presets = preset_obj.presets
|
|
if presets and presets[preset_name] then
|
|
preset_obj.loadPreset(presets[preset_name])
|
|
if show_notification then
|
|
Notification:notify(T(_("Preset '%1' was loaded"), preset_name))
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function Presets.cycleThroughPresets(preset_obj, show_notification)
|
|
local preset_names = Presets.getPresets(preset_obj)
|
|
if #preset_names == 0 then
|
|
Notification:notify(_("No presets available"), Notification.SOURCE_ALWAYS_SHOW)
|
|
return true -- we *must* return true here to prevent further event propagation, i.e multiple notifications
|
|
end
|
|
-- Get and increment index, wrap around if needed
|
|
local index = (preset_obj.cycle_index or 0) + 1
|
|
if index > #preset_names then
|
|
index = 1
|
|
end
|
|
local next_preset_name = preset_names[index]
|
|
preset_obj.loadPreset(preset_obj.presets[next_preset_name])
|
|
preset_obj.cycle_index = index
|
|
preset_obj:saveCycleIndex()
|
|
if show_notification then
|
|
Notification:notify(T(_("Loaded preset: %1"), next_preset_name))
|
|
end
|
|
return true
|
|
end
|
|
|
|
function Presets.getPresets(preset_obj)
|
|
local presets = preset_obj.presets
|
|
local actions = {}
|
|
if presets and next(presets) then
|
|
for preset_name in pairs(presets) do
|
|
table.insert(actions, preset_name)
|
|
end
|
|
if #actions > 1 then
|
|
table.sort(actions)
|
|
end
|
|
end
|
|
return actions, actions
|
|
end
|
|
|
|
return Presets
|