Files
koreader/frontend/apps/filemanager/filemanagermenu.lua
NiLuJe a3acc667d6 [Kobo] Restart KOReader after a crash (#5328)
* 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.
2019-09-07 03:19:18 +02:00

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