You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koreader/frontend/apps/filemanager/filemanagermenu.lua

1110 lines
44 KiB
Lua

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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 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