mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
* Restart KOReader after a crash, after showing a fancy crash recap screen (our very own Gray Screen of Death!). Kobo only, because it's possibly the platform where getting booted out of KOReader is the most annoying. Keeps track of crashes, to be able to give up after a while, in order to avoid boot loops in the unlikely event of a crash loop on startup. Adds a dev option to *always* exit KOReader after the crash screen.
613 lines
22 KiB
Lua
613 lines
22 KiB
Lua
local CenterContainer = require("ui/widget/container/centercontainer")
|
|
local CloudStorage = require("apps/cloudstorage/cloudstorage")
|
|
local ConfirmBox = require("ui/widget/confirmbox")
|
|
local Device = require("device")
|
|
local Event = require("ui/event")
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
local PluginLoader = require("pluginloader")
|
|
local Search = require("apps/filemanager/filemanagersearch")
|
|
local SetDefaults = require("apps/filemanager/filemanagersetdefaults")
|
|
local UIManager = require("ui/uimanager")
|
|
local Screen = Device.screen
|
|
local dbg = require("dbg")
|
|
local logger = require("logger")
|
|
local util = require("util")
|
|
local _ = require("gettext")
|
|
local T = require("ffi/util").template
|
|
|
|
local FileManagerMenu = InputContainer:extend{
|
|
tab_item_table = nil,
|
|
menu_items = {},
|
|
registered_widgets = nil,
|
|
}
|
|
|
|
function FileManagerMenu:init()
|
|
self.menu_items = {
|
|
["KOMenu:menu_buttons"] = {
|
|
-- top menu
|
|
},
|
|
-- items in top menu
|
|
filemanager_settings = {
|
|
icon = "resources/icons/appbar.cabinet.files.png",
|
|
},
|
|
setting = {
|
|
icon = "resources/icons/appbar.settings.png",
|
|
},
|
|
tools = {
|
|
icon = "resources/icons/appbar.tools.png",
|
|
},
|
|
search = {
|
|
icon = "resources/icons/appbar.magnify.browse.png",
|
|
},
|
|
main = {
|
|
icon = "resources/icons/menu-icon.png",
|
|
},
|
|
}
|
|
|
|
self.registered_widgets = {}
|
|
|
|
if Device:hasKeys() then
|
|
self.key_events = {
|
|
ShowMenu = { { "Menu" }, doc = "show menu" },
|
|
}
|
|
end
|
|
self.activation_menu = G_reader_settings:readSetting("activate_menu")
|
|
if self.activation_menu == nil then
|
|
self.activation_menu = "swipe_tap"
|
|
end
|
|
end
|
|
|
|
function FileManagerMenu:initGesListener()
|
|
if not Device:isTouchDevice() then return end
|
|
|
|
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_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,
|
|
},
|
|
})
|
|
end
|
|
|
|
function FileManagerMenu:openLastDoc()
|
|
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
|
|
local ReaderUI = require("apps/reader/readerui")
|
|
ReaderUI:showReader(last_file)
|
|
|
|
-- only close menu if we were called from the menu
|
|
if self.menu_container then
|
|
self:onCloseFileManagerMenu()
|
|
end
|
|
|
|
local FileManager = require("apps/filemanager/filemanager")
|
|
FileManager.instance:onClose()
|
|
end
|
|
|
|
function FileManagerMenu:setUpdateItemTable()
|
|
for _, widget in pairs(self.registered_widgets) do
|
|
local ok, err = pcall(widget.addToMainMenu, widget, self.menu_items)
|
|
if not ok then
|
|
logger.err("failed to register widget", widget.name, err)
|
|
end
|
|
end
|
|
|
|
-- setting tab
|
|
self.menu_items.show_hidden_files = {
|
|
text = _("Show hidden files"),
|
|
checked_func = function() return self.ui.file_chooser.show_hidden end,
|
|
callback = function() self.ui:toggleHiddenFiles() end
|
|
}
|
|
self.menu_items.show_unsupported_files = {
|
|
text = _("Show unsupported files"),
|
|
checked_func = function() return self.ui.file_chooser.show_unsupported end,
|
|
callback = function() self.ui:toggleUnsupportedFiles() end
|
|
}
|
|
self.menu_items.items = {
|
|
text = _("Items"),
|
|
sub_item_table = {
|
|
{
|
|
text = _("Items per page"),
|
|
help_text = _([[This sets the number of items per page in:
|
|
- File browser and history in 'classic' display mode
|
|
- File and directory selection
|
|
- Table of contents
|
|
- Bookmarks list]]),
|
|
keep_menu_open = true,
|
|
callback = function()
|
|
local SpinWidget = require("ui/widget/spinwidget")
|
|
local curr_items = G_reader_settings:readSetting("items_per_page") or 14
|
|
local items = SpinWidget:new{
|
|
width = Screen:getWidth() * 0.6,
|
|
value = curr_items,
|
|
value_min = 6,
|
|
value_max = 24,
|
|
ok_text = _("Set items"),
|
|
title_text = _("Items per page"),
|
|
callback = function(spin)
|
|
G_reader_settings:saveSetting("items_per_page", spin.value)
|
|
self.ui:onRefresh()
|
|
end
|
|
}
|
|
UIManager:show(items)
|
|
end
|
|
},
|
|
{
|
|
text = _("Font size"),
|
|
keep_menu_open = true,
|
|
callback = function()
|
|
local SpinWidget = require("ui/widget/spinwidget")
|
|
local curr_items = G_reader_settings:readSetting("items_per_page") or 14
|
|
local default_font_size = math.floor(24 - ((curr_items - 6)/ 18) * 10 )
|
|
local curr_font_size = G_reader_settings:readSetting("items_font_size") or default_font_size
|
|
local items_font = SpinWidget:new{
|
|
width = Screen:getWidth() * 0.6,
|
|
value = curr_font_size,
|
|
value_min = 10,
|
|
value_max = 72,
|
|
default_value = default_font_size,
|
|
ok_text = _("Set size"),
|
|
title_text = _("Maximum font size for item"),
|
|
callback = function(spin)
|
|
G_reader_settings:saveSetting("items_font_size", spin.value)
|
|
self.ui:onRefresh()
|
|
end
|
|
}
|
|
UIManager:show(items_font)
|
|
end
|
|
}
|
|
}
|
|
}
|
|
self.menu_items.sort_by = self.ui:getSortingMenuTable()
|
|
self.menu_items.reverse_sorting = {
|
|
text = _("Reverse sorting"),
|
|
checked_func = function() return self.ui.file_chooser.reverse_collate end,
|
|
callback = function() self.ui:toggleReverseCollate() end
|
|
}
|
|
self.menu_items.start_with = self.ui: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
|
|
|
|
-- tools tab
|
|
self.menu_items.advanced_settings = {
|
|
text = _("Advanced settings"),
|
|
callback = function()
|
|
SetDefaults:ConfirmEdit()
|
|
end,
|
|
hold_callback = function()
|
|
SetDefaults:ConfirmSave()
|
|
end,
|
|
}
|
|
self.menu_items.plugin_management = {
|
|
text = _("Plugin management"),
|
|
sub_item_table = PluginLoader:genPluginManagerSubItem()
|
|
}
|
|
|
|
self.menu_items.opds_catalog = {
|
|
text = _("OPDS catalog"),
|
|
callback = function()
|
|
local OPDSCatalog = require("apps/opdscatalog/opdscatalog")
|
|
local filemanagerRefresh = function() self.ui:onRefresh() end
|
|
function OPDSCatalog:onClose()
|
|
filemanagerRefresh()
|
|
UIManager:close(self)
|
|
end
|
|
OPDSCatalog:showCatalog()
|
|
end,
|
|
}
|
|
self.menu_items.developer_options = {
|
|
text = _("Developer options"),
|
|
sub_item_table = {
|
|
{
|
|
text = _("Clear readers' caches"),
|
|
callback = function()
|
|
UIManager:show(ConfirmBox:new{
|
|
text = _("Clear cache/ and cr3cache/ ?"),
|
|
ok_callback = function()
|
|
local purgeDir = require("ffi/util").purgeDir
|
|
local DataStorage = require("datastorage")
|
|
local cachedir = DataStorage:getDataDir() .. "/cache"
|
|
if lfs.attributes(cachedir, "mode") == "directory" then
|
|
purgeDir(cachedir)
|
|
end
|
|
lfs.mkdir(cachedir)
|
|
-- Also remove from Cache objet references to
|
|
-- the cache files we just deleted
|
|
local Cache = require("cache")
|
|
Cache.cached = {}
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Caches cleared. Please exit and 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:flipFalse("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() 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")
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("This will take effect on next restart."),
|
|
})
|
|
end,
|
|
})
|
|
end
|
|
--- @note Currently, only Kobo has a fancy crash display (#5328)
|
|
if Device:isKobo() 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")
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("This will take effect on next restart."),
|
|
})
|
|
end,
|
|
})
|
|
end
|
|
if not Device.should_restrict_JIT then
|
|
table.insert(self.menu_items.developer_options.sub_item_table, {
|
|
text = _("Disable C blitter"),
|
|
enabled_func = function()
|
|
local lfs = require("libs/libkoreader-lfs")
|
|
return lfs.attributes("libs/libblitbuffer.so", "mode") == "file"
|
|
end,
|
|
checked_func = function()
|
|
return G_reader_settings:isTrue("dev_no_c_blitter")
|
|
end,
|
|
callback = function()
|
|
G_reader_settings:flipNilOrFalse("dev_no_c_blitter")
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("This will take effect on next restart."),
|
|
})
|
|
end,
|
|
})
|
|
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()
|
|
G_reader_settings:flipNilOrFalse("dev_no_hw_dither")
|
|
Device.screen:toggleHWDithering()
|
|
-- Make sure SW dithering gets disabled when we enable HW dithering
|
|
if Device.screen.hw_dithering and Device.screen.sw_dithering then
|
|
Device.screen:toggleSWDithering()
|
|
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()
|
|
G_reader_settings:flipNilOrFalse("dev_no_sw_dither")
|
|
Device.screen:toggleSWDithering()
|
|
-- Make sure HW dithering gets disabled when we enable SW dithering
|
|
if Device.screen.hw_dithering and Device.screen.sw_dithering then
|
|
Device.screen:toggleHWDithering()
|
|
end
|
|
UIManager:setDirty("all", "full")
|
|
end,
|
|
})
|
|
end
|
|
self.menu_items.cloud_storage = {
|
|
text = _("Cloud storage"),
|
|
callback = function()
|
|
local cloud_storage = CloudStorage:new{}
|
|
UIManager:show(cloud_storage)
|
|
local filemanagerRefresh = function() self.ui:onRefresh() end
|
|
function cloud_storage:onClose()
|
|
filemanagerRefresh()
|
|
UIManager:close(cloud_storage)
|
|
end
|
|
end,
|
|
}
|
|
|
|
-- search tab
|
|
self.menu_items.find_book_in_calibre_catalog = {
|
|
text = _("Find a book in calibre catalog"),
|
|
callback = function()
|
|
Search:getCalibre()
|
|
Search:ShowSearch()
|
|
end
|
|
}
|
|
self.menu_items.find_file = {
|
|
-- @translators Search for files by name.
|
|
text = _("Find a file"),
|
|
callback = function()
|
|
self.ui:handleEvent(Event:new("ShowFileSearch", self.ui.file_chooser.path))
|
|
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 not G_reader_settings:readSetting("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"), file_name)
|
|
end,
|
|
enabled_func = function()
|
|
return G_reader_settings:readSetting("lastfile") ~= nil
|
|
end,
|
|
callback = function()
|
|
self:openLastDoc()
|
|
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?"), last_file),
|
|
ok_text = _("OK"),
|
|
ok_callback = function()
|
|
self:openLastDoc()
|
|
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
|
|
self.menu_items.exit_menu = {
|
|
text = _("Exit"),
|
|
hold_callback = function()
|
|
self:exitOrRestart()
|
|
end,
|
|
}
|
|
self.menu_items.exit = {
|
|
text = _("Exit"),
|
|
callback = function()
|
|
self:exitOrRestart()
|
|
end,
|
|
}
|
|
self.menu_items.restart_koreader = {
|
|
text = _("Restart KOReader"),
|
|
callback = function()
|
|
self:exitOrRestart(function() UIManager:restartKOReader() end)
|
|
end,
|
|
}
|
|
if not Device:canRestart() then
|
|
self.menu_items.exit_menu = self.menu_items.exit
|
|
self.menu_items.exit = nil
|
|
self.menu_items.restart_koreader = nil
|
|
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 = "resources/icons/appbar.plus.png",
|
|
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:exitOrRestart(callback)
|
|
if SetDefaults.settings_changed then
|
|
UIManager:show(ConfirmBox:new{
|
|
text = _("You have unsaved default settings. Save them now?\nTap \"Cancel\" to return to KOReader."),
|
|
ok_text = _("Save"),
|
|
ok_callback = function()
|
|
SetDefaults.settings_changed = false
|
|
SetDefaults:saveSettings()
|
|
self:exitOrRestart(callback)
|
|
end,
|
|
cancel_text = _("Don't save"),
|
|
cancel_callback = function()
|
|
SetDefaults.settings_changed = false
|
|
self:exitOrRestart(callback)
|
|
end,
|
|
other_buttons = {{
|
|
text = _("Cancel"),
|
|
}}
|
|
})
|
|
else
|
|
UIManager:close(self.menu_container)
|
|
self.ui:onClose()
|
|
if callback then
|
|
callback()
|
|
end
|
|
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()-10,
|
|
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()
|
|
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 > 2 * Screen:getWidth() / 3 then
|
|
return #self.tab_item_table
|
|
-- if the start position is far left
|
|
elseif ges.pos.x < Screen:getWidth() / 3 then
|
|
return 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)
|
|
-- update listening according to new screen dimen
|
|
if Device:isTouchDevice() then
|
|
self:initGesListener()
|
|
end
|
|
end
|
|
|
|
function FileManagerMenu:registerToMainMenu(widget)
|
|
table.insert(self.registered_widgets, widget)
|
|
end
|
|
|
|
return FileManagerMenu
|