mirror of
https://github.com/koreader/koreader.git
synced 2025-12-24 12:14:05 +01:00
The Kindle swipe animations & physical key inversion apply *everywhere*, so we need this accessible in the FM, too. Move it to Navigation, which is where it already was on !Touch devices.
1108 lines
44 KiB
Lua
1108 lines
44 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 FFIUtil = require("ffi/util")
|
||
local InputContainer = require("ui/widget/container/inputcontainer")
|
||
local PluginLoader = require("pluginloader")
|
||
local SetDefaults = require("apps/filemanager/filemanagersetdefaults")
|
||
local Size = require("ui/size")
|
||
local UIManager = require("ui/uimanager")
|
||
local Screen = Device.screen
|
||
local dbg = require("dbg")
|
||
local lfs = require("libs/libkoreader-lfs")
|
||
local logger = require("logger")
|
||
local util = require("util")
|
||
local _ = require("gettext")
|
||
local N_ = _.ngettext
|
||
local T = FFIUtil.template
|
||
|
||
local FileManagerMenu = InputContainer:extend{
|
||
tab_item_table = nil,
|
||
menu_items = nil, -- table, mandatory
|
||
registered_widgets = nil,
|
||
}
|
||
|
||
function FileManagerMenu:init()
|
||
self.menu_items = {
|
||
["KOMenu:menu_buttons"] = {
|
||
-- top menu
|
||
},
|
||
-- items in top menu
|
||
filemanager_settings = {
|
||
icon = "appbar.filebrowser",
|
||
},
|
||
setting = {
|
||
icon = "appbar.settings",
|
||
},
|
||
tools = {
|
||
icon = "appbar.tools",
|
||
},
|
||
search = {
|
||
icon = "appbar.search",
|
||
},
|
||
main = {
|
||
icon = "appbar.menu",
|
||
},
|
||
}
|
||
|
||
self.registered_widgets = {}
|
||
|
||
self:registerKeyEvents()
|
||
|
||
self.activation_menu = G_reader_settings:readSetting("activate_menu")
|
||
if self.activation_menu == nil then
|
||
self.activation_menu = "swipe_tap"
|
||
end
|
||
end
|
||
|
||
function FileManagerMenu:registerKeyEvents()
|
||
if Device:hasKeys() then
|
||
self.key_events.ShowMenu = { { "Menu" } }
|
||
end
|
||
end
|
||
|
||
FileManagerMenu.onPhysicalKeyboardConnected = FileManagerMenu.registerKeyEvents
|
||
|
||
function FileManagerMenu: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:registerTouchZones({
|
||
{
|
||
id = "filemanager_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,
|
||
},
|
||
handler = function(ges) return self:onTapShowMenu(ges) end,
|
||
},
|
||
{
|
||
id = "filemanager_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 = {
|
||
"filemanager_tap",
|
||
},
|
||
handler = function(ges) return self:onTapShowMenu(ges) end,
|
||
},
|
||
{
|
||
id = "filemanager_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 = "filemanager_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 = {
|
||
"filemanager_swipe",
|
||
},
|
||
handler = function(ges) return self:onSwipeShowMenu(ges) end,
|
||
},
|
||
})
|
||
end
|
||
|
||
function FileManagerMenu:onOpenLastDoc()
|
||
local last_file = G_reader_settings:readSetting("lastfile")
|
||
if not last_file or lfs.attributes(last_file, "mode") ~= "file" then
|
||
local InfoMessage = require("ui/widget/infomessage")
|
||
UIManager:show(InfoMessage:new{
|
||
text = _("Cannot open last document"),
|
||
})
|
||
return
|
||
end
|
||
|
||
-- Only close menu if we were called from the menu
|
||
if self.menu_container then
|
||
-- Mimic's FileManager's onShowingReader refresh optimizations
|
||
self.ui.tearing_down = true
|
||
self.ui.dithered = nil
|
||
self:onCloseFileManagerMenu()
|
||
end
|
||
|
||
local ReaderUI = require("apps/reader/readerui")
|
||
ReaderUI:showReader(last_file)
|
||
end
|
||
|
||
function FileManagerMenu:setUpdateItemTable()
|
||
|
||
-- setting tab
|
||
self.menu_items.filebrowser_settings = {
|
||
text = _("Settings"),
|
||
sub_item_table = {
|
||
{
|
||
text = _("Show hidden files"),
|
||
checked_func = function() return self.ui.file_chooser.show_hidden end,
|
||
callback = function() self.ui:toggleHiddenFiles() end,
|
||
},
|
||
{
|
||
text = _("Show unsupported files"),
|
||
checked_func = function() return self.ui.file_chooser.show_unsupported end,
|
||
callback = function() self.ui:toggleUnsupportedFiles() end,
|
||
separator = true,
|
||
},
|
||
{
|
||
text = _("Classic mode settings"),
|
||
sub_item_table = {
|
||
{
|
||
text = _("Items per page"),
|
||
help_text = _([[This sets the number of items per page in:
|
||
- File browser, history and favorites in 'classic' display mode
|
||
- Search results and folder shortcuts
|
||
- File and folder selection
|
||
- Calibre and OPDS browsers/search results]]),
|
||
callback = function()
|
||
local SpinWidget = require("ui/widget/spinwidget")
|
||
local Menu = require("ui/widget/menu")
|
||
local default_perpage = Menu.items_per_page_default
|
||
local curr_perpage = G_reader_settings:readSetting("items_per_page") or default_perpage
|
||
local items = SpinWidget:new{
|
||
value = curr_perpage,
|
||
value_min = 6,
|
||
value_max = 24,
|
||
default_value = default_perpage,
|
||
title_text = _("Items per page"),
|
||
keep_shown_on_apply = true,
|
||
callback = function(spin)
|
||
G_reader_settings:saveSetting("items_per_page", spin.value)
|
||
self.ui:onRefresh()
|
||
end
|
||
}
|
||
UIManager:show(items)
|
||
end,
|
||
},
|
||
{
|
||
text = _("Item font size"),
|
||
callback = function()
|
||
local SpinWidget = require("ui/widget/spinwidget")
|
||
local Menu = require("ui/widget/menu")
|
||
local curr_perpage = G_reader_settings:readSetting("items_per_page") or Menu.items_per_page_default
|
||
local default_font_size = Menu.getItemFontSize(curr_perpage)
|
||
local curr_font_size = G_reader_settings:readSetting("items_font_size") or default_font_size
|
||
local items_font = SpinWidget:new{
|
||
value = curr_font_size,
|
||
value_min = 10,
|
||
value_max = 72,
|
||
default_value = default_font_size,
|
||
keep_shown_on_apply = true,
|
||
title_text = _("Item font size"),
|
||
callback = function(spin)
|
||
if spin.value == default_font_size then
|
||
-- We can't know if the user has set a size or hit "Use default", but
|
||
-- assume that if it is the default font size, he will prefer to have
|
||
-- our default font size if he later updates per-page
|
||
G_reader_settings:delSetting("items_font_size")
|
||
else
|
||
G_reader_settings:saveSetting("items_font_size", spin.value)
|
||
end
|
||
self.ui:onRefresh()
|
||
end
|
||
}
|
||
UIManager:show(items_font)
|
||
end,
|
||
},
|
||
{
|
||
text = _("Shrink item font size to fit more text"),
|
||
checked_func = function()
|
||
return G_reader_settings:isTrue("items_multilines_show_more_text")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrFalse("items_multilines_show_more_text")
|
||
self.ui:onRefresh()
|
||
end,
|
||
separator = true,
|
||
},
|
||
{
|
||
text = _("Show opened files in bold"),
|
||
checked_func = function()
|
||
return G_reader_settings:readSetting("show_file_in_bold") == "opened"
|
||
end,
|
||
callback = function()
|
||
if G_reader_settings:readSetting("show_file_in_bold") == "opened" then
|
||
G_reader_settings:saveSetting("show_file_in_bold", false)
|
||
else
|
||
G_reader_settings:saveSetting("show_file_in_bold", "opened")
|
||
end
|
||
self.ui:onRefresh()
|
||
end,
|
||
},
|
||
{
|
||
text = _("Show new (not yet opened) files in bold"),
|
||
checked_func = function()
|
||
return G_reader_settings:hasNot("show_file_in_bold")
|
||
end,
|
||
callback = function()
|
||
if G_reader_settings:hasNot("show_file_in_bold") then
|
||
G_reader_settings:saveSetting("show_file_in_bold", false)
|
||
else
|
||
G_reader_settings:delSetting("show_file_in_bold")
|
||
end
|
||
self.ui:onRefresh()
|
||
end,
|
||
},
|
||
},
|
||
},
|
||
{
|
||
text = _("History settings"),
|
||
sub_item_table = {
|
||
{
|
||
text = _("Shorten date/time"),
|
||
checked_func = function()
|
||
return G_reader_settings:isTrue("history_datetime_short")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrFalse("history_datetime_short")
|
||
require("readhistory"):reload(true)
|
||
end,
|
||
separator = true,
|
||
},
|
||
{
|
||
text = _("Clear history of deleted files"),
|
||
callback = function()
|
||
UIManager:show(ConfirmBox:new{
|
||
text = _("Clear history of deleted files?"),
|
||
ok_text = _("Clear"),
|
||
ok_callback = function()
|
||
require("readhistory"):clearMissing()
|
||
end,
|
||
})
|
||
end,
|
||
},
|
||
{
|
||
text = _("Auto-remove deleted or purged items from history"),
|
||
checked_func = function()
|
||
return G_reader_settings:isTrue("autoremove_deleted_items_from_history")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrFalse("autoremove_deleted_items_from_history")
|
||
end,
|
||
separator = true,
|
||
},
|
||
{
|
||
text = _("Show filename in Open last/previous menu items"),
|
||
checked_func = function()
|
||
return G_reader_settings:isTrue("open_last_menu_show_filename")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrFalse("open_last_menu_show_filename")
|
||
end,
|
||
},
|
||
},
|
||
},
|
||
{
|
||
text = _("Home folder settings"),
|
||
sub_item_table = {
|
||
{
|
||
text = _("Set home folder"),
|
||
callback = function()
|
||
local text
|
||
local home_dir = G_reader_settings:readSetting("home_dir")
|
||
if home_dir then
|
||
text = T(_("Home folder is set to:\n%1"), home_dir)
|
||
else
|
||
text = _("Home folder is not set.")
|
||
home_dir = Device.home_dir
|
||
end
|
||
UIManager:show(ConfirmBox:new{
|
||
text = text .. "\n" .. _("Choose new folder to set as home?"),
|
||
ok_text = _("Choose folder"),
|
||
ok_callback = function()
|
||
local path_chooser = require("ui/widget/pathchooser"):new{
|
||
select_file = false,
|
||
show_files = false,
|
||
path = home_dir,
|
||
onConfirm = function(new_path)
|
||
G_reader_settings:saveSetting("home_dir", new_path)
|
||
end
|
||
}
|
||
UIManager:show(path_chooser)
|
||
end,
|
||
})
|
||
end,
|
||
},
|
||
{
|
||
text = _("Shorten home folder"),
|
||
checked_func = function()
|
||
return G_reader_settings:nilOrTrue("shorten_home_dir")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrTrue("shorten_home_dir")
|
||
local FileManager = require("apps/filemanager/filemanager")
|
||
if FileManager.instance then FileManager.instance:reinit() end
|
||
end,
|
||
help_text = _([[
|
||
"Shorten home folder" will display the home folder itself as "Home" instead of its full path.
|
||
|
||
Assuming the home folder is:
|
||
`/mnt/onboard/.books`
|
||
A subfolder will be shortened from:
|
||
`/mnt/onboard/.books/Manga/Cells at Work`
|
||
To:
|
||
`Manga/Cells at Work`.]]),
|
||
},
|
||
{
|
||
text = _("Lock home folder"),
|
||
enabled_func = function()
|
||
return G_reader_settings:has("home_dir")
|
||
end,
|
||
checked_func = function()
|
||
return G_reader_settings:isTrue("lock_home_folder")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrFalse("lock_home_folder")
|
||
self.ui:onRefresh()
|
||
end,
|
||
},
|
||
},
|
||
separator = true,
|
||
},
|
||
{
|
||
text = _("Info lists items per page"),
|
||
help_text = _([[This sets the number of items per page in:
|
||
- Book information
|
||
- Dictionary and Wikipedia lookup history
|
||
- Reading statistics details
|
||
- A few other plugins]]),
|
||
keep_menu_open = true,
|
||
callback = function()
|
||
local SpinWidget = require("ui/widget/spinwidget")
|
||
local KeyValuePage = require("ui/widget/keyvaluepage")
|
||
local default_perpage = KeyValuePage:getDefaultKeyValuesPerPage()
|
||
local curr_perpage = G_reader_settings:readSetting("keyvalues_per_page") or default_perpage
|
||
local items = SpinWidget:new{
|
||
value = curr_perpage,
|
||
value_min = 10,
|
||
value_max = 24,
|
||
default_value = default_perpage,
|
||
title_text = _("Info lists items per page"),
|
||
callback = function(spin)
|
||
if spin.value == default_perpage then
|
||
-- We can't know if the user has set a value or hit "Use default", but
|
||
-- assume that if it is the default, he will prefer to stay with our
|
||
-- default if he later changes screen DPI
|
||
G_reader_settings:delSetting("keyvalues_per_page")
|
||
else
|
||
G_reader_settings:saveSetting("keyvalues_per_page", spin.value)
|
||
end
|
||
end
|
||
}
|
||
UIManager:show(items)
|
||
end,
|
||
},
|
||
},
|
||
}
|
||
|
||
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
|
||
|
||
self.menu_items.sort_by = self:getSortingMenuTable()
|
||
self.menu_items.reverse_sorting = {
|
||
text = _("Reverse sorting"),
|
||
checked_func = function()
|
||
return G_reader_settings:isTrue("reverse_collate")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrFalse("reverse_collate")
|
||
self.ui.file_chooser:refreshPath()
|
||
end,
|
||
}
|
||
self.menu_items.sort_mixed = {
|
||
text = _("Folders and files mixed"),
|
||
enabled_func = function()
|
||
local collate = G_reader_settings:readSetting("collate")
|
||
return collate ~= "size" and
|
||
collate ~= "type" and
|
||
collate ~= "percent_unopened_first" and
|
||
collate ~= "percent_unopened_last"
|
||
end,
|
||
checked_func = function()
|
||
local collate = G_reader_settings:readSetting("collate")
|
||
return G_reader_settings:isTrue("collate_mixed") and
|
||
collate ~= "size" and
|
||
collate ~= "type" and
|
||
collate ~= "percent_unopened_first" and
|
||
collate ~= "percent_unopened_last"
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrFalse("collate_mixed")
|
||
self.ui.file_chooser:refreshPath()
|
||
end,
|
||
}
|
||
self.menu_items.start_with = self:getStartWithMenuTable()
|
||
|
||
if Device:supportsScreensaver() then
|
||
self.menu_items.screensaver = {
|
||
text = _("Screensaver"),
|
||
sub_item_table = require("ui/elements/screensaver_menu"),
|
||
}
|
||
end
|
||
|
||
-- 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
|
||
|
||
-- settings > Navigation; this mostly concerns platform-specific features or physical keys, and applies *everywhere*
|
||
self.menu_items.platform_navigation_features = require("ui/elements/platform_navigation")
|
||
|
||
-- settings tab - Document submenu
|
||
self.menu_items.document_metadata_location_move = {
|
||
text = _("Move book metadata"),
|
||
keep_menu_open = true,
|
||
callback = function()
|
||
self:moveBookMetadata()
|
||
end,
|
||
}
|
||
|
||
-- tools tab
|
||
self.menu_items.advanced_settings = {
|
||
text = _("Advanced settings"),
|
||
callback = function()
|
||
SetDefaults:ConfirmEdit()
|
||
end,
|
||
}
|
||
self.menu_items.plugin_management = {
|
||
text = _("Plugin management"),
|
||
sub_item_table = PluginLoader:genPluginManagerSubItem()
|
||
}
|
||
|
||
self.menu_items.developer_options = {
|
||
text = _("Developer options"),
|
||
sub_item_table = {
|
||
{
|
||
text = _("Clear caches"),
|
||
callback = function()
|
||
UIManager:show(ConfirmBox:new{
|
||
text = _("Clear the cache folder?"),
|
||
ok_callback = function()
|
||
local DataStorage = require("datastorage")
|
||
local cachedir = DataStorage:getDataDir() .. "/cache"
|
||
if lfs.attributes(cachedir, "mode") == "directory" then
|
||
FFIUtil.purgeDir(cachedir)
|
||
end
|
||
lfs.mkdir(cachedir)
|
||
-- Also remove from the Cache objet references to the cache files we've just deleted
|
||
local Cache = require("cache")
|
||
Cache.cached = {}
|
||
UIManager:askForRestart(_("Caches cleared. Please restart KOReader."))
|
||
end,
|
||
})
|
||
end,
|
||
},
|
||
{
|
||
text = _("Enable debug logging"),
|
||
checked_func = function()
|
||
return G_reader_settings:isTrue("debug")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrFalse("debug")
|
||
if G_reader_settings:isTrue("debug") then
|
||
dbg:turnOn()
|
||
else
|
||
dbg:setVerbose(false)
|
||
dbg:turnOff()
|
||
G_reader_settings:makeFalse("debug_verbose")
|
||
end
|
||
end,
|
||
},
|
||
{
|
||
text = _("Enable verbose debug logging"),
|
||
enabled_func = function()
|
||
return G_reader_settings:isTrue("debug")
|
||
end,
|
||
checked_func = function()
|
||
return G_reader_settings:isTrue("debug_verbose")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrFalse("debug_verbose")
|
||
if G_reader_settings:isTrue("debug_verbose") then
|
||
dbg:setVerbose(true)
|
||
else
|
||
dbg:setVerbose(false)
|
||
end
|
||
end,
|
||
},
|
||
},
|
||
}
|
||
if Device:isKobo() and not Device:isSunxi() then
|
||
table.insert(self.menu_items.developer_options.sub_item_table, {
|
||
text = _("Disable forced 8-bit pixel depth"),
|
||
checked_func = function()
|
||
return G_reader_settings:isTrue("dev_startup_no_fbdepth")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrFalse("dev_startup_no_fbdepth")
|
||
UIManager:askForRestart()
|
||
end,
|
||
})
|
||
end
|
||
--- @note Currently, only Kobo, rM & PB have a fancy crash display (#5328)
|
||
if Device:isKobo() or Device:isRemarkable() or Device:isPocketBook() then
|
||
table.insert(self.menu_items.developer_options.sub_item_table, {
|
||
text = _("Always abort on crash"),
|
||
checked_func = function()
|
||
return G_reader_settings:isTrue("dev_abort_on_crash")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrFalse("dev_abort_on_crash")
|
||
UIManager:askForRestart()
|
||
end,
|
||
})
|
||
end
|
||
local Blitbuffer = require("ffi/blitbuffer")
|
||
table.insert(self.menu_items.developer_options.sub_item_table, {
|
||
text = _("Disable C blitter"),
|
||
enabled_func = function()
|
||
return Blitbuffer.has_cblitbuffer
|
||
end,
|
||
checked_func = function()
|
||
return G_reader_settings:isTrue("dev_no_c_blitter")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrFalse("dev_no_c_blitter")
|
||
Blitbuffer:enableCBB(G_reader_settings:nilOrFalse("dev_no_c_blitter"))
|
||
end,
|
||
})
|
||
if Device:hasEinkScreen() and Device:canHWDither() then
|
||
table.insert(self.menu_items.developer_options.sub_item_table, {
|
||
text = _("Disable HW dithering"),
|
||
checked_func = function()
|
||
return not Device.screen.hw_dithering
|
||
end,
|
||
callback = function()
|
||
Device.screen:toggleHWDithering()
|
||
G_reader_settings:saveSetting("dev_no_hw_dither", not Device.screen.hw_dithering)
|
||
-- Make sure SW dithering gets disabled when we enable HW dithering
|
||
if Device.screen.hw_dithering and Device.screen.sw_dithering then
|
||
G_reader_settings:makeTrue("dev_no_sw_dither")
|
||
Device.screen:toggleSWDithering(false)
|
||
end
|
||
UIManager:setDirty("all", "full")
|
||
end,
|
||
})
|
||
end
|
||
if Device:hasEinkScreen() then
|
||
table.insert(self.menu_items.developer_options.sub_item_table, {
|
||
text = _("Disable SW dithering"),
|
||
enabled_func = function()
|
||
return Device.screen.fb_bpp == 8
|
||
end,
|
||
checked_func = function()
|
||
return not Device.screen.sw_dithering
|
||
end,
|
||
callback = function()
|
||
Device.screen:toggleSWDithering()
|
||
G_reader_settings:saveSetting("dev_no_sw_dither", not Device.screen.sw_dithering)
|
||
-- Make sure HW dithering gets disabled when we enable SW dithering
|
||
if Device.screen.hw_dithering and Device.screen.sw_dithering then
|
||
G_reader_settings:makeTrue("dev_no_hw_dither")
|
||
Device.screen:toggleHWDithering(false)
|
||
end
|
||
UIManager:setDirty("all", "full")
|
||
end,
|
||
})
|
||
end
|
||
--- @note: Currently, only Kobo implements this quirk
|
||
if Device:hasEinkScreen() and Device:isKobo() then
|
||
table.insert(self.menu_items.developer_options.sub_item_table, {
|
||
-- @translators Highly technical (ioctl is a Linux API call, the uppercase stuff is a constant). What's translatable is essentially only the action ("bypass") and the article.
|
||
text = _("Bypass the WAIT_FOR ioctls"),
|
||
checked_func = function()
|
||
local mxcfb_bypass_wait_for
|
||
if G_reader_settings:has("mxcfb_bypass_wait_for") then
|
||
mxcfb_bypass_wait_for = G_reader_settings:isTrue("mxcfb_bypass_wait_for")
|
||
else
|
||
mxcfb_bypass_wait_for = not Device:hasReliableMxcWaitFor()
|
||
end
|
||
return mxcfb_bypass_wait_for
|
||
end,
|
||
callback = function()
|
||
local mxcfb_bypass_wait_for
|
||
if G_reader_settings:has("mxcfb_bypass_wait_for") then
|
||
mxcfb_bypass_wait_for = G_reader_settings:isTrue("mxcfb_bypass_wait_for")
|
||
else
|
||
mxcfb_bypass_wait_for = not Device:hasReliableMxcWaitFor()
|
||
end
|
||
G_reader_settings:saveSetting("mxcfb_bypass_wait_for", not mxcfb_bypass_wait_for)
|
||
UIManager:askForRestart()
|
||
end,
|
||
})
|
||
end
|
||
--- @note: Intended to debug/investigate B288 quirks on PocketBook devices
|
||
if Device:hasEinkScreen() and Device:isPocketBook() then
|
||
table.insert(self.menu_items.developer_options.sub_item_table, {
|
||
-- @translators B288 is the codename of the CPU/chipset (SoC stands for 'System on Chip').
|
||
text = _("Ignore feature bans on B288 SoCs"),
|
||
enabled_func = function()
|
||
return Device:isB288SoC()
|
||
end,
|
||
checked_func = function()
|
||
return G_reader_settings:isTrue("pb_ignore_b288_quirks")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrFalse("pb_ignore_b288_quirks")
|
||
UIManager:askForRestart()
|
||
end,
|
||
})
|
||
end
|
||
if Device:isAndroid() then
|
||
table.insert(self.menu_items.developer_options.sub_item_table, {
|
||
text = _("Start compatibility test"),
|
||
callback = function()
|
||
Device:test()
|
||
end,
|
||
})
|
||
end
|
||
|
||
table.insert(self.menu_items.developer_options.sub_item_table, {
|
||
text = _("Disable enhanced UI text shaping (xtext)"),
|
||
checked_func = function()
|
||
return G_reader_settings:isFalse("use_xtext")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrTrue("use_xtext")
|
||
UIManager:askForRestart()
|
||
end,
|
||
})
|
||
table.insert(self.menu_items.developer_options.sub_item_table, {
|
||
text = _("UI layout mirroring and text direction"),
|
||
sub_item_table = {
|
||
{
|
||
text = _("Reverse UI layout mirroring"),
|
||
checked_func = function()
|
||
return G_reader_settings:isTrue("dev_reverse_ui_layout_mirroring")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrFalse("dev_reverse_ui_layout_mirroring")
|
||
UIManager:askForRestart()
|
||
end
|
||
},
|
||
{
|
||
text = _("Reverse UI text direction"),
|
||
checked_func = function()
|
||
return G_reader_settings:isTrue("dev_reverse_ui_text_direction")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrFalse("dev_reverse_ui_text_direction")
|
||
UIManager:askForRestart()
|
||
end
|
||
},
|
||
},
|
||
})
|
||
table.insert(self.menu_items.developer_options.sub_item_table, {
|
||
text_func = function()
|
||
if G_reader_settings:nilOrTrue("use_cre_call_cache")
|
||
and G_reader_settings:isTrue("use_cre_call_cache_log_stats") then
|
||
return _("Enable CRE call cache (with stats)")
|
||
end
|
||
return _("Enable CRE call cache")
|
||
end,
|
||
checked_func = function()
|
||
return G_reader_settings:nilOrTrue("use_cre_call_cache")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrTrue("use_cre_call_cache")
|
||
-- No need to show "This will take effect on next CRE book opening."
|
||
-- as this menu is only accessible from file browser
|
||
end,
|
||
hold_callback = function(touchmenu_instance)
|
||
G_reader_settings:flipNilOrFalse("use_cre_call_cache_log_stats")
|
||
touchmenu_instance:updateItems()
|
||
end,
|
||
})
|
||
table.insert(self.menu_items.developer_options.sub_item_table, {
|
||
text = _("Dump the fontlist cache"),
|
||
callback = function()
|
||
local FontList = require("fontlist")
|
||
FontList:dumpFontList()
|
||
end,
|
||
})
|
||
if Device:isKobo() and Device:canToggleChargingLED() then
|
||
table.insert(self.menu_items.developer_options.sub_item_table, {
|
||
-- @translators This is a debug option to help determine cases when standby failed to initiate properly. PM = power management.
|
||
text = _("Turn on the LED on PM entry failure"),
|
||
checked_func = function()
|
||
return G_reader_settings:isTrue("pm_debug_entry_failure")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:toggle("pm_debug_entry_failure")
|
||
end,
|
||
})
|
||
end
|
||
|
||
self.menu_items.cloud_storage = {
|
||
text = _("Cloud storage"),
|
||
callback = function()
|
||
local cloud_storage = require("apps/cloudstorage/cloudstorage"):new{}
|
||
UIManager:show(cloud_storage)
|
||
local filemanagerRefresh = function() self.ui:onRefresh() end
|
||
function cloud_storage:onClose()
|
||
filemanagerRefresh()
|
||
UIManager:close(cloud_storage)
|
||
end
|
||
end,
|
||
}
|
||
|
||
self.menu_items.find_file = {
|
||
-- @translators Search for files by name.
|
||
text = _("File search"),
|
||
help_text = _([[Search a book by filename in the current or home folder and its subfolders.
|
||
|
||
Wildcards for one '?' or more '*' characters can be used.
|
||
A search for '*' will show all files.
|
||
|
||
The sorting order is the same as in filemanager.
|
||
|
||
Tap a book in the search results to open it.]]),
|
||
callback = function()
|
||
self.ui:handleEvent(Event:new("ShowFileSearch"))
|
||
end
|
||
}
|
||
|
||
-- main menu tab
|
||
self.menu_items.open_last_document = {
|
||
text_func = function()
|
||
if not G_reader_settings:isTrue("open_last_menu_show_filename") or G_reader_settings:hasNot("lastfile") then
|
||
return _("Open last document")
|
||
end
|
||
local last_file = G_reader_settings:readSetting("lastfile")
|
||
local path, file_name = util.splitFilePathName(last_file) -- luacheck: no unused
|
||
return T(_("Last: %1"), BD.filename(file_name))
|
||
end,
|
||
enabled_func = function()
|
||
return G_reader_settings:has("lastfile")
|
||
end,
|
||
callback = function()
|
||
self:onOpenLastDoc()
|
||
end,
|
||
hold_callback = function()
|
||
local last_file = G_reader_settings:readSetting("lastfile")
|
||
UIManager:show(ConfirmBox:new{
|
||
text = T(_("Would you like to open the last document: %1?"), BD.filepath(last_file)),
|
||
ok_text = _("OK"),
|
||
ok_callback = function()
|
||
self:onOpenLastDoc()
|
||
end,
|
||
})
|
||
end
|
||
}
|
||
-- 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 filemanager
|
||
for id, common_setting in pairs(dofile("frontend/ui/elements/common_exit_menu_table.lua")) do
|
||
self.menu_items[id] = common_setting
|
||
end
|
||
if not Device:isTouchDevice() then
|
||
-- add a shortcut on non touch-device
|
||
-- because this menu is not accessible otherwise
|
||
self.menu_items.plus_menu = {
|
||
icon = "plus",
|
||
remember = false,
|
||
callback = function()
|
||
self:onCloseFileManagerMenu()
|
||
self.ui:tapPlus()
|
||
end,
|
||
}
|
||
end
|
||
|
||
local order = require("ui/elements/filemanager_menu_order")
|
||
|
||
local MenuSorter = require("ui/menusorter")
|
||
self.tab_item_table = MenuSorter:mergeAndSort("filemanager", self.menu_items, order)
|
||
end
|
||
dbg:guard(FileManagerMenu, '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 FileManagerMenu:getSortingMenuTable()
|
||
local collates = {
|
||
{ _("name"), "strcoll" },
|
||
{ _("name (natural sorting)"), "natural" },
|
||
{ _("last read date"), "access" },
|
||
{ _("date modified"), "date" },
|
||
{ _("size"), "size" },
|
||
{ _("type"), "type" },
|
||
{ _("percent – unopened first"), "percent_unopened_first" },
|
||
{ _("percent – unopened last"), "percent_unopened_last" },
|
||
}
|
||
local sub_item_table = {}
|
||
for i, v in ipairs(collates) do
|
||
table.insert(sub_item_table, {
|
||
text = v[1],
|
||
checked_func = function()
|
||
return v[2] == G_reader_settings:readSetting("collate", "strcoll")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:saveSetting("collate", v[2])
|
||
self.ui.file_chooser:refreshPath()
|
||
end,
|
||
})
|
||
end
|
||
return {
|
||
text_func = function()
|
||
local collate = G_reader_settings:readSetting("collate")
|
||
for i, v in ipairs(collates) do
|
||
if v[2] == collate then
|
||
return T(_("Sort by: %1"), v[1])
|
||
end
|
||
end
|
||
end,
|
||
sub_item_table = sub_item_table,
|
||
}
|
||
end
|
||
|
||
function FileManagerMenu:getStartWithMenuTable()
|
||
local start_withs = {
|
||
{ _("file browser"), "filemanager" },
|
||
{ _("history"), "history" },
|
||
{ _("favorites"), "favorites" },
|
||
{ _("folder shortcuts"), "folder_shortcuts" },
|
||
{ _("last file"), "last" },
|
||
}
|
||
local sub_item_table = {}
|
||
for i, v in ipairs(start_withs) do
|
||
table.insert(sub_item_table, {
|
||
text = v[1],
|
||
checked_func = function()
|
||
return v[2] == G_reader_settings:readSetting("start_with", "filemanager")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:saveSetting("start_with", v[2])
|
||
end,
|
||
})
|
||
end
|
||
return {
|
||
text_func = function()
|
||
local start_with = G_reader_settings:readSetting("start_with") or "filemanager"
|
||
for i, v in ipairs(start_withs) do
|
||
if v[2] == start_with then
|
||
return T(_("Start with: %1"), v[1])
|
||
end
|
||
end
|
||
end,
|
||
sub_item_table = sub_item_table,
|
||
}
|
||
end
|
||
|
||
function FileManagerMenu:moveBookMetadata()
|
||
local DocSettings = require("docsettings")
|
||
local FileChooser = self.ui.file_chooser
|
||
local function scanPath()
|
||
local sys_folders = { -- do not scan sys_folders
|
||
["/dev"] = true,
|
||
["/proc"] = true,
|
||
["/sys"] = true,
|
||
}
|
||
local books_to_move = {}
|
||
local dirs = {FileChooser.path}
|
||
while #dirs ~= 0 do
|
||
local new_dirs = {}
|
||
for _, d in ipairs(dirs) do
|
||
local ok, iter, dir_obj = pcall(lfs.dir, d)
|
||
if ok then
|
||
for f in iter, dir_obj do
|
||
local fullpath = "/" .. f
|
||
if d ~= "/" then
|
||
fullpath = d .. fullpath
|
||
end
|
||
local attributes = lfs.attributes(fullpath) or {}
|
||
if attributes.mode == "directory" and f ~= "." and f ~= ".."
|
||
and FileChooser:show_dir(f) and not sys_folders[fullpath] then
|
||
table.insert(new_dirs, fullpath)
|
||
elseif attributes.mode == "file" and not util.stringStartsWith(f, "._")
|
||
and FileChooser:show_file(f) and DocSettings:hasSidecarFile(fullpath)
|
||
and lfs.attributes(DocSettings:getSidecarFile(fullpath), "mode") ~= "file" then
|
||
table.insert(books_to_move, fullpath)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
dirs = new_dirs
|
||
end
|
||
return books_to_move
|
||
end
|
||
UIManager:show(ConfirmBox:new{
|
||
text = _("Scan books in current folder and subfolders for their metadata location?"),
|
||
ok_text = _("Scan"),
|
||
ok_callback = function()
|
||
local books_to_move = scanPath()
|
||
local books_to_move_nb = #books_to_move
|
||
if books_to_move_nb == 0 then
|
||
local InfoMessage = require("ui/widget/infomessage")
|
||
UIManager:show(InfoMessage:new{
|
||
text = _("No books with metadata not in your preferred location found."),
|
||
})
|
||
else
|
||
UIManager:show(ConfirmBox:new{
|
||
text = T(N_("1 book with metadata not in your preferred location found.",
|
||
"%1 books with metadata not in your preferred location found.",
|
||
books_to_move_nb), books_to_move_nb) .. "\n" ..
|
||
_("Move book metadata to your preferred location?"),
|
||
ok_text = _("Move"),
|
||
ok_callback = function()
|
||
UIManager:close(self.menu_container)
|
||
for _, book in ipairs(books_to_move) do
|
||
DocSettings:updateLocation(book, book)
|
||
end
|
||
FileChooser:refreshPath()
|
||
end,
|
||
})
|
||
end
|
||
end,
|
||
})
|
||
end
|
||
|
||
function FileManagerMenu:exitOrRestart(callback, force)
|
||
UIManager:close(self.menu_container)
|
||
|
||
-- 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.ui:onClose()
|
||
if callback then
|
||
callback()
|
||
end
|
||
end
|
||
|
||
function FileManagerMenu:onShowMenu(tab_index)
|
||
if self.tab_item_table == nil then
|
||
self:setUpdateItemTable()
|
||
end
|
||
|
||
if not tab_index then
|
||
tab_index = G_reader_settings:readSetting("filemanagermenu_tab_index") or 1
|
||
end
|
||
|
||
local menu_container = CenterContainer:new{
|
||
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,
|
||
tab_item_table = self.tab_item_table,
|
||
show_parent = menu_container,
|
||
}
|
||
else
|
||
local Menu = require("ui/widget/menu")
|
||
main_menu = Menu:new{
|
||
title = _("File manager menu"),
|
||
item_table = Menu.itemTableFromTouchMenu(self.tab_item_table),
|
||
width = Screen:getWidth() - (Size.margin.fullscreen_popout * 2),
|
||
show_parent = menu_container,
|
||
}
|
||
end
|
||
|
||
main_menu.close_callback = function()
|
||
self:onCloseFileManagerMenu()
|
||
end
|
||
|
||
menu_container[1] = main_menu
|
||
-- maintain a reference to menu_container
|
||
self.menu_container = menu_container
|
||
UIManager:show(menu_container)
|
||
return true
|
||
end
|
||
|
||
function FileManagerMenu:onCloseFileManagerMenu()
|
||
if not self.menu_container then return end
|
||
local last_tab_index = self.menu_container[1].last_index
|
||
G_reader_settings:saveSetting("filemanagermenu_tab_index", last_tab_index)
|
||
UIManager:close(self.menu_container)
|
||
return true
|
||
end
|
||
|
||
function FileManagerMenu:_getTabIndexFromLocation(ges)
|
||
if self.tab_item_table == nil then
|
||
self:setUpdateItemTable()
|
||
end
|
||
local last_tab_index = G_reader_settings:readSetting("filemanagermenu_tab_index") or 1
|
||
if not ges then
|
||
return 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 last_tab_index
|
||
end
|
||
end
|
||
|
||
function FileManagerMenu:onTapShowMenu(ges)
|
||
if self.activation_menu ~= "swipe" then
|
||
self:onShowMenu(self:_getTabIndexFromLocation(ges))
|
||
return true
|
||
end
|
||
end
|
||
|
||
function FileManagerMenu:onSwipeShowMenu(ges)
|
||
if self.activation_menu ~= "tap" and ges.direction == "south" then
|
||
self:onShowMenu(self:_getTabIndexFromLocation(ges))
|
||
return true
|
||
end
|
||
end
|
||
|
||
function FileManagerMenu:onSetDimensions(dimen)
|
||
self:onCloseFileManagerMenu()
|
||
-- update listening according to new screen dimen
|
||
if Device:isTouchDevice() then
|
||
self:initGesListener()
|
||
end
|
||
end
|
||
|
||
function FileManagerMenu:onMenuSearch()
|
||
self:onShowMenu()
|
||
self.menu_container[1]:onShowMenuSearch()
|
||
end
|
||
|
||
function FileManagerMenu:registerToMainMenu(widget)
|
||
table.insert(self.registered_widgets, widget)
|
||
end
|
||
|
||
return FileManagerMenu
|