mirror of
https://github.com/koreader/koreader
synced 2024-11-11 19:11:14 +00:00
1125 lines
45 KiB
Lua
1125 lines
45 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()
|
||
local FileChooser = self.ui.file_chooser
|
||
|
||
-- setting tab
|
||
self.menu_items.filebrowser_settings = {
|
||
text = _("Settings"),
|
||
sub_item_table = {
|
||
{
|
||
text = _("Show finished books"),
|
||
checked_func = function() return FileChooser.show_finished end,
|
||
callback = function() FileChooser:toggleShowFilesMode("show_finished") end,
|
||
},
|
||
{
|
||
text = _("Show hidden files"),
|
||
checked_func = function() return FileChooser.show_hidden end,
|
||
callback = function() FileChooser:toggleShowFilesMode("show_hidden") end,
|
||
},
|
||
{
|
||
text = _("Show unsupported files"),
|
||
checked_func = function() return FileChooser.show_unsupported end,
|
||
callback = function() FileChooser:toggleShowFilesMode("show_unsupported") 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,
|
||
},
|
||
{
|
||
text = _("Freeze last read date of finished books"),
|
||
checked_func = function()
|
||
return G_reader_settings:isTrue("history_freeze_finished_books")
|
||
end,
|
||
callback = function()
|
||
G_reader_settings:flipNilOrFalse("history_freeze_finished_books")
|
||
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")
|
||
FileChooser: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")
|
||
FileChooser: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 physical keys, and applies *everywhere*
|
||
if Device:hasKeys() then
|
||
self.menu_items.physical_buttons_setup = require("ui/elements/physical_buttons")
|
||
end
|
||
|
||
-- 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
|