mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Ignore files starting with "._" (metafiles by MacOS), that will otherwise show up in the Styletweaks menu. (These files are alrady ignored in the File manager, even when 'show hidden files' is enabled.)
1195 lines
51 KiB
Lua
1195 lines
51 KiB
Lua
local BD = require("ui/bidi")
|
|
local Blitbuffer = require("ffi/blitbuffer")
|
|
local ButtonDialog = require("ui/widget/buttondialog")
|
|
local ButtonTable = require("ui/widget/buttontable")
|
|
local CenterContainer = require("ui/widget/container/centercontainer")
|
|
local CssTweaks = require("ui/data/css_tweaks")
|
|
local DataStorage = require("datastorage")
|
|
local Device = require("device")
|
|
local Dispatcher = require("dispatcher")
|
|
local Event = require("ui/event")
|
|
local Font = require("ui/font")
|
|
local FrameContainer = require("ui/widget/container/framecontainer")
|
|
local Geom = require("ui/geometry")
|
|
local GestureRange = require("ui/gesturerange")
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
local MovableContainer = require("ui/widget/container/movablecontainer")
|
|
local Notification = require("ui/widget/notification")
|
|
local Size = require("ui/size")
|
|
local TextBoxWidget = require("ui/widget/textboxwidget")
|
|
local UIManager = require("ui/uimanager")
|
|
local VerticalGroup = require("ui/widget/verticalgroup")
|
|
local VerticalSpan = require("ui/widget/verticalspan")
|
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
|
local lfs = require("libs/libkoreader-lfs")
|
|
local logger = require("logger")
|
|
local util = require("util")
|
|
local _ = require("gettext")
|
|
local C_ = _.pgettext
|
|
local Screen = Device.screen
|
|
local T = require("ffi/util").template
|
|
|
|
-- Simple widget for showing tweak info
|
|
local TweakInfoWidget = InputContainer:extend{
|
|
tweak = nil,
|
|
is_global_default = nil,
|
|
toggle_global_default_callback = function() end,
|
|
modal = true,
|
|
width = math.floor(Screen:getWidth() * 0.75),
|
|
}
|
|
|
|
function TweakInfoWidget:init()
|
|
local tweak = self.tweak
|
|
if Device:isTouchDevice() then
|
|
self.ges_events.Tap = {
|
|
GestureRange:new{
|
|
ges = "tap",
|
|
range = Geom:new{
|
|
x = 0, y = 0,
|
|
w = Screen:getWidth(),
|
|
h = Screen:getHeight(),
|
|
}
|
|
}
|
|
}
|
|
end
|
|
self:registerKeyEvents()
|
|
|
|
local content = VerticalGroup:new{
|
|
TextBoxWidget:new{
|
|
text = tweak.title,
|
|
bold = true,
|
|
face = Font:getFace("infofont"),
|
|
width = self.width,
|
|
},
|
|
VerticalSpan:new{
|
|
width = Size.padding.large,
|
|
},
|
|
}
|
|
if tweak.description then
|
|
table.insert(content,
|
|
TextBoxWidget:new{
|
|
text = tweak.description,
|
|
face = Font:getFace("smallinfofont"),
|
|
width = self.width,
|
|
}
|
|
)
|
|
table.insert(content, VerticalSpan:new{
|
|
width = Size.padding.large,
|
|
})
|
|
end
|
|
|
|
-- This css TextBoxWidget may make the widget overflow screen with
|
|
-- large css text. For now, we don't bother with the complicated
|
|
-- setup of a scrollable ScrollTextWidget.
|
|
local css = tweak.css
|
|
if not css and tweak.css_path then
|
|
css = ""
|
|
local f = io.open(tweak.css_path, "r")
|
|
if f then
|
|
css = f:read("*all")
|
|
f:close()
|
|
end
|
|
end
|
|
self.css_text = util.trim(css)
|
|
self.css_frame = FrameContainer:new{
|
|
bordersize = Size.border.thin,
|
|
padding = Size.padding.large,
|
|
TextBoxWidget:new{
|
|
text = self.css_text,
|
|
face = Font:getFace("infont", 16),
|
|
width = self.width - 2*Size.padding.large,
|
|
para_direction_rtl = false, -- LTR
|
|
}
|
|
}
|
|
table.insert(content, self.css_frame)
|
|
|
|
if self.is_global_default then
|
|
table.insert(content, VerticalSpan:new{
|
|
width = Size.padding.large,
|
|
})
|
|
table.insert(content,
|
|
TextBoxWidget:new{
|
|
text = _("This tweak is applied on all books."),
|
|
face = Font:getFace("smallinfofont"),
|
|
width = self.width,
|
|
}
|
|
)
|
|
end
|
|
|
|
content = FrameContainer:new{
|
|
bordersize = 0,
|
|
padding = Size.padding.large,
|
|
padding_top = Size.padding.default,
|
|
content,
|
|
}
|
|
|
|
local buttons = {
|
|
{
|
|
{
|
|
text = self.is_tweak_in_dispatcher and _("Don't show in action list") or _("Show in action list"),
|
|
callback = function()
|
|
self.toggle_tweak_in_dispatcher_callback()
|
|
UIManager:close(self)
|
|
end,
|
|
},
|
|
},
|
|
{
|
|
{
|
|
text = _("Close"),
|
|
callback = function()
|
|
UIManager:close(self)
|
|
end,
|
|
},
|
|
{
|
|
text = self.is_global_default and _("Don't use on all books") or _("Use on all books"),
|
|
callback = function()
|
|
self.toggle_global_default_callback()
|
|
UIManager:close(self)
|
|
end,
|
|
},
|
|
},
|
|
}
|
|
|
|
local button_table = ButtonTable:new{
|
|
width = content:getSize().w,
|
|
buttons = buttons,
|
|
zero_sep = true,
|
|
show_parent = self,
|
|
}
|
|
|
|
self.movable = MovableContainer:new{
|
|
FrameContainer:new{
|
|
background = Blitbuffer.COLOR_WHITE,
|
|
radius = Size.radius.window,
|
|
margin = Size.margin.default,
|
|
padding = Size.padding.default,
|
|
padding_bottom = 0, -- no padding below buttontable
|
|
VerticalGroup:new{
|
|
align = "left",
|
|
content,
|
|
button_table,
|
|
}
|
|
}
|
|
}
|
|
self[1] = CenterContainer:new{
|
|
dimen = Screen:getSize(),
|
|
self.movable
|
|
}
|
|
end
|
|
|
|
function TweakInfoWidget:registerKeyEvents()
|
|
if Device:hasKeys() then
|
|
self.key_events.Close = { { Device.input.group.Back } }
|
|
end
|
|
end
|
|
|
|
TweakInfoWidget.onPhysicalKeyboardConnected = TweakInfoWidget.registerKeyEvents
|
|
|
|
function TweakInfoWidget:onShow()
|
|
UIManager:setDirty(self, function()
|
|
return "ui", self.movable.dimen
|
|
end)
|
|
end
|
|
|
|
function TweakInfoWidget:onCloseWidget()
|
|
UIManager:setDirty(nil, function()
|
|
return "ui", self.movable.dimen
|
|
end)
|
|
end
|
|
|
|
function TweakInfoWidget:onClose()
|
|
UIManager:close(self)
|
|
return true
|
|
end
|
|
|
|
function TweakInfoWidget:onTap(arg, ges)
|
|
if ges.pos:intersectWith(self.css_frame.dimen) and Device:hasClipboard() then
|
|
-- Tap inside CSS text copies it into clipboard (so it
|
|
-- can be pasted into the book-specific tweak editor)
|
|
-- (Add \n on both sides for easier pasting)
|
|
Device.input.setClipboardText("\n"..self.css_text.."\n")
|
|
UIManager:show(Notification:new{
|
|
text = _("CSS text copied to clipboard"),
|
|
})
|
|
return true
|
|
elseif ges.pos:notIntersectWith(self.movable.dimen) then
|
|
-- Tap outside closes widget
|
|
self:onClose()
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function TweakInfoWidget:onSelect()
|
|
if self.selected.x == 1 then
|
|
self:toggle_global_default_callback()
|
|
end
|
|
UIManager:close(self)
|
|
return true
|
|
end
|
|
|
|
-- Ordering function for tweaks when appened to css_test.
|
|
-- The order needs to be consistent for crengine's stylesheet change
|
|
-- detection code to not invalidate cache across loadings.
|
|
local function tweakOrdering(l, r)
|
|
if l.priority ~= r.priority then
|
|
-- lower priority first in the CSS text
|
|
return l.priority < r.priority
|
|
end
|
|
-- same priority: order by ids
|
|
return l.id < r.id
|
|
end
|
|
|
|
-- Reader component for managing tweaks. The aggregated css_text
|
|
-- is actually requested from us and applied by ReaderTypeset
|
|
local ReaderStyleTweak = WidgetContainer:extend{
|
|
tweaks_by_id = nil,
|
|
tweaks_table = nil, -- sub-menu items
|
|
nb_enabled_tweaks = 0, -- for use by main menu item
|
|
css_text = nil, -- aggregated css text from tweaks individual css snippets
|
|
enabled = true, -- allows for toggling between selected tweaks / none
|
|
dispatcher_prefix = "style_tweak_",
|
|
}
|
|
|
|
function ReaderStyleTweak:isTweakEnabled(tweak_id)
|
|
local g_enabled = false
|
|
local enabled = false
|
|
if self.global_tweaks[tweak_id] then
|
|
enabled = true
|
|
g_enabled = true
|
|
end
|
|
if self.doc_tweaks[tweak_id] == true then
|
|
enabled = true
|
|
elseif self.doc_tweaks[tweak_id] == false then
|
|
enabled = false
|
|
end
|
|
return enabled, g_enabled
|
|
end
|
|
|
|
function ReaderStyleTweak:nbTweaksEnabled(sub_item_table)
|
|
local nb_enabled = 0
|
|
local nb_found = 0
|
|
for _, item in ipairs(sub_item_table) do
|
|
if item.sub_item_table then
|
|
local sub_nb_enabled, sub_nb_found = self:nbTweaksEnabled(item.sub_item_table)
|
|
nb_enabled = nb_enabled + sub_nb_enabled
|
|
nb_found = nb_found + sub_nb_found
|
|
elseif item.tweak_id then
|
|
if self:isTweakEnabled(item.tweak_id) then
|
|
nb_enabled = nb_enabled + 1
|
|
end
|
|
nb_found = nb_found + 1
|
|
end
|
|
end
|
|
return nb_enabled, nb_found
|
|
end
|
|
|
|
function ReaderStyleTweak:resolveConflictsBeforeEnabling(id, conflicts_with)
|
|
-- conflicts_with may be a string, an array or hash table of ids, or a function:
|
|
-- make it a function for us here
|
|
local conflicts_with_type = type(conflicts_with)
|
|
local conflicts_with_func
|
|
if conflicts_with_type == "function" then
|
|
conflicts_with_func = conflicts_with
|
|
elseif conflicts_with_type == "string" then
|
|
conflicts_with_func = function(otid) return otid == conflicts_with end
|
|
elseif conflicts_with_type == "table" then
|
|
conflicts_with_func = function(otid) return conflicts_with[otid] ~= nil or util.arrayContains(conflicts_with, otid) end
|
|
else
|
|
conflicts_with_func = function(otid) return false end
|
|
end
|
|
local to_remove = {}
|
|
for other_id, other_enabled in pairs(self.doc_tweaks) do
|
|
-- We also reset the provided "id" for a complete cleanup,
|
|
-- it is expected the caller will re-enable it
|
|
if other_enabled and (other_id == id or conflicts_with_func(other_id)) then
|
|
table.insert(to_remove, other_id)
|
|
end
|
|
end
|
|
for _, other_id in ipairs(to_remove) do
|
|
self.doc_tweaks[other_id] = nil
|
|
end
|
|
-- global_tweaks may also contain some conflicting ids: we need to make them false
|
|
-- in doc_tweaks to have them disabled (but we keep them in global_tweaks)
|
|
local to_make_false = {}
|
|
for other_id, other_enabled in pairs(self.global_tweaks) do
|
|
-- (We shouldn't be called if the provided "id" is already enabled
|
|
-- in global_tweaks. So we don't check for that here.)
|
|
if other_enabled and conflicts_with_func(other_id) then
|
|
table.insert(to_make_false, other_id)
|
|
end
|
|
end
|
|
for _, other_id in ipairs(to_make_false) do
|
|
self.doc_tweaks[other_id] = false
|
|
end
|
|
end
|
|
|
|
function ReaderStyleTweak:resolveConflictsBeforeMakingDefault(id, conflicts_with)
|
|
local conflicts_with_type = type(conflicts_with)
|
|
local conflicts_with_func
|
|
if conflicts_with_type == "function" then
|
|
conflicts_with_func = conflicts_with
|
|
elseif conflicts_with_type == "string" then
|
|
conflicts_with_func = function(otid) return otid == conflicts_with end
|
|
elseif conflicts_with_type == "table" then
|
|
conflicts_with_func = function(otid) return conflicts_with[otid] ~= nil or util.arrayContains(conflicts_with, otid) end
|
|
else
|
|
conflicts_with_func = function(otid) return false end
|
|
end
|
|
local to_remove = {}
|
|
for other_id, other_enabled in pairs(self.global_tweaks) do
|
|
-- We also reset the provided "id" for a complete cleanup,
|
|
-- it is expected the caller will re-enable it
|
|
if other_id == id or conflicts_with_func(other_id) then
|
|
table.insert(to_remove, other_id)
|
|
end
|
|
end
|
|
for _, other_id in ipairs(to_remove) do
|
|
self.global_tweaks[other_id] = nil
|
|
end
|
|
-- Also remove the provided "id" and any conflicting one from doc_tweaks (where
|
|
-- they may be false and prevent this new default to apply to current book)
|
|
to_remove = {}
|
|
for other_id, other_enabled in pairs(self.doc_tweaks) do
|
|
if other_id == id or conflicts_with_func(other_id) then
|
|
table.insert(to_remove, other_id)
|
|
end
|
|
end
|
|
for _, other_id in ipairs(to_remove) do
|
|
self.doc_tweaks[other_id] = nil
|
|
end
|
|
end
|
|
|
|
-- Called by ReaderTypeset, returns the already built string
|
|
function ReaderStyleTweak:getCssText()
|
|
return self.css_text
|
|
end
|
|
|
|
-- Build css_text, and request ReaderTypeset to apply it if wanted
|
|
function ReaderStyleTweak:updateCssText(apply)
|
|
if self.enabled then
|
|
local tweaks = {}
|
|
for id, enabled in pairs(self.global_tweaks) do
|
|
-- there are only enabled tweaks in global_tweaks, but we don't
|
|
-- add them here if they appear in doc_tweaks (if enabled in
|
|
-- doc_tweaks, they'll be added below; if disabled, they should
|
|
-- not be added)
|
|
if self.doc_tweaks[id] == nil then
|
|
table.insert(tweaks, self.tweaks_by_id[id])
|
|
end
|
|
end
|
|
for id, enabled in pairs(self.doc_tweaks) do
|
|
-- there are enabled (true) and disabled (false) tweaks in doc_tweaks
|
|
if self.doc_tweaks[id] == true then
|
|
table.insert(tweaks, self.tweaks_by_id[id])
|
|
end
|
|
end
|
|
table.sort(tweaks, tweakOrdering)
|
|
self.nb_enabled_tweaks = 0
|
|
local css_snippets = {}
|
|
for _, tweak in ipairs(tweaks) do
|
|
self.nb_enabled_tweaks = self.nb_enabled_tweaks + 1
|
|
local css = tweak.css
|
|
if not css and tweak.css_path then
|
|
css = ""
|
|
local f = io.open(tweak.css_path, "r")
|
|
if f then
|
|
css = f:read("*all")
|
|
f:close()
|
|
end
|
|
-- We could store what's been read into tweak.css to avoid
|
|
-- re-reading it, but this will allow a user to experiment
|
|
-- wihout having to restart KOReader
|
|
end
|
|
css = util.trim(css)
|
|
table.insert(css_snippets, css)
|
|
end
|
|
if self.book_style_tweak and self.book_style_tweak_enabled then
|
|
self.nb_enabled_tweaks = self.nb_enabled_tweaks + 1
|
|
table.insert(css_snippets, self.book_style_tweak)
|
|
end
|
|
self.css_text = table.concat(css_snippets, "\n")
|
|
logger.dbg("made tweak css:\n".. self.css_text .. "[END]")
|
|
else
|
|
self.css_text = nil
|
|
logger.dbg("made no tweak css (Style tweaks disabled)")
|
|
end
|
|
if apply then
|
|
self.ui:handleEvent(Event:new("ApplyStyleSheet"))
|
|
end
|
|
end
|
|
|
|
function ReaderStyleTweak:onReadSettings(config)
|
|
self.enabled = config:nilOrTrue("style_tweaks_enabled")
|
|
self.doc_tweaks = config:readSetting("style_tweaks") or {}
|
|
-- Default globally enabled style tweaks (for new installations)
|
|
-- are defined in css_tweaks.lua
|
|
self.global_tweaks = G_reader_settings:readSetting("style_tweaks") or CssTweaks.DEFAULT_GLOBAL_STYLE_TWEAKS
|
|
self.book_style_tweak = config:readSetting("book_style_tweak") -- string or nil
|
|
self.book_style_tweak_enabled = config:readSetting("book_style_tweak_enabled")
|
|
self.book_style_tweak_last_edit_pos = config:readSetting("book_style_tweak_last_edit_pos")
|
|
self:updateCssText()
|
|
end
|
|
|
|
function ReaderStyleTweak:onSaveSettings()
|
|
if self.enabled then
|
|
self.ui.doc_settings:delSetting("style_tweaks_enabled")
|
|
else
|
|
self.ui.doc_settings:makeFalse("style_tweaks_enabled")
|
|
end
|
|
self.ui.doc_settings:saveSetting("style_tweaks", util.tableSize(self.doc_tweaks) > 0 and self.doc_tweaks or nil)
|
|
G_reader_settings:saveSetting("style_tweaks", self.global_tweaks)
|
|
G_reader_settings:saveSetting("style_tweaks_in_dispatcher", self.tweaks_in_dispatcher)
|
|
self.ui.doc_settings:saveSetting("book_style_tweak", self.book_style_tweak)
|
|
self.ui.doc_settings:saveSetting("book_style_tweak_enabled", self.book_style_tweak_enabled)
|
|
self.ui.doc_settings:saveSetting("book_style_tweak_last_edit_pos", self.book_style_tweak_last_edit_pos)
|
|
end
|
|
|
|
local function dispatcherRegisterStyleTweak(tweak_id, tweak_title)
|
|
Dispatcher:registerAction(ReaderStyleTweak.dispatcher_prefix..tweak_id,
|
|
{category="none", event="ToggleStyleTweak", arg=tweak_id, title=T(_("Toggle style tweak: %1"), tweak_title), rolling=true})
|
|
end
|
|
|
|
local function dispatcherUnregisterStyleTweak(tweak_id)
|
|
Dispatcher:removeAction(ReaderStyleTweak.dispatcher_prefix..tweak_id)
|
|
end
|
|
|
|
function ReaderStyleTweak:init()
|
|
self.tweaks_in_dispatcher = G_reader_settings:readSetting("style_tweaks_in_dispatcher") or {}
|
|
self.tweaks_by_id = {}
|
|
self.tweaks_table = {}
|
|
|
|
-- Add first item of sub-menu, that allows toggling between
|
|
-- enabled tweaks / none (without the need to disable each of
|
|
-- them)
|
|
table.insert(self.tweaks_table, {
|
|
text = _("Enable style tweaks (long-press for help)"),
|
|
checked_func = function() return self.enabled end,
|
|
callback = function()
|
|
self.enabled = not self.enabled
|
|
self:updateCssText(true) -- apply it immediately
|
|
end,
|
|
hold_callback = function()
|
|
UIManager:show(InfoMessage:new{
|
|
text = _([[
|
|
Style tweaks allow changing small parts of book styles (including the publisher/embedded styles) to make visual adjustments or disable unwanted publisher layout choices.
|
|
|
|
Some tweaks may be useful with some books, while resulting in undesirable effects with others.
|
|
|
|
You can enable individual tweaks on this book with a tap, or view more details about a tweak and enable it on all books with hold.]])
|
|
})
|
|
end,
|
|
separator = true,
|
|
})
|
|
|
|
-- Single function for use as enabled_func
|
|
local is_enabled = function() return self.enabled end
|
|
|
|
-- Generic function to recursively build a sub_item_table (as expected
|
|
-- by TouchMenu) from a table of tweak definitions (like CssTweaks from
|
|
-- css_tweaks.lua, or like the one we build from user styletweaks
|
|
-- directory files and sub-directories)
|
|
local addTweakMenuItem
|
|
addTweakMenuItem = function(menu, item, max_per_page)
|
|
if type(item) == "table" and #item > 0 then -- sub-menu
|
|
local sub_item_table = {}
|
|
sub_item_table.max_per_page = max_per_page
|
|
for _, it in ipairs(item) do
|
|
addTweakMenuItem(sub_item_table, it, max_per_page) -- recurse
|
|
end
|
|
table.insert(menu, {
|
|
text_func = function()
|
|
local text = item.title or "### undefined submenu title ###"
|
|
local nb_enabled, nb_found = self:nbTweaksEnabled(sub_item_table) -- luacheck: no unused
|
|
-- We could add nb_enabled/nb_found, but that makes for
|
|
-- a busy/ugly menu
|
|
-- text = string.format("%s (%d/%d)", text, nb_enabled, nb_found)
|
|
if nb_enabled > 0 then
|
|
text = string.format("%s (%d)", text, nb_enabled)
|
|
end
|
|
return text
|
|
end,
|
|
enabled_func = is_enabled,
|
|
sub_item_table = sub_item_table,
|
|
separator = item.separator,
|
|
})
|
|
elseif item.id then -- tweak menu item
|
|
-- Set a default priority of 0 if item doesn't have one
|
|
if not item.priority then item.priority = 0 end
|
|
self.tweaks_by_id[item.id] = item
|
|
table.insert(menu, {
|
|
tweak_id = item.id,
|
|
enabled_func = is_enabled,
|
|
checked_func = function() return self:isTweakEnabled(item.id) end,
|
|
text_func = function()
|
|
local title = item.title or "### undefined tweak title ###"
|
|
if self.global_tweaks[item.id] then
|
|
title = title .. " ★"
|
|
end
|
|
if self.tweaks_in_dispatcher[item.id] then
|
|
title = title .. " \u{F144}"
|
|
end
|
|
return title
|
|
end,
|
|
hold_callback = function(touchmenu_instance)
|
|
UIManager:show(TweakInfoWidget:new{
|
|
tweak = item,
|
|
is_global_default = self.global_tweaks[item.id],
|
|
toggle_global_default_callback = function()
|
|
if self.global_tweaks[item.id] then
|
|
self.global_tweaks[item.id] = nil
|
|
if self.doc_tweaks[item.id] == false then
|
|
self.doc_tweaks[item.id] = nil
|
|
end
|
|
else
|
|
if item.conflicts_with and item.global_conflicts_with ~= false then
|
|
-- For hold/makeDefault/global_tweaks, the tweak may provide 'global_conflicts_with':
|
|
-- if 'false': no conflict checks
|
|
-- if nil or 'true', use item.conflicts_with
|
|
-- otherwise, use it instead of item.conflicts_with
|
|
if item.global_conflicts_with ~= true and item.global_conflicts_with ~= nil then
|
|
self:resolveConflictsBeforeMakingDefault(item.id, item.global_conflicts_with)
|
|
else
|
|
self:resolveConflictsBeforeMakingDefault(item.id, item.conflicts_with)
|
|
end
|
|
-- Remove all references in doc_tweak
|
|
self:resolveConflictsBeforeEnabling(item.id, item.conflicts_with)
|
|
self.doc_tweaks[item.id] = nil
|
|
end
|
|
self.global_tweaks[item.id] = true
|
|
end
|
|
touchmenu_instance:updateItems()
|
|
self:updateCssText(true) -- apply it immediately
|
|
end,
|
|
is_tweak_in_dispatcher = self.tweaks_in_dispatcher[item.id],
|
|
toggle_tweak_in_dispatcher_callback = function()
|
|
if self.tweaks_in_dispatcher[item.id] then
|
|
self.tweaks_in_dispatcher[item.id] = nil
|
|
dispatcherUnregisterStyleTweak(item.id)
|
|
if self.ui.profiles then
|
|
self.ui.profiles:updateProfiles(self.dispatcher_prefix..item.id)
|
|
end
|
|
else
|
|
self.tweaks_in_dispatcher[item.id] = item.title
|
|
dispatcherRegisterStyleTweak(item.id, item.title)
|
|
end
|
|
touchmenu_instance:updateItems()
|
|
end,
|
|
})
|
|
end,
|
|
callback = function()
|
|
-- enable/disable only for this book
|
|
self:onToggleStyleTweak(item.id, item, true) -- no notification
|
|
end,
|
|
separator = item.separator,
|
|
})
|
|
elseif item.info_text then -- informative menu item
|
|
table.insert(menu, {
|
|
text = item.title or "### undefined menu title ###",
|
|
-- No check box.
|
|
-- Show the info text when either tap or hold
|
|
keep_menu_open = true,
|
|
callback = function()
|
|
UIManager:show(InfoMessage:new{
|
|
text = item.info_text,
|
|
})
|
|
end,
|
|
hold_callback = function()
|
|
UIManager:show(InfoMessage:new{
|
|
text = item.info_text,
|
|
})
|
|
end,
|
|
separator = item.separator,
|
|
})
|
|
else
|
|
table.insert(menu, {
|
|
text = item.if_empty_menu_title or _("This section is empty"),
|
|
enabled = false,
|
|
})
|
|
end
|
|
end
|
|
|
|
-- Add each of CssTweaks' top-level items as a sub-menu
|
|
for _, item in ipairs(CssTweaks) do
|
|
addTweakMenuItem(self.tweaks_table, item)
|
|
end
|
|
|
|
-- Users can put their own style tweaks as individual .css files into
|
|
-- koreader/styletweaks/ directory. These can be organized into
|
|
-- sub-directories that will show up as sub-menus.
|
|
local user_styletweaks_dir = DataStorage:getDataDir() .. "/styletweaks"
|
|
local user_tweaks_table = { title = _("User style tweaks") }
|
|
|
|
-- Build a tweak definition table from the content of a directory
|
|
local process_tweaks_dir
|
|
process_tweaks_dir = function(dir, item_table, if_empty_menu_title)
|
|
local file_list = {}
|
|
local dir_list = {}
|
|
if lfs.attributes(dir, "mode") == "directory" then
|
|
for f in lfs.dir(dir) do
|
|
if f ~= "." and f ~= ".." then
|
|
local mode = lfs.attributes(dir.."/"..f, "mode")
|
|
if mode == "directory" then
|
|
table.insert(dir_list, f)
|
|
elseif mode == "file" and string.match(f, "%.css$") and not util.stringStartsWith(f, "._") then
|
|
table.insert(file_list, f)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
table.sort(dir_list)
|
|
table.sort(file_list)
|
|
for __, subdir in ipairs(dir_list) do
|
|
local sub_item_table = { title = subdir:gsub("_", " ") }
|
|
process_tweaks_dir(dir.."/"..subdir, sub_item_table)
|
|
table.insert(item_table, sub_item_table)
|
|
end
|
|
for __, file in ipairs(file_list) do
|
|
local title = file:gsub("%.css$", ""):gsub("_", " ")
|
|
local filepath = dir.."/"..file
|
|
table.insert(item_table, {
|
|
title = title,
|
|
id = file, -- keep ".css" in id, to distinguish between koreader/user tweaks
|
|
description = T(_("User style tweak at %1"), BD.filepath(filepath)),
|
|
priority = 10, -- give user tweaks a higher priority
|
|
css_path = filepath,
|
|
})
|
|
end
|
|
if #item_table == 0 then
|
|
table.insert(item_table, {
|
|
if_empty_menu_title = if_empty_menu_title or _("No CSS tweak found in this directory"),
|
|
})
|
|
end
|
|
end
|
|
local if_empty_menu_title = _("Add your own tweaks in koreader/styletweaks/")
|
|
process_tweaks_dir(user_styletweaks_dir, user_tweaks_table, if_empty_menu_title)
|
|
self.tweaks_table[#self.tweaks_table].separator = true
|
|
addTweakMenuItem(self.tweaks_table, user_tweaks_table, 6)
|
|
-- limit to 6 user tweaks per page
|
|
|
|
-- Book-specific editable tweak
|
|
self.tweaks_table[#self.tweaks_table].separator = true
|
|
local book_tweak_item = {
|
|
text_func = function()
|
|
if self.book_style_tweak then
|
|
return _("Book-specific tweak (long-press to edit)")
|
|
else
|
|
return _("Book-specific tweak")
|
|
end
|
|
end,
|
|
enabled_func = function() return self.enabled end,
|
|
checked_func = function() return self.book_style_tweak_enabled end,
|
|
callback = function(touchmenu_instance)
|
|
if self.book_style_tweak then
|
|
-- There is a tweak: toggle it on tap, like other tweaks
|
|
self.book_style_tweak_enabled = not self.book_style_tweak_enabled
|
|
self:updateCssText(true) -- apply it immediately
|
|
else
|
|
-- No tweak defined: launch editor
|
|
self:editBookTweak(touchmenu_instance)
|
|
end
|
|
end,
|
|
hold_callback = function(touchmenu_instance)
|
|
self:editBookTweak(touchmenu_instance)
|
|
end,
|
|
}
|
|
table.insert(self.tweaks_table, book_tweak_item)
|
|
|
|
self.ui.menu:registerToMainMenu(self)
|
|
self:onDispatcherRegisterActions()
|
|
end
|
|
|
|
function ReaderStyleTweak:addToMainMenu(menu_items)
|
|
-- insert table to main reader menu
|
|
menu_items.style_tweaks = {
|
|
text_func = function()
|
|
if self.enabled and self.nb_enabled_tweaks > 0 then
|
|
return T(_("Style tweaks (%1)"), self.nb_enabled_tweaks)
|
|
else
|
|
return _("Style tweaks")
|
|
end
|
|
end,
|
|
sub_item_table = self.tweaks_table,
|
|
}
|
|
end
|
|
|
|
function ReaderStyleTweak:onToggleStyleTweak(tweak_id, item, no_notification)
|
|
local text
|
|
local enabled, g_enabled = self:isTweakEnabled(tweak_id)
|
|
if enabled then
|
|
if g_enabled then
|
|
-- if globaly enabled, mark it as disabled
|
|
-- for this document only
|
|
self.doc_tweaks[tweak_id] = false
|
|
else
|
|
self.doc_tweaks[tweak_id] = nil
|
|
end
|
|
text = T(C_("Style tweak", "Off: %1"), self.tweaks_in_dispatcher[tweak_id])
|
|
else
|
|
local conflicts_with
|
|
if item then
|
|
conflicts_with = item.conflicts_with
|
|
else -- called from Dispatcher
|
|
for _, v in ipairs(CssTweaks) do
|
|
if v.id == tweak_id then
|
|
conflicts_with = v.conflicts_with
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if conflicts_with then
|
|
self:resolveConflictsBeforeEnabling(tweak_id, conflicts_with)
|
|
end
|
|
self.doc_tweaks[tweak_id] = true
|
|
text = T(C_("Style tweak", "On: %1"), self.tweaks_in_dispatcher[tweak_id])
|
|
end
|
|
self:updateCssText(true) -- apply it immediately
|
|
if not no_notification then
|
|
UIManager:show(Notification:new{
|
|
text = text,
|
|
})
|
|
end
|
|
end
|
|
|
|
function ReaderStyleTweak:onDispatcherRegisterActions()
|
|
for tweak_id, tweak_title in pairs(self.tweaks_in_dispatcher) do
|
|
dispatcherRegisterStyleTweak(tweak_id, tweak_title)
|
|
end
|
|
end
|
|
|
|
local BOOK_TWEAK_SAMPLE_CSS = [[
|
|
/* Remove indent from some P used as titles */
|
|
p.someTitleClassName {
|
|
text-indent: 0;
|
|
}
|
|
/* Get in-page footnotes when no tweak works */
|
|
.footnoteContainerClassName {
|
|
-cr-hint: footnote-inpage;
|
|
}
|
|
/* Help getting some alternative ToC when no headings */
|
|
.someSeparatorClassName {
|
|
-cr-hint: toc-level1;
|
|
break-before: always;
|
|
}
|
|
/* Hide annoying content */
|
|
DIV.someAdvertisement {
|
|
display: none !important;
|
|
}
|
|
]]
|
|
|
|
local BOOK_TWEAK_INPUT_HINT = T([[
|
|
/* %1 */
|
|
|
|
%2]], _("You can add CSS snippets which will be applied only to this book."), BOOK_TWEAK_SAMPLE_CSS)
|
|
|
|
local CSS_SUGGESTIONS = {
|
|
{ _("Long-press for info ⓘ"), _([[
|
|
This menu provides a non-exhaustive CSS syntax and properties list. It also shows some KOReader-specific, non-standard CSS features that can be useful with e-books.
|
|
|
|
Most of these bits are already used by our categorized 'Style tweaks' (found in the top menu). Long-press on any style-tweak option to see its code and its expected results. Should these not be enough to achieve your desired look, you may need to adjust them slightly: tap once on the CSS code-box to copy the code to the clipboard, paste it here and edit it.
|
|
|
|
Long-press on any item in this popup to get more information on what it does and what it can help solving.
|
|
|
|
Tap on the item to insert it: you can then edit it and combine it with others.]]), true },
|
|
|
|
{ _("Matching elements"), {
|
|
{ "p.className", _([[
|
|
p.className matches a <p> with class='className'.
|
|
|
|
*.className matches any element with class='className'.
|
|
|
|
p:not([class]) matches a <p> without any class= attribute.]])},
|
|
{ "aside > p", _([[
|
|
aside > p matches a <p> children of an <aside> element.
|
|
|
|
aside p (without any intermediate symbol) matches a <p> descendant of an <aside> element.]])},
|
|
{ "p + img", _([[
|
|
p + img matches a <img> if its immediate previous sibling is a <p>.
|
|
|
|
p ~ img matches a <img> if any of its previous siblings is a <p>.]])},
|
|
|
|
{ "p[name='what']", _([[
|
|
[name="what"] matches if the element has the attribute 'name' and its value is exactly 'what'.
|
|
|
|
[name] matches if the attribute 'name' is present.
|
|
|
|
[name~="what"] matches if the value of the attribute 'name' contains 'what' as a word (among other words separated by spaces).]])},
|
|
|
|
{ "p[name*='what' i]", _([[
|
|
[name*="what" i] matches any element having the attribute 'name' with a value that contains 'what', case insensitive.
|
|
|
|
[name^="what"] matches if the attribute value starts with 'what'.
|
|
|
|
[name$="what"] matches if the attribute value ends with 'what'.]])},
|
|
|
|
{ "p[_='what']", _([[
|
|
Similar in syntax to attribute matching, but matches the inner text of an element.
|
|
|
|
p[_="what"] matches any <p> whose text is exactly 'what'.
|
|
|
|
p[_] matches any non-empty <p>.
|
|
|
|
p:not([_]) matches any empty <p>.
|
|
|
|
p[_~="what"] matches any <p> that contains the word 'what'.]])},
|
|
|
|
{ "p[_*='what' i]", _([[
|
|
Similar in syntax to attribute matching, but matches the inner text of an element.
|
|
|
|
p[_*="what" i] matches any <p> that contains 'what', case insensitive.
|
|
|
|
p[_^="what"] matches any <p> whose text starts with 'what'.
|
|
(This can be used to match "Act" or "Scene", or character names in plays, and make them stand out.)
|
|
|
|
p[_$="what"] matches any <p> whose text ends with 'what'.]])},
|
|
|
|
{ "p:first-child", _([[
|
|
p:first-child matches a <p> that is the first child of its parent.
|
|
|
|
p:last-child matches a <p> that is the last child of its parent.
|
|
|
|
p:nth-child(odd) matches any other <p> in a series of sibling <p>.]])},
|
|
|
|
{ "Tip: use View HTML ⓘ", _([[
|
|
On a book page, select some text spanning around (before and after) the element you are interested in, and use 'View HTML'.
|
|
In the HTML viewer, long press on tags or text to get a list of selectors matching the element: tap on one of them to copy it to the clipboard.
|
|
You can then paste it here with long-press in the text box.]]), true},
|
|
|
|
}},
|
|
|
|
{ _("Common classic properties"), {
|
|
{ "font-size: 1rem !important;", _("1rem will enforce your main font size.")},
|
|
{ "font-weight: normal !important;", _("Remove bold. Use 'bold' to get bold.")},
|
|
{ "hyphens: none !important;", _("Disables hyphenation inside the targeted elements.")},
|
|
{ "text-indent: 1.2em !important;", _("1.2em is our default text indentation.")},
|
|
{ "break-before: always !important;", _("Start a new page with this element. Use 'avoid' to avoid a new page.")},
|
|
{ "color: black !important;", _("Force text to be black.")},
|
|
{ "background: transparent !important;", _("Remove any background color.")},
|
|
{ "max-width: 50vw !important;", _("Limit an element width to 50% of your screen width (use 'max-height: 50vh' for 50% of the screen height). Can be useful with <img> to limit their size.")},
|
|
}},
|
|
|
|
{ _("Private CSS properties"), {
|
|
{ "-cr-hint: footnote-inpage;", _("When set on a block element containing the target id of a href, this block element will be shown as an in-page footnote.")},
|
|
{ "-cr-hint: non-linear;", _("Can be set on some specific DocFragments (e.g. DocFragment[id$=_16]) to ignore them in the linear pages flow.")},
|
|
{ "-cr-hint: non-linear-combining;", _("Can be set on contiguous footnote blocks to ignore them in the linear pages flow.")},
|
|
{ "-cr-hint: toc-level1;", _("When set on an element, its text can be used to build the alternative table of contents. toc-level2 to toc-level6 can be used for nested chapters.")},
|
|
{ "-cr-hint: toc-ignore;", _("When set on an element, it will be ignored when building the alternative table of contents.")},
|
|
{ "-cr-hint: footnote;", _("Can be set on target of links (<div id='..'>) to have their link trigger as footnote popup, in case KOReader wrongly detect this target is not a footnote.")},
|
|
{ "-cr-hint: noteref;", _("Can be set on links (<a href='#..'>) to have them trigger as footnote popups, in case KOReader wrongly detect the links is not to a footnote.")},
|
|
{ "-cr-hint: noteref-ignore;", _([[
|
|
Can be set on links (<a href='#..'>) to have them NOT trigger footnote popups and in-page footnotes.
|
|
If some DocFragment presents an index of names with cross references, resulting in in-page footnotes taking half of these pages, you can avoid this with:
|
|
DocFragment[id$=_16] a { -cr-hint: noteref-ignore }]])},
|
|
}},
|
|
|
|
{ _("Useful 'content:' values"), {
|
|
{ _("Caution ⚠"), _([[
|
|
Be careful with these: stick them to a proper discriminating selector, like:
|
|
|
|
span.specificClassName
|
|
|
|
p[_*="keyword" i]
|
|
|
|
If used as-is, they will act on ALL elements!]]), true},
|
|
{ "::before {content: ' '}", _("Insert a visible space before an element.")},
|
|
{ "::before {content: '\\A0 '}", _("Insert a visible non-breakable space before an element, so it sticks to what's before.")},
|
|
{ "::before {content: '\\2060'}", _("U+2060 WORD JOINER may act as a glue (like an invisible non-breakable space) before an element, so it sticks to what's before.")},
|
|
{ "::before {content: '\\200B'}", _("U+200B ZERO WIDTH SPACE may allow a linebreak before an element, in case the absence of any space prevents that.")},
|
|
{ "::before {content: attr(title)}", _("Insert the value of the attribute 'title' at start of an element content.")},
|
|
{ "::before {content: '▶ '}", _("Prepend a visible marker.")},
|
|
{ "::before {content: '● '}", _("Prepend a visible marker.")},
|
|
{ "::before {content: '█ '}", _("Prepend a visible marker.")},
|
|
}},
|
|
}
|
|
|
|
function ReaderStyleTweak:editBookTweak(touchmenu_instance)
|
|
local InputDialog = require("ui/widget/inputdialog")
|
|
local editor -- our InputDialog instance
|
|
local tweak_button_id = "editBookTweakButton"
|
|
-- We add a button on the left, which can have 3 states/labels:
|
|
local BUTTON_USE_SAMPLE = _("Use sample")
|
|
local BUTTON_PRETTIFY = _("Prettify")
|
|
local BUTTON_CONDENSE = _("Condense")
|
|
-- Initial button state differs whether we already have some CSS content
|
|
local tweak_button_state = self.book_style_tweak and BUTTON_PRETTIFY or BUTTON_USE_SAMPLE
|
|
local toggle_tweak_button = function(state)
|
|
if state then -- use provided state
|
|
tweak_button_state = state
|
|
else -- natural toggling
|
|
if tweak_button_state == BUTTON_USE_SAMPLE then
|
|
tweak_button_state = BUTTON_PRETTIFY
|
|
elseif tweak_button_state == BUTTON_PRETTIFY then
|
|
tweak_button_state = BUTTON_CONDENSE
|
|
elseif tweak_button_state == BUTTON_CONDENSE then
|
|
tweak_button_state = BUTTON_PRETTIFY
|
|
end
|
|
end
|
|
local tweak_button = editor.button_table:getButtonById(tweak_button_id)
|
|
tweak_button:init()
|
|
editor:refreshButtons()
|
|
end
|
|
-- The Save and Close buttons default behaviour, how they trigger
|
|
-- the callbacks and how they show or not a notification, is not
|
|
-- the most convenient here. We try to tweak that a bit.
|
|
local SAVE_BUTTON_LABEL
|
|
if self.book_style_tweak_enabled or not self.book_style_tweak then
|
|
SAVE_BUTTON_LABEL = _("Apply")
|
|
else
|
|
SAVE_BUTTON_LABEL = _("Save")
|
|
end
|
|
-- This message might be shown by multiple notifications at the
|
|
-- same time: having it similar will make that unnoticed.
|
|
local NOT_MODIFIED_MSG = _("Book tweak not modified.")
|
|
editor = InputDialog:new{
|
|
title = _("Edit book-specific style tweak"),
|
|
input = self.book_style_tweak or "",
|
|
input_hint = BOOK_TWEAK_INPUT_HINT,
|
|
input_face = Font:getFace("infont", 16), -- same as in TweakInfoWidget
|
|
para_direction_rtl = false,
|
|
lang = "en",
|
|
fullscreen = true,
|
|
condensed = true,
|
|
allow_newline = true,
|
|
cursor_at_end = false,
|
|
add_nav_bar = true,
|
|
scroll_by_pan = true,
|
|
buttons = {{
|
|
-- First buttons on first row (row will be completed with Reset|Save|Close)
|
|
{
|
|
id = tweak_button_id,
|
|
text_func = function()
|
|
return tweak_button_state -- usable as a label
|
|
end,
|
|
callback = function()
|
|
if tweak_button_state == BUTTON_USE_SAMPLE then
|
|
editor:setInputText(BOOK_TWEAK_SAMPLE_CSS, true)
|
|
-- will have edited_callback() called, which will do toggle_tweak_button()
|
|
else
|
|
local css_text = editor:getInputText()
|
|
css_text = util.prettifyCSS(css_text, tweak_button_state == BUTTON_CONDENSE)
|
|
editor:setInputText(css_text, true)
|
|
toggle_tweak_button()
|
|
end
|
|
end,
|
|
},
|
|
{
|
|
id = "css_suggestions_button_id",
|
|
text = "CSS \u{2261}",
|
|
callback = function()
|
|
local suggestions_popup_widget
|
|
local buttons = {}
|
|
for _, suggestion in ipairs(CSS_SUGGESTIONS) do
|
|
local title = suggestion[1]
|
|
local is_submenu, submenu_items, description
|
|
if type(suggestion[2]) == "table" then
|
|
is_submenu = true
|
|
submenu_items = suggestion[2]
|
|
else
|
|
description = suggestion[2]
|
|
end
|
|
local is_info_only = suggestion[3]
|
|
local text
|
|
if is_submenu then -- add the same arrow we use for top menu submenus
|
|
text = require("ui/widget/menu").getMenuText({text=title, sub_item_table=true})
|
|
elseif is_info_only then
|
|
text = title
|
|
else
|
|
text = BD.ltr(title) -- CSS code, keep it LTR
|
|
end
|
|
table.insert(buttons, {{
|
|
text = text,
|
|
id = title,
|
|
align = "left",
|
|
callback = function()
|
|
if is_info_only then
|
|
-- No CSS bit to insert, show description also on tap
|
|
UIManager:show(InfoMessage:new{ text = description })
|
|
return
|
|
end
|
|
if not is_submenu then -- insert as-is on tap
|
|
UIManager:close(suggestions_popup_widget)
|
|
editor:addTextToInput(title)
|
|
else
|
|
local sub_suggestions_popup_widget
|
|
local sub_buttons = {}
|
|
for _, sub_suggestion in ipairs(submenu_items) do
|
|
-- (No 2nd level submenu needed for now)
|
|
local sub_title = sub_suggestion[1]
|
|
local sub_description = sub_suggestion[2]
|
|
local sub_is_info_only = sub_suggestion[3]
|
|
local sub_text = sub_is_info_only and sub_title or BD.ltr(sub_title)
|
|
table.insert(sub_buttons, {{
|
|
text = sub_text,
|
|
align = "left",
|
|
callback = function()
|
|
if sub_is_info_only then
|
|
UIManager:show(InfoMessage:new{ text = sub_description })
|
|
return
|
|
end
|
|
UIManager:close(sub_suggestions_popup_widget)
|
|
UIManager:close(suggestions_popup_widget)
|
|
editor:addTextToInput(sub_title)
|
|
end,
|
|
hold_callback = sub_description and function()
|
|
UIManager:show(InfoMessage:new{ text = sub_description })
|
|
end,
|
|
}})
|
|
end
|
|
local anchor_func = function()
|
|
local d = suggestions_popup_widget:getButtonById(title).dimen:copy()
|
|
if BD.mirroredUILayout() then
|
|
d.x = d.x - d.w + Size.padding.default
|
|
else
|
|
d.x = d.x + d.w - Size.padding.default
|
|
end
|
|
-- As we don't know if we will pop up or down, anchor it on the middle of the item
|
|
d.y = d.y + math.floor(d.h / 2)
|
|
d.h = 1
|
|
return d, true
|
|
end
|
|
sub_suggestions_popup_widget = ButtonDialog:new{
|
|
modal = true, -- needed when keyboard is shown
|
|
width = math.floor(Screen:getWidth() * 0.9), -- max width, will get smaller
|
|
shrink_unneeded_width = true,
|
|
buttons = sub_buttons,
|
|
anchor = anchor_func,
|
|
}
|
|
UIManager:show(sub_suggestions_popup_widget)
|
|
end
|
|
end,
|
|
hold_callback = description and function()
|
|
UIManager:show(InfoMessage:new{ text = description })
|
|
end or nil
|
|
}})
|
|
end
|
|
suggestions_popup_widget = ButtonDialog:new{
|
|
modal = true, -- needed when keyboard is shown
|
|
width = math.floor(Screen:getWidth() * 0.9), -- max width, will get smaller
|
|
shrink_unneeded_width = true,
|
|
buttons = buttons,
|
|
anchor = function()
|
|
-- we return prefers_pop_down=true so it pops over the keyboard
|
|
-- instead of the text if it can
|
|
return editor.button_table:getButtonById("css_suggestions_button_id").dimen, true
|
|
end,
|
|
}
|
|
UIManager:show(suggestions_popup_widget)
|
|
end,
|
|
},
|
|
}},
|
|
edited_callback = function()
|
|
if not editor then
|
|
-- We might be called while the InputDialog is being
|
|
-- initialized (so not yet assigned to 'editor')
|
|
return
|
|
end
|
|
if #editor:getInputText() == 0 then
|
|
-- No content: show "Use sample"
|
|
if tweak_button_state ~= BUTTON_USE_SAMPLE then
|
|
toggle_tweak_button(BUTTON_USE_SAMPLE)
|
|
end
|
|
else
|
|
-- Some content: get rid of "Use sample" to not risk
|
|
-- overriding content
|
|
if tweak_button_state == BUTTON_USE_SAMPLE then
|
|
toggle_tweak_button()
|
|
end
|
|
end
|
|
end,
|
|
-- Store/retrieve view and cursor position callback
|
|
view_pos_callback = function(top_line_num, charpos)
|
|
-- This same callback is called with no arguments on init to retrieve the stored initial position,
|
|
-- and with arguments to store the final position on close.
|
|
if top_line_num and charpos then
|
|
self.book_style_tweak_last_edit_pos = {top_line_num, charpos}
|
|
else
|
|
local prev_pos = self.book_style_tweak_last_edit_pos
|
|
if type(prev_pos) == "table" and prev_pos[1] and prev_pos[2] then
|
|
return prev_pos[1], prev_pos[2]
|
|
end
|
|
return nil, nil -- no previous position known
|
|
end
|
|
end,
|
|
reset_button_text = _("Restore"),
|
|
reset_callback = function(content) -- Will add a Reset button
|
|
return self.book_style_tweak or "", _("Book tweak restored")
|
|
end,
|
|
save_button_text = SAVE_BUTTON_LABEL,
|
|
close_save_button_text = SAVE_BUTTON_LABEL,
|
|
save_callback = function(content, closing) -- Will add Save/Close buttons
|
|
if content and content == "" then
|
|
content = nil -- we store nil when empty
|
|
end
|
|
local was_empty = self.book_style_tweak == nil
|
|
local is_empty = content == nil
|
|
local tweak_updated = content ~= self.book_style_tweak
|
|
local should_apply = false
|
|
local msg -- returned and shown as a notification by InputDialog
|
|
if was_empty and not is_empty then
|
|
-- Tweak was empty, and so just created: enable book tweak
|
|
-- so it's immediately applied, and checked in the menu
|
|
self.book_style_tweak_enabled = true
|
|
should_apply = true
|
|
msg = _("Book tweak created, applying…")
|
|
elseif is_empty then
|
|
if not was_empty and self.book_style_tweak_enabled then
|
|
-- Tweak was enabled, but has been emptied: make it
|
|
-- disabled in the menu, but apply CSS without it
|
|
should_apply = true
|
|
msg = _("Book tweak removed, rendering…")
|
|
else
|
|
msg = _("Book tweak emptied and removed.")
|
|
end
|
|
self.book_style_tweak_enabled = false
|
|
elseif tweak_updated then
|
|
if self.book_style_tweak_enabled then
|
|
should_apply = true
|
|
msg = _("Book tweak updated, applying…")
|
|
else
|
|
msg = _("Book tweak saved (not enabled).")
|
|
end
|
|
else
|
|
msg = NOT_MODIFIED_MSG
|
|
end
|
|
self.book_style_tweak = content
|
|
-- We always close the editor when this callback is called.
|
|
-- If closing=true, InputDialog will call close_callback().
|
|
-- If not, let's do it ourselves.
|
|
if not closing then
|
|
UIManager:close(editor)
|
|
end
|
|
if should_apply then
|
|
-- Let menu be closed and previous page be refreshed,
|
|
-- so one can see how the text is changed by the tweak.
|
|
touchmenu_instance:closeMenu()
|
|
UIManager:scheduleIn(0.2, function()
|
|
self:updateCssText(true) -- have it applied
|
|
end)
|
|
else
|
|
touchmenu_instance:updateItems()
|
|
end
|
|
editor.save_callback_called = true
|
|
return true, msg
|
|
end,
|
|
close_callback = function()
|
|
-- save_callback() will always have shown some notification,
|
|
-- so don't add another one
|
|
if not editor.save_callback_called then
|
|
UIManager:show(Notification:new{
|
|
text = NOT_MODIFIED_MSG,
|
|
})
|
|
-- This has to be the same message above and below: when
|
|
-- discarding, we can't prevent these 2 notifications from
|
|
-- being shown: having them identical will hide that.
|
|
end
|
|
end,
|
|
close_discarded_notif_text = NOT_MODIFIED_MSG,
|
|
}
|
|
UIManager:show(editor)
|
|
editor:onShowKeyboard(true)
|
|
-- ignore first hold release, as we may be invoked from hold
|
|
end
|
|
|
|
return ReaderStyleTweak
|