Files
koreader-mirror/frontend/apps/reader/modules/readermenu.lua

575 lines
20 KiB
Lua

local BD = require("ui/bidi")
local CenterContainer = require("ui/widget/container/centercontainer")
local ConfirmBox = require("ui/widget/confirmbox")
local Device = require("device")
local Event = require("ui/event")
local InputContainer = require("ui/widget/container/inputcontainer")
local PluginLoader = require("pluginloader")
local Screensaver = require("ui/screensaver")
local UIManager = require("ui/uimanager")
local logger = require("logger")
local dbg = require("dbg")
local util = require("util")
local Screen = Device.screen
local _ = require("gettext")
local T = require("ffi/util").template
local ReaderMenu = InputContainer:extend{
tab_item_table = nil,
menu_items = nil, -- table, mandatory
registered_widgets = nil, -- array
}
function ReaderMenu:init()
self.menu_items = {
["KOMenu:menu_buttons"] = {
-- top menu
},
-- items in top menu
navi = {
icon = "appbar.navigation",
},
typeset = {
icon = "appbar.typeset",
},
setting = {
icon = "appbar.settings",
},
tools = {
icon = "appbar.tools",
},
search = {
icon = "appbar.search",
},
filemanager = {
icon = "appbar.filebrowser",
remember = false,
callback = function()
self:onTapCloseMenu()
local file = self.ui.document.file
self.ui:onClose()
self.ui:showFileManager(file)
end,
},
main = {
icon = "appbar.menu",
}
}
self.registered_widgets = {}
self:registerKeyEvents()
if G_reader_settings:has("activate_menu") then
self.activation_menu = G_reader_settings:readSetting("activate_menu")
else
self.activation_menu = "swipe_tap"
end
-- delegate gesture listener to readerui, NOP our own
self.ges_events = nil
end
function ReaderMenu:onGesture() end
function ReaderMenu:registerKeyEvents()
if Device:hasKeys() then
if Device:isTouchDevice() then
self.key_events.PressMenu = { { "Menu" } }
if Device:hasFewKeys() then
self.key_events.PressMenu = { { { "Menu", "Right" } } }
end
else
-- Map Menu key to top menu only, because the bottom menu is only designed for touch devices.
self.key_events.KeyPressShowMenu = { { "Menu" } }
if Device:hasFewKeys() then
self.key_events.KeyPressShowMenu = { { { "Menu", "Right" } } }
end
end
end
end
ReaderMenu.onPhysicalKeyboardConnected = ReaderMenu.registerKeyEvents
function ReaderMenu:getPreviousFile()
return require("readhistory"):getPreviousFile(self.ui.document.file)
end
function ReaderMenu:initGesListener()
if not Device:isTouchDevice() then return end
local DTAP_ZONE_MENU = G_defaults:readSetting("DTAP_ZONE_MENU")
local DTAP_ZONE_MENU_EXT = G_defaults:readSetting("DTAP_ZONE_MENU_EXT")
self.ui:registerTouchZones({
{
id = "readermenu_tap",
ges = "tap",
screen_zone = {
ratio_x = DTAP_ZONE_MENU.x, ratio_y = DTAP_ZONE_MENU.y,
ratio_w = DTAP_ZONE_MENU.w, ratio_h = DTAP_ZONE_MENU.h,
},
overrides = {
"tap_forward",
"tap_backward",
},
handler = function(ges) return self:onTapShowMenu(ges) end,
},
{
id = "readermenu_ext_tap",
ges = "tap",
screen_zone = {
ratio_x = DTAP_ZONE_MENU_EXT.x, ratio_y = DTAP_ZONE_MENU_EXT.y,
ratio_w = DTAP_ZONE_MENU_EXT.w, ratio_h = DTAP_ZONE_MENU_EXT.h,
},
overrides = {
"readermenu_tap",
},
handler = function(ges) return self:onTapShowMenu(ges) end,
},
{
id = "readermenu_swipe",
ges = "swipe",
screen_zone = {
ratio_x = DTAP_ZONE_MENU.x, ratio_y = DTAP_ZONE_MENU.y,
ratio_w = DTAP_ZONE_MENU.w, ratio_h = DTAP_ZONE_MENU.h,
},
overrides = {
"rolling_swipe",
"paging_swipe",
},
handler = function(ges) return self:onSwipeShowMenu(ges) end,
},
{
id = "readermenu_ext_swipe",
ges = "swipe",
screen_zone = {
ratio_x = DTAP_ZONE_MENU_EXT.x, ratio_y = DTAP_ZONE_MENU_EXT.y,
ratio_w = DTAP_ZONE_MENU_EXT.w, ratio_h = DTAP_ZONE_MENU_EXT.h,
},
overrides = {
"readermenu_swipe",
},
handler = function(ges) return self:onSwipeShowMenu(ges) end,
},
{
id = "readermenu_pan",
ges = "pan",
screen_zone = {
ratio_x = DTAP_ZONE_MENU.x, ratio_y = DTAP_ZONE_MENU.y,
ratio_w = DTAP_ZONE_MENU.w, ratio_h = DTAP_ZONE_MENU.h,
},
overrides = {
"rolling_pan",
"paging_pan",
},
handler = function(ges) return self:onSwipeShowMenu(ges) end,
},
{
id = "readermenu_ext_pan",
ges = "pan",
screen_zone = {
ratio_x = DTAP_ZONE_MENU_EXT.x, ratio_y = DTAP_ZONE_MENU_EXT.y,
ratio_w = DTAP_ZONE_MENU_EXT.w, ratio_h = DTAP_ZONE_MENU_EXT.h,
},
overrides = {
"readermenu_pan",
},
handler = function(ges) return self:onSwipeShowMenu(ges) end,
},
})
end
ReaderMenu.onReaderReady = ReaderMenu.initGesListener
function ReaderMenu:setUpdateItemTable()
for _, widget in pairs(self.registered_widgets) do
local ok, err = pcall(widget.addToMainMenu, widget, self.menu_items)
if not ok then
logger.err("failed to register widget", widget.name, err)
end
end
-- typeset tab
self.menu_items.document_settings = {
text = _("Document settings"),
sub_item_table = {
{
text = _("Reset document settings to default"),
keep_menu_open = true,
callback = function()
UIManager:show(ConfirmBox:new{
text = _("Reset current document settings to their default values?\n\nReading position, highlights and bookmarks will be kept.\nThe document will be reloaded."),
ok_text = _("Reset"),
ok_callback = function()
local current_file = self.ui.document.file
self:onTapCloseMenu()
self.ui:onClose()
require("apps/filemanager/filemanagerutil").resetDocumentSettings(current_file)
require("apps/reader/readerui"):showReader(current_file)
end,
})
end,
},
{
text = _("Save document settings as default"),
keep_menu_open = true,
separator = true,
callback = function()
UIManager:show(ConfirmBox:new{
text = _("Save current document settings as default values?"),
ok_text = _("Save"),
ok_callback = function()
self:onTapCloseMenu()
self:saveDocumentSettingsAsDefault()
UIManager:show(require("ui/widget/notification"):new{
text = _("Default settings updated"),
})
end,
})
end,
},
},
}
if not Device:isTouchDevice() then
-- This menu entry is a duplicate of the one found in page_turns for touch devices
-- but we need to add it here for non-touch devices.
table.insert(self.menu_items.document_settings.sub_item_table, {
text_func = function()
local text = _("Invert document-related dialogs")
if G_reader_settings:isTrue("invert_ui_layout") then
text = text .. ""
end
return text
end,
checked_func = function()
return self.view:shouldInvertBiDiLayoutMirroring()
end,
callback = function()
UIManager:broadcastEvent(Event:new("ToggleUILayoutMiroring"))
end,
hold_callback = function(touchmenu_instance)
local invert_ui_layout = G_reader_settings:isTrue("invert_ui_layout")
local MultiConfirmBox = require("ui/widget/multiconfirmbox")
UIManager:show(MultiConfirmBox:new{
text = invert_ui_layout and _("The default (★) for newly opened books is to Invert document-related dialogs.\n\nWould you like to change it?")
or _("The default (★) for newly opened books is not to Invert document-related dialogs.\n\nWould you like to change it?"),
choice1_text_func = function()
return invert_ui_layout and _("Don't invert") or _("Don't invert") .." (★)"
end,
choice1_callback = function()
G_reader_settings:makeFalse("invert_ui_layout")
if touchmenu_instance then touchmenu_instance:updateItems() end
end,
choice2_text_func = function()
return invert_ui_layout and _("Invert") .." (★)" or _("Invert")
end,
choice2_callback = function()
G_reader_settings:makeTrue("invert_ui_layout")
if touchmenu_instance then touchmenu_instance:updateItems() end
end,
})
end,
help_text = _([[
When enabled the UI direction for the Table of Contents, Book Map, and Page Browser dialogs will mirror the default UI direction.
Useful when used alongside 'Invert page turn taps and swipes'.]]),
})
end
self.menu_items.page_overlap = dofile("frontend/ui/elements/page_overlap.lua")
-- settings tab
-- insert common settings
for id, common_setting in pairs(dofile("frontend/ui/elements/common_settings_menu_table.lua")) do
self.menu_items[id] = common_setting
end
if Device:isTouchDevice() then
-- Settings > Taps & Gestures; mostly concerns touch related page turn stuff, and only applies to Reader
self.menu_items.page_turns = dofile("frontend/ui/elements/page_turns.lua")
end
-- Settings > Navigation; while also related to page turns, this mostly concerns physical keys, and applies *everywhere*
if Device:hasKeys() then
self.menu_items.physical_buttons_setup = dofile("frontend/ui/elements/physical_buttons.lua")
end
-- insert DjVu render mode submenu just before the last entry (show advanced)
-- this is a bit of a hack
if self.ui.document.is_djvu then
self.menu_items.djvu_render_mode = self.view:getRenderModeMenuTable()
end
if Device:supportsScreensaver() then
local screensaver_sub_item_table = dofile("frontend/ui/elements/screensaver_menu.lua")
table.insert(screensaver_sub_item_table, {
text = _("Do not show this book cover on sleep screen"),
enabled_func = function()
local screensaver_type = G_reader_settings:readSetting("screensaver_type")
return screensaver_type == "cover" or screensaver_type == "disable"
end,
checked_func = function()
return self.ui.doc_settings:isTrue("exclude_screensaver")
end,
callback = function()
if Screensaver.isExcluded(self.ui) then
self.ui.doc_settings:makeFalse("exclude_screensaver")
else
self.ui.doc_settings:makeTrue("exclude_screensaver")
end
self.ui:saveSettings()
end,
})
self.menu_items.screensaver = {
text = _("Sleep screen"),
sub_item_table = screensaver_sub_item_table,
}
end
-- tools tab
self.menu_items.plugin_management = {
text = _("Plugin management"),
sub_item_table = PluginLoader:genPluginManagerSubItem(),
}
self.menu_items.patch_management = dofile("frontend/ui/elements/patch_management.lua")
-- main menu tab
-- insert common info
for id, common_setting in pairs(dofile("frontend/ui/elements/common_info_menu_table.lua")) do
self.menu_items[id] = common_setting
end
-- insert common exit for reader
for id, common_setting in pairs(dofile("frontend/ui/elements/common_exit_menu_table.lua")) do
self.menu_items[id] = common_setting
end
self.menu_items.open_previous_document = {
text_func = function()
local previous_file = self:getPreviousFile()
if not G_reader_settings:isTrue("open_last_menu_show_filename") or not previous_file then
return _("Open previous document")
end
local path, file_name = util.splitFilePathName(previous_file) -- luacheck: no unused
return T(_("Previous: %1"), BD.filename(file_name))
end,
enabled_func = function()
return self:getPreviousFile() ~= nil
end,
callback = function()
self.ui:onOpenLastDoc()
end,
hold_callback = function()
local previous_file = self:getPreviousFile()
UIManager:show(ConfirmBox:new{
text = T(_("Would you like to open the previous document: %1?"), BD.filepath(previous_file)),
ok_text = _("OK"),
ok_callback = function()
self.ui:switchDocument(previous_file)
end,
})
end
}
-- NOTE: This is cached via require for ui/plugin/insert_menu's sake...
local order = require("ui/elements/reader_menu_order")
local MenuSorter = require("ui/menusorter")
self.tab_item_table = MenuSorter:mergeAndSort("reader", self.menu_items, order)
end
dbg:guard(ReaderMenu, 'setUpdateItemTable',
function(self)
local mock_menu_items = {}
for _, widget in pairs(self.registered_widgets) do
-- make sure addToMainMenu works in debug mode
widget:addToMainMenu(mock_menu_items)
end
end)
function ReaderMenu:saveDocumentSettingsAsDefault()
local prefix
if self.ui.rolling then
G_reader_settings:saveSetting("cre_font", self.ui.font.font_face)
G_reader_settings:saveSetting("copt_css", self.ui.document.default_css)
local style_tweaks = G_reader_settings:readSetting("style_tweaks")
for tweak_id, is_enabled in pairs(self.ui.styletweak.doc_tweaks) do
style_tweaks[tweak_id] = is_enabled or nil
end
prefix = "copt_"
else
prefix = "kopt_"
end
for k, v in pairs(self.ui.document.configurable) do
G_reader_settings:saveSetting(prefix .. k, v)
end
end
function ReaderMenu:exitOrRestart(callback, force)
-- Only restart sets a callback, which suits us just fine for this check ;)
if callback and not force and not Device:isStartupScriptUpToDate() then
UIManager:show(ConfirmBox:new{
text = _("KOReader's startup script has been updated. You'll need to completely exit KOReader to finalize the update."),
ok_text = _("Restart anyway"),
ok_callback = function()
self:exitOrRestart(callback, true)
end,
})
return
end
self:onTapCloseMenu()
UIManager:nextTick(function()
self.ui:onClose()
if callback then
callback()
end
end)
end
function ReaderMenu:onShowMenu(tab_index, do_not_show)
if self.tab_item_table == nil then
self:setUpdateItemTable()
end
local menu_container = CenterContainer:new{
covers_header = true,
ignore = "height",
dimen = Screen:getSize(),
}
local main_menu
if Device:isTouchDevice() or Device:hasDPad() then
local TouchMenu = require("ui/widget/touchmenu")
main_menu = TouchMenu:new{
width = Screen:getWidth(),
last_index = tab_index or self.last_tab_index,
tab_item_table = self.tab_item_table,
show_parent = menu_container,
not_shown = do_not_show,
}
else
local Menu = require("ui/widget/menu")
main_menu = Menu:new{
title = _("Document menu"),
item_table = Menu.itemTableFromTouchMenu(self.tab_item_table),
width = Screen:getWidth() - 100,
show_parent = menu_container,
}
end
main_menu.close_callback = function()
self:onCloseReaderMenu()
end
main_menu.touch_menu_callback = function ()
self.ui:handleEvent(Event:new("CloseConfigMenu"))
end
menu_container[1] = main_menu
-- maintain a reference to menu_container
self.menu_container = menu_container
if not do_not_show then
UIManager:show(menu_container)
end
return true
end
function ReaderMenu:onCloseReaderMenu()
if not self.menu_container then return true end
self.last_tab_index = self.menu_container[1].last_index
self:onSaveSettings()
UIManager:close(self.menu_container)
self.menu_container = nil
return true
end
function ReaderMenu:onSetDimensions(dimen)
-- This widget doesn't support in-place layout updates, so, close & reopen
if self.menu_container then
self:onCloseReaderMenu()
self:onShowMenu()
end
-- update gesture zones according to new screen dimen
-- (On CRe, this will get called a second time by ReaderReady once the document is reloaded).
self:initGesListener()
end
function ReaderMenu:_getTabIndexFromLocation(ges)
if self.tab_item_table == nil then
self:setUpdateItemTable()
end
if not ges then
return self.last_tab_index
-- if the start position is far right
elseif ges.pos.x > Screen:getWidth() * (2/3) then
return BD.mirroredUILayout() and 1 or #self.tab_item_table
-- if the start position is far left
elseif ges.pos.x < Screen:getWidth() * (1/3) then
return BD.mirroredUILayout() and #self.tab_item_table or 1
-- if center return the last index
else
return self.last_tab_index
end
end
function ReaderMenu:onSwipeShowMenu(ges)
if self.activation_menu ~= "tap" and ges.direction == "south" then
if G_reader_settings:nilOrTrue("show_bottom_menu") then
self.ui:handleEvent(Event:new("ShowConfigMenu"))
end
self:onShowMenu(self:_getTabIndexFromLocation(ges))
self.ui:handleEvent(Event:new("HandledAsSwipe")) -- cancel any pan scroll made
return true
end
end
function ReaderMenu:onTapShowMenu(ges)
if self.activation_menu ~= "swipe" then
if G_reader_settings:nilOrTrue("show_bottom_menu") then
self.ui:handleEvent(Event:new("ShowConfigMenu"))
end
self:onShowMenu(self:_getTabIndexFromLocation(ges))
return true
end
end
function ReaderMenu:onPressMenu()
if G_reader_settings:nilOrTrue("show_bottom_menu") then
self.ui:handleEvent(Event:new("ShowConfigMenu"))
end
self:onShowMenu()
return true
end
function ReaderMenu:onKeyPressShowMenu(_, key_ev)
return self:onShowMenu()
end
function ReaderMenu:onTapCloseMenu()
self:onCloseReaderMenu()
self.ui:handleEvent(Event:new("CloseConfigMenu"))
end
function ReaderMenu:onReadSettings(config)
self.last_tab_index = config:readSetting("readermenu_tab_index") or 1
end
function ReaderMenu:onSaveSettings()
self.ui.doc_settings:saveSetting("readermenu_tab_index", self.last_tab_index)
end
function ReaderMenu:onMenuSearch()
self:onShowMenu(nil, true)
self.menu_container[1]:onShowMenuSearch()
end
function ReaderMenu:registerToMainMenu(widget)
table.insert(self.registered_widgets, widget)
end
function ReaderMenu:onShowCloudStorage()
local CloudStorage = require("apps/cloudstorage/cloudstorage")
UIManager:show(CloudStorage:new{ ui = self.ui })
return true
end
return ReaderMenu