Files
koreader-mirror/plugins/profiles.koplugin/main.lua

1230 lines
55 KiB
Lua

local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage")
local DateTimeWidget = require("ui/widget/datetimewidget")
local Device = require("device")
local Dispatcher = require("dispatcher")
local Event = require("ui/event")
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 ffiUtil = require("ffi/util")
local logger = require("logger")
local util = require("util")
local _ = require("gettext")
local C_ = _.pgettext
local Screen = Device.screen
local T = ffiUtil.template
local autostart_done
local Profiles = WidgetContainer:extend{
name = "profiles",
prefix = "profile_exec_",
profiles_file = DataStorage:getSettingsDir() .. "/profiles.lua",
profiles = nil,
data = nil,
updated = false,
}
function Profiles:init()
Dispatcher:init()
self.autoexec = G_reader_settings:readSetting("profiles_autoexec", {})
self.ui.menu:registerToMainMenu(self)
self:onDispatcherRegisterActions()
self:onStart()
end
function Profiles:loadProfiles()
if self.profiles then
return
end
self.profiles = LuaSettings:open(self.profiles_file)
self.data = self.profiles.data
-- ensure profile name
for k, v in pairs(self.data) do
if not v.settings then
v.settings = {}
end
if not v.settings.name then
v.settings.name = k
self.updated = true
end
end
self:onFlushSettings()
end
function Profiles:onFlushSettings()
if self.profiles and self.updated then
self.profiles:flush()
self.updated = false
end
end
local function dispatcherRegisterProfile(name)
Dispatcher:registerAction(Profiles.prefix..name,
{category="none", event="ProfileExecute", arg=name, title=T(_("Profile %1"), name), general=true})
end
local function dispatcherUnregisterProfile(name)
Dispatcher:removeAction(Profiles.prefix..name)
end
function Profiles:onDispatcherRegisterActions()
self:loadProfiles()
for k, v in pairs(self.data) do
if v.settings.registered then
dispatcherRegisterProfile(k)
end
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 function editCallback(new_name)
self.data[new_name] = { settings = { name = new_name } }
self.updated = true
touchmenu_instance.item_table = self:getSubMenuItems()
touchmenu_instance.page = 1
touchmenu_instance:updateItems()
end
self:editProfileName(editCallback)
end,
},
{
text = _("New with current book settings"),
enabled = self.document ~= nil,
keep_menu_open = true,
callback = function(touchmenu_instance)
local function editCallback(new_name)
self.data[new_name] = self:getProfileFromCurrentBookSettings(new_name)
self.updated = true
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
local sub_items = {
ignored_by_menu_search = true,
{
text = _("Execute"),
callback = function(touchmenu_instance)
touchmenu_instance:onClose()
self:onProfileExecute(k, { qm_show = false })
end,
},
{
text = _("Show as QuickMenu"),
callback = function(touchmenu_instance)
touchmenu_instance:onClose()
self:onProfileExecute(k, { qm_show = true })
end,
},
{
text = _("Auto-execute"),
checked_func = function()
for _, profiles in pairs(self.autoexec) do
if profiles[k] then
return true
end
end
end,
sub_item_table_func = function()
return {
{
text = _("Ask to execute"),
checked_func = function()
return v.settings.auto_exec_ask
end,
callback = function()
v.settings.auto_exec_ask = not v.settings.auto_exec_ask or nil
self.updated = true
end,
},
{
text_func = function()
local txt
if v.settings.auto_exec_promptly then
txt = _("promptly")
elseif v.settings.auto_exec_delay then
txt = string.format("%0.1f", v.settings.auto_exec_delay) .. " " .. C_("Time", "s")
else
txt = _("default")
end
return T(_("Executing delay: %1"), txt)
end,
checked_func = function()
return v.settings.auto_exec_promptly or v.settings.auto_exec_delay ~= nil
end,
sub_item_table_func = function()
return {
{
text = _("promptly"),
help_text =
_([[Enable this option to execute the profile before some other operations triggered by the event.
For example, with a trigger "on document closing" the profile will be executed before the document is closed.]]),
checked_func = function()
return v.settings.auto_exec_promptly
end,
radio = true,
callback = function()
if not v.settings.auto_exec_promptly then
v.settings.auto_exec_promptly = true
v.settings.auto_exec_delay = nil
self.updated = true
end
end,
},
{
text = _("default"),
checked_func = function()
return not (v.settings.auto_exec_promptly or v.settings.auto_exec_delay)
end,
radio = true,
callback = function()
if v.settings.auto_exec_promptly or v.settings.auto_exec_delay then
v.settings.auto_exec_promptly = nil
v.settings.auto_exec_delay = nil
self.updated = true
end
end,
},
{
text_func = function()
return v.settings.auto_exec_delay
and string.format("%0.1f", v.settings.auto_exec_delay) .. " " .. C_("Time", "s")
or _("custom")
end,
checked_func = function()
return v.settings.auto_exec_delay ~= nil
end,
radio = true,
callback = function(touchmenu_instance)
local SpinWidget = require("ui/widget/spinwidget")
UIManager:show(SpinWidget:new{
title_text = _("Executing delay"),
value = v.settings.auto_exec_delay or 3,
value_min = 1,
value_max = 10,
value_hold_step = 0.1,
precision = "%0.1f",
unit = C_("Time", "s"),
ok_always_enabled = true,
callback = function(spin)
v.settings.auto_exec_promptly = nil
v.settings.auto_exec_delay = spin.value
self.updated = true
touchmenu_instance:updateItems()
end,
})
end,
},
}
end,
hold_callback = function(touchmenu_instance)
v.settings.auto_exec_promptly = nil
v.settings.auto_exec_delay = nil
self.updated = true
touchmenu_instance:updateItems()
end,
},
{
text_func = function()
local interval = v.settings.auto_exec_time_interval
return _("Only within time interval") ..
(interval and ": " .. interval[1] .. " - " .. interval[2] or "")
end,
checked_func = function()
return v.settings.auto_exec_time_interval and true
end,
sub_item_table_func = function()
local sub_sub_item_table = {}
local points = { _("start: "), _("end: ") }
local titles = { _("Set start time"), _("Set end time") }
for i, point in ipairs(points) do
sub_sub_item_table[i] = {
text_func = function()
local interval = v.settings.auto_exec_time_interval
return point .. (interval and interval[i] or "--:--")
end,
keep_menu_open = true,
callback = function(touchmenu_instance)
local interval = v.settings.auto_exec_time_interval
local time_str = interval and interval[i] or os.date("%H:%M")
local h, m = time_str:match("(%d+):(%d+)")
UIManager:show(DateTimeWidget:new{
title_text = titles[i],
info_text = _("Enter time in hours and minutes."),
hour = tonumber(h),
min = tonumber(m),
ok_text = _("Set time"),
callback = function(new_time)
local str = string.format("%02d:%02d", new_time.hour, new_time.min)
if interval then
v.settings.auto_exec_time_interval[i] = str
else
v.settings.auto_exec_time_interval = { str, str }
end
self.updated = true
touchmenu_instance:updateItems()
end,
})
end,
}
end
return sub_sub_item_table
end,
hold_callback = function(touchmenu_instance)
v.settings.auto_exec_time_interval = nil
self.updated = true
touchmenu_instance:updateItems()
end,
separator = true,
},
self:genAutoExecMenuItem(_("on KOReader start"), "Start", k),
self:genAutoExecMenuItem(_("on wake-up"), "Resume", k),
self:genAutoExecMenuItem(_("on exiting sleep screen"), "OutOfScreenSaver", k),
self:genAutoExecMenuItem(_("on read timer expiry"), "ReadTimerExpired", k),
self:genAutoExecMenuItem(_("on rotation"), "SetRotationMode", k),
self:genAutoExecMenuItem(_("on showing folder"), "PathChanged", k),
self:genAutoExecMenuItem(_("on book opening"), "ReaderReadyAll", k),
self:genAutoExecMenuItem(_("on book closing"), "CloseDocumentAll", k),
max_per_page = 11,
}
end,
hold_callback = function(touchmenu_instance)
for event, profiles in pairs(self.autoexec) do
if profiles[k] then
util.tableRemoveValue(self.autoexec, event, k)
end
end
touchmenu_instance:updateItems()
end,
},
{
text = _("Show notification on executing"),
checked_func = function()
return v.settings.notify
end,
callback = function()
v.settings.notify = not v.settings.notify or nil
self.updated = true
end,
separator = true,
},
{
text = _("Show in action list"),
checked_func = function()
return v.settings.registered
end,
callback = function()
if v.settings.registered then
dispatcherUnregisterProfile(k)
UIManager:broadcastEvent(Event:new("DispatcherActionNameChanged",
{ old_name = self.prefix..k, new_name = nil }))
v.settings.registered = nil
else
dispatcherRegisterProfile(k)
v.settings.registered = true
end
self.updated = true
end,
},
{
text_func = function()
return T(_("Edit actions: (%1)"), Dispatcher:menuTextFunc(v))
end,
sub_item_table_func = function()
local edit_actions_sub_items = {}
Dispatcher:addSubMenu(self, edit_actions_sub_items, self.data, k)
return edit_actions_sub_items
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[new_name].settings.name = new_name
self:updateAutoExec(k, new_name)
if v.settings.registered then
dispatcherUnregisterProfile(k)
dispatcherRegisterProfile(new_name)
UIManager:broadcastEvent(Event:new("DispatcherActionNameChanged",
{ old_name = self.prefix..k, new_name = self.prefix..new_name }))
end
self.data[k] = nil
self.updated = true
touchmenu_instance.item_table = self:getSubMenuItems()
touchmenu_instance:updateItems()
table.remove(touchmenu_instance.item_table_stack)
end
self:editProfileName(editCallback, k)
end,
},
{
text = _("Duplicate"),
keep_menu_open = true,
callback = function(touchmenu_instance)
local function editCallback(new_name)
self.data[new_name] = util.tableDeepCopy(v)
self.data[new_name].settings.name = new_name
if v.settings.registered then
dispatcherRegisterProfile(new_name)
end
self.updated = true
touchmenu_instance.item_table = self:getSubMenuItems()
touchmenu_instance:updateItems()
table.remove(touchmenu_instance.item_table_stack)
end
self:editProfileName(editCallback, k)
end,
},
{
text = _("Delete"),
keep_menu_open = true,
callback = function(touchmenu_instance)
UIManager:show(ConfirmBox:new{
text = _("Do you want to delete this profile?"),
ok_text = _("Delete"),
ok_callback = function()
self:updateAutoExec(k)
if v.settings.registered then
dispatcherUnregisterProfile(k)
UIManager:broadcastEvent(Event:new("DispatcherActionNameChanged",
{ old_name = self.prefix..k, new_name = nil }))
end
self.data[k] = nil
self.updated = true
touchmenu_instance.item_table = self:getSubMenuItems()
touchmenu_instance:updateItems()
table.remove(touchmenu_instance.item_table_stack)
end,
})
end,
separator = true,
},
}
table.insert(sub_item_table, {
text_func = function()
return (v.settings.show_as_quickmenu and "\u{F0CA} " or "\u{F144} ") .. k
end,
sub_item_table = sub_items,
})
end
return sub_item_table
end
function Profiles:onProfileExecute(name, exec_props)
Dispatcher:execute(self.data[name], exec_props)
end
function Profiles:editProfileName(editCallback, old_name)
local name_input
name_input = InputDialog:new{
title = _("Enter profile name"),
input = old_name,
buttons = {{
{
text = _("Cancel"),
id = "close",
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:getProfileFromCurrentBookSettings(new_name)
local document_settings
if self.ui.rolling then
document_settings = {
"rotation_mode",
"set_font",
"font_size",
"font_gamma",
"font_base_weight",
"font_hinting",
"font_kerning",
"word_spacing",
"word_expansion",
"visible_pages",
"h_page_margins",
"t_page_margin",
"b_page_margin",
"sync_t_b_page_margins",
"view_mode",
"block_rendering_mode",
"render_dpi",
"line_spacing",
"embedded_css",
"embedded_fonts",
"smooth_scaling",
"nightmode_images",
"status_line",
}
else
document_settings = {
"rotation_mode",
"kopt_text_wrap",
"kopt_trim_page",
"kopt_page_margin",
"kopt_zoom_overlap_h",
"kopt_zoom_overlap_v",
"kopt_zoom_mode_type",
"kopt_zoom_mode_genus",
"kopt_zoom_range_number",
"kopt_zoom_factor",
"kopt_zoom_direction",
"kopt_page_scroll",
"kopt_line_spacing",
"kopt_font_size",
"kopt_contrast",
"kopt_quality",
"kopt_max_columns",
}
end
local setting_needs_arg = {
["sync_t_b_page_margins"] = true,
["view_mode"] = true,
["embedded_css"] = true,
["embedded_fonts"] = true,
["smooth_scaling"] = true,
["nightmode_images"] = true,
["kopt_trim_page"] = true,
["kopt_zoom_mode_genus"] = true,
["kopt_zoom_mode_type"] = true,
["kopt_page_scroll"] = true,
}
local profile = { settings = { name = new_name, order = document_settings } }
for _, v in ipairs(document_settings) do
-- document configurable settings do not have prefixes
local value = self.document.configurable[v:gsub("^kopt_", "")]
if setting_needs_arg[v] then
value = Dispatcher:getArgFromValue(v, value)
end
profile[v] = value
end
if self.ui.rolling then
profile["set_font"] = self.ui.font.font_face -- not in configurable settings
end
return profile
end
function Profiles:onDispatcherActionNameChanged(action)
for _, profile in pairs(self.data) do
if profile[action.old_name] ~= nil then
if profile.settings and profile.settings.order then
for i, action_in_order in ipairs(profile.settings.order) do
if action_in_order == action.old_name then
if action.new_name then
profile.settings.order[i] = action.new_name
else
table.remove(profile.settings.order, i)
if #profile.settings.order < 2 then
profile.settings.order = nil
end
end
break
end
end
end
profile[action.old_name] = nil
if action.new_name then
profile[action.new_name] = true
end
self.updated = true
end
end
end
function Profiles:onDispatcherActionValueChanged(action)
for _, profile in pairs(self.data) do
if profile[action.name] == action.old_value then
profile[action.name] = action.new_value
if action.new_value == nil then
if profile.settings and profile.settings.order then
for i, action_in_order in ipairs(profile.settings.order) do
if action_in_order == action.name then
table.remove(profile.settings.order, i)
if #profile.settings.order < 2 then
profile.settings.order = nil
end
break
end
end
end
end
self.updated = true
end
end
end
-- AutoExec
function Profiles:updateAutoExec(old_name, new_name)
for event, profiles in pairs(self.autoexec) do
local old_value
for profile_name in pairs(profiles) do
if profile_name == old_name then
old_value = profiles[old_name]
profiles[old_name] = nil
break
end
end
if old_value then
if new_name then
profiles[new_name] = old_value
else
if next(profiles) == nil then
self.autoexec[event] = nil
end
end
end
end
end
function Profiles:genAutoExecMenuItem(text, event, profile_name, separator)
if event == "SetRotationMode" then
return self:genAutoExecSetRotationModeMenuItem(text, event, profile_name, separator)
elseif event == "PathChanged" then
return self:genAutoExecPathChangedMenuItem(text, event, profile_name, separator)
elseif event == "ReaderReadyAll" or event == "CloseDocumentAll" then
return self:genAutoExecDocConditionalMenuItem(text, event, profile_name, separator)
end
return {
text = text,
enabled_func = function()
if event == "Resume" then
local screensaver_delay = G_reader_settings:readSetting("screensaver_delay")
return screensaver_delay == nil or screensaver_delay == "disable"
end
return true
end,
checked_func = function()
return util.tableGetValue(self.autoexec, event, profile_name)
end,
callback = function()
if util.tableGetValue(self.autoexec, event, profile_name) then
util.tableRemoveValue(self.autoexec, event, profile_name)
else
util.tableSetValue(self.autoexec, true, event, profile_name)
if event == "ReaderReady" or event == "CloseDocument" then
-- "always" is checked, clear all conditional triggers
util.tableRemoveValue(self.autoexec, event .. "All", profile_name)
end
end
end,
separator = separator,
}
end
function Profiles:genAutoExecSetRotationModeMenuItem(text, event, profile_name, separator)
return {
text = text,
checked_func = function()
return util.tableGetValue(self.autoexec, event, profile_name) and true
end,
sub_item_table_func = function()
local sub_item_table = {}
local optionsutil = require("ui/data/optionsutil")
for i, mode in ipairs(optionsutil.rotation_modes) do
sub_item_table[i] = {
text = optionsutil.rotation_labels[i],
checked_func = function()
return util.tableGetValue(self.autoexec, event, profile_name, mode)
end,
callback = function()
if util.tableGetValue(self.autoexec, event, profile_name, mode) then
util.tableRemoveValue(self.autoexec, event, profile_name, mode)
else
util.tableSetValue(self.autoexec, true, event, profile_name, mode)
end
end,
}
end
return sub_item_table
end,
hold_callback = function(touchmenu_instance)
util.tableRemoveValue(self.autoexec, event, profile_name)
touchmenu_instance:updateItems()
end,
separator = separator,
}
end
function Profiles:genAutoExecPathChangedMenuItem(text, event, profile_name, separator)
return {
text = text,
checked_func = function()
return util.tableGetValue(self.autoexec, event, profile_name) and true
end,
sub_item_table_func = function()
local conditions = {
{ _("if folder path contains"), "has" },
{ _("if folder path does not contain"), "has_not" },
{ _("if folder path is equal"), "is_equal" },
{ _("if folder path is not equal"), "is_not_equal" },
}
local sub_item_table = {}
for i, mode in ipairs(conditions) do
local condition = mode[2]
sub_item_table[i] = {
text_func = function()
local txt = mode[1]
local value = util.tableGetValue(self.autoexec, event, profile_name, condition)
return value and txt .. ": " .. value or txt
end,
checked_func = function()
return util.tableGetValue(self.autoexec, event, profile_name, condition)
end,
check_callback_updates_menu = true,
callback = function(touchmenu_instance)
local dialog
local buttons = {{
{
text = _("Current folder"),
callback = function()
local curr_path = self.ui.file_chooser and self.ui.file_chooser.path or self.ui:getLastDirFile()
dialog:addTextToInput(curr_path)
end,
},
}}
table.insert(buttons, {
{
text = _("Cancel"),
id = "close",
callback = function()
UIManager:close(dialog)
end,
},
{
text = _("Save"),
callback = function()
local txt = dialog:getInputText()
if txt == "" then
util.tableRemoveValue(self.autoexec, event, profile_name, condition)
else
util.tableSetValue(self.autoexec, txt, event, profile_name, condition)
end
UIManager:close(dialog)
touchmenu_instance:updateItems()
end,
},
})
dialog = InputDialog:new{
title = _("Enter text contained in folder path"),
input = util.tableGetValue(self.autoexec, event, profile_name, condition),
buttons = buttons,
}
UIManager:show(dialog)
dialog:onShowKeyboard()
end,
hold_callback = function(touchmenu_instance)
util.tableRemoveValue(self.autoexec, event, profile_name, condition)
touchmenu_instance:updateItems()
end,
}
end
return sub_item_table
end,
hold_callback = function(touchmenu_instance)
util.tableRemoveValue(self.autoexec, event, profile_name)
touchmenu_instance:updateItems()
end,
separator = separator,
}
end
function Profiles:genAutoExecDocConditionalMenuItem(text, event, profile_name, separator)
local event_always = event:gsub("All", "")
return {
text = text,
checked_func = function()
return (util.tableGetValue(self.autoexec, event_always, profile_name) or util.tableGetValue(self.autoexec, event, profile_name)) and true
end,
sub_item_table_func = function()
local conditions = {
{ _("if device orientation is"), "orientation" },
{ _("if book metadata contains"), "doc_props" },
{ _("if book file path contains"), "filepath" },
{ _("if book is in collections"), "collections" },
{ _("and if book is new"), "is_new" },
}
local sub_item_table = {
self:genAutoExecMenuItem(_("always"), event_always, profile_name, true),
-- separator
{
text = conditions[1][1], -- orientation
enabled_func = function()
return not util.tableGetValue(self.autoexec, event_always, profile_name)
end,
checked_func = function()
return util.tableGetValue(self.autoexec, event, profile_name, conditions[1][2]) and true
end,
sub_item_table_func = function()
local condition = conditions[1][2]
local sub_item_table = {}
local optionsutil = require("ui/data/optionsutil")
for i, mode in ipairs(optionsutil.rotation_modes) do
sub_item_table[i] = {
text = optionsutil.rotation_labels[i],
checked_func = function()
return util.tableGetValue(self.autoexec, event, profile_name, condition, mode)
end,
callback = function()
if util.tableGetValue(self.autoexec, event, profile_name, condition, mode) then
util.tableRemoveValue(self.autoexec, event, profile_name, condition, mode)
else
util.tableSetValue(self.autoexec, true, event, profile_name, condition, mode)
end
end,
}
end
return sub_item_table
end,
hold_callback = function(touchmenu_instance)
util.tableRemoveValue(self.autoexec, event, profile_name, conditions[1][2])
touchmenu_instance:updateItems()
end,
},
{
text = conditions[2][1], -- doc_props
enabled_func = function()
return not util.tableGetValue(self.autoexec, event_always, profile_name)
end,
checked_func = function()
return util.tableGetValue(self.autoexec, event, profile_name, conditions[2][2]) and true
end,
sub_item_table_func = function()
local condition = conditions[2][2]
local sub_item_table = {}
for i, prop in ipairs(self.ui.bookinfo.props) do
sub_item_table[i] = {
text_func = function()
local title = self.ui.bookinfo.prop_text[prop]:lower()
local txt = util.tableGetValue(self.autoexec, event, profile_name, condition, prop)
return txt and title .. " " .. txt or title:sub(1, -2)
end,
checked_func = function()
return util.tableGetValue(self.autoexec, event, profile_name, condition, prop) and true
end,
check_callback_updates_menu = true,
callback = function(touchmenu_instance)
local dialog
local buttons = self.document == nil and {} or {{
{
text = _("Current book"),
enabled_func = function()
return prop == "title" or self.ui.doc_props[prop] ~= nil
end,
callback = function()
local txt = self.ui.doc_props[prop == "title" and "display_title" or prop]
dialog:addTextToInput(txt)
end,
},
}}
table.insert(buttons, {
{
text = _("Cancel"),
id = "close",
callback = function()
UIManager:close(dialog)
end,
},
{
text = _("Save"),
callback = function()
local txt = dialog:getInputText()
if txt == "" then
util.tableRemoveValue(self.autoexec, event, profile_name, condition, prop)
else
util.tableSetValue(self.autoexec, txt, event, profile_name, condition, prop)
end
UIManager:close(dialog)
touchmenu_instance:updateItems()
end,
},
})
dialog = InputDialog:new{
title = _("Enter text contained in:") .. " " .. self.ui.bookinfo.prop_text[prop]:sub(1, -2),
input = util.tableGetValue(self.autoexec, event, profile_name, condition, prop),
buttons = buttons,
}
UIManager:show(dialog)
dialog:onShowKeyboard()
end,
hold_callback = function(touchmenu_instance)
util.tableRemoveValue(self.autoexec, event, profile_name, condition, prop)
touchmenu_instance:updateItems()
end,
}
end
return sub_item_table
end,
hold_callback = function(touchmenu_instance)
util.tableRemoveValue(self.autoexec, event, profile_name, conditions[2][2])
touchmenu_instance:updateItems()
end,
},
{
text_func = function() -- filepath
local txt = conditions[3][1]
local value = util.tableGetValue(self.autoexec, event, profile_name, conditions[3][2])
return value and txt .. ": " .. value or txt
end,
enabled_func = function()
return not util.tableGetValue(self.autoexec, event_always, profile_name)
end,
checked_func = function()
return util.tableGetValue(self.autoexec, event, profile_name, conditions[3][2]) and true
end,
check_callback_updates_menu = true,
callback = function(touchmenu_instance)
local condition = conditions[3][2]
local dialog
local buttons = self.document == nil and {} or {{
{
text = _("Current book"),
callback = function()
dialog:addTextToInput(self.document.file)
end,
},
}}
table.insert(buttons, {
{
text = _("Cancel"),
id = "close",
callback = function()
UIManager:close(dialog)
end,
},
{
text = _("Save"),
callback = function()
local txt = dialog:getInputText()
if txt == "" then
util.tableRemoveValue(self.autoexec, event, profile_name, condition)
else
util.tableSetValue(self.autoexec, txt, event, profile_name, condition)
end
UIManager:close(dialog)
touchmenu_instance:updateItems()
end,
},
})
dialog = InputDialog:new{
title = _("Enter text contained in file path"),
input = util.tableGetValue(self.autoexec, event, profile_name, condition),
buttons = buttons,
}
UIManager:show(dialog)
dialog:onShowKeyboard()
end,
hold_callback = function(touchmenu_instance)
util.tableRemoveValue(self.autoexec, event, profile_name, conditions[3][2])
touchmenu_instance:updateItems()
end,
},
{
text_func = function() -- collections
local txt = conditions[4][1]
local collections = util.tableGetValue(self.autoexec, event, profile_name, conditions[4][2])
if collections then
local collections_nb = util.tableSize(collections)
return txt .. ": " ..
(collections_nb == 1 and self.ui.collections:getCollectionTitle(next(collections))
or "(" .. collections_nb .. ")")
end
return txt
end,
enabled_func = function()
return not util.tableGetValue(self.autoexec, event_always, profile_name)
end,
checked_func = function()
return util.tableGetValue(self.autoexec, event, profile_name, conditions[4][2]) and true
end,
check_callback_updates_menu = true,
callback = function(touchmenu_instance)
local condition = conditions[4][2]
local collections = util.tableGetValue(self.autoexec, event, profile_name, condition)
local caller_callback = function(selected_collections)
if next(selected_collections) == nil then
util.tableRemoveValue(self.autoexec, event, profile_name, condition)
else
util.tableSetValue(self.autoexec, selected_collections, event, profile_name, condition)
end
touchmenu_instance:updateItems()
end
self.ui.collections:onShowCollList(collections or {}, caller_callback, true)
end,
hold_callback = function(touchmenu_instance)
util.tableRemoveValue(self.autoexec, event, profile_name, conditions[4][2])
touchmenu_instance:updateItems()
end,
separator = true,
},
event == "ReaderReadyAll" and {
text = conditions[5][1], -- new
enabled_func = function()
return not util.tableGetValue(self.autoexec, event_always, profile_name)
end,
checked_func = function()
return util.tableGetValue(self.autoexec, event, profile_name, conditions[5][2]) and true
end,
callback = function(touchmenu_instance)
local condition = conditions[5][2]
if util.tableGetValue(self.autoexec, event, profile_name, condition) then
util.tableRemoveValue(self.autoexec, event, profile_name, condition)
else
util.tableSetValue(self.autoexec, true, event, profile_name, condition)
end
end,
hold_callback = function(touchmenu_instance)
util.tableRemoveValue(self.autoexec, event, profile_name, conditions[5][2])
touchmenu_instance:updateItems()
end,
} or nil,
}
return sub_item_table
end,
hold_callback = function(touchmenu_instance)
util.tableRemoveValue(self.autoexec, event_always, profile_name)
util.tableRemoveValue(self.autoexec, event, profile_name)
touchmenu_instance:updateItems()
end,
separator = separator,
}
end
function Profiles:onStart() -- local event
if not autostart_done then
self:executeAutoExecEvent("Start")
autostart_done = true
end
end
function Profiles:onResume() -- global
self:executeAutoExecEvent("Resume")
end
function Profiles:onOutOfScreenSaver() -- global
self:executeAutoExecEvent("OutOfScreenSaver")
end
function Profiles:onReadTimerExpired() -- global by ReadTimer plugin
self:executeAutoExecEvent("ReadTimerExpired")
end
function Profiles:onSetRotationMode(mode) -- global
local event = "SetRotationMode"
if self.autoexec[event] == nil then return end
for profile_name, modes in pairs(self.autoexec[event]) do
if modes[mode] then
if self.ui.config then -- close bottom menu to let Dispatcher execute profile
self.ui.config:onCloseConfigMenu()
end
self:executeAutoExec(profile_name)
end
end
end
function Profiles:onPathChanged(path) -- global
local event = "PathChanged"
if self.autoexec[event] == nil then return end
local function is_match(txt, pattern)
for str in util.gsplit(pattern, ",") do -- comma separated patterns are allowed
if util.stringSearch(txt, util.trim(str)) ~= 0 then
return true
end
end
end
for profile_name, conditions in pairs(self.autoexec[event]) do
local do_execute
for condition, trigger in pairs(conditions) do
if condition == "has" then
do_execute = is_match(path, trigger)
elseif condition == "has_not" then
do_execute = not is_match(path, trigger)
elseif condition == "is_equal" then
do_execute = path == trigger
elseif condition == "is_not_equal" then
do_execute = path ~= trigger
end
if do_execute then
break
end
end
if do_execute then
self:executeAutoExec(profile_name)
end
end
end
function Profiles:onReaderReady() -- global
if not self.ui.reloading then
self:executeAutoExecEvent("ReaderReady")
self:executeAutoExecDocConditional("ReaderReadyAll")
end
end
function Profiles:onCloseDocument() -- global
if not self.ui.reloading then
self:executeAutoExecEvent("CloseDocument")
self:executeAutoExecDocConditional("CloseDocumentAll")
end
end
function Profiles:executeAutoExecEvent(event)
if self.autoexec[event] == nil then return end
for profile_name in pairs(self.autoexec[event]) do
self:executeAutoExec(profile_name, event)
end
end
function Profiles:executeAutoExec(profile_name, event)
local profile = self.data[profile_name]
if profile == nil then return end
if profile.settings.auto_exec_time_interval then
local now = os.date("%H:%M")
local start_time, end_time = unpack(profile.settings.auto_exec_time_interval)
local do_execute
if start_time < end_time then
do_execute = start_time <= now and now <= end_time
else
do_execute = (start_time <= now and now <= "23:59") or ("00:00" <= now and now <= end_time)
end
if not do_execute then return end
end
local function execute_profile()
if profile.settings.auto_exec_promptly then
logger.dbg("Profiles - auto executing promptly:", profile_name)
Dispatcher:execute(profile)
elseif profile.settings.auto_exec_delay then
logger.dbg("Profiles - auto executing delayed:", profile_name, profile.settings.auto_exec_delay)
UIManager:scheduleIn(profile.settings.auto_exec_delay, function()
Dispatcher:execute(profile)
end)
else
logger.dbg("Profiles - auto executing:", profile_name)
if event == "CloseDocument" or event == "CloseDocumentAll" then
UIManager:tickAfterNext(function()
Dispatcher:execute(profile)
end)
else
UIManager:nextTick(function()
Dispatcher:execute(profile)
end)
end
end
end
if profile.settings.auto_exec_ask then
UIManager:show(ConfirmBox:new{
text = _("Do you want to execute profile?") .. "\n\n" .. profile_name .. "\n",
ok_text = _("Execute"),
ok_callback = function()
execute_profile()
end,
})
else
execute_profile()
end
end
function Profiles:executeAutoExecDocConditional(event)
if self.autoexec[event] == nil then return end
local function is_match(txt, pattern)
for str in util.gsplit(pattern, ",") do
if util.stringSearch(txt, util.trim(str)) ~= 0 then
return true
end
end
end
for profile_name, conditions in pairs(self.autoexec[event]) do
if self.data[profile_name] then
local do_execute
if not conditions.is_new or self.document.is_new then
for condition, trigger in pairs(conditions) do
if condition == "orientation" then
local mode = Screen:getRotationMode()
do_execute = trigger[mode]
elseif condition == "doc_props" then
if self.document then
for prop_name, pattern in pairs(trigger) do
local prop = self.ui.doc_props[prop_name == "title" and "display_title" or prop_name]
do_execute = is_match(prop, pattern)
if do_execute then
break -- any prop match is enough
end
end
end
elseif condition == "filepath" then
if self.document then
do_execute = is_match(self.document.file, trigger)
end
elseif condition == "collections" then
if self.document then
local ReadCollection = require("readcollection")
for collection_name in pairs(trigger) do
if ReadCollection:isFileInCollection(self.document.file, collection_name) then
do_execute = true
break -- any collection is enough
end
end
end
end
if do_execute then
break -- execute profile only once
end
end
end
if do_execute then
self:executeAutoExec(profile_name, event)
end
end
end
end
return Profiles