local InputContainer = require("ui/widget/container/inputcontainer") local UIManager = require("ui/uimanager") local logger = require("logger") local _ = require("gettext") local BookInfoManager = require("bookinfomanager") --[[ This plugin provides additional display modes to file browsers (File Manager and History). It does that by dynamically replacing some methods code to their classes or instances. --]] -- We need to save the original methods early here as locals. -- For some reason, saving them as attributes in init() does not allow -- us to get back to classic mode local FileChooser = require("ui/widget/filechooser") local _FileChooser__recalculateDimen_orig = FileChooser._recalculateDimen local _FileChooser_updateItems_orig = FileChooser.updateItems local _FileChooser_onCloseWidget_orig = FileChooser.onCloseWidget local _FileChooser_onSwipe_orig = FileChooser.onSwipe local FileManagerHistory = require("apps/filemanager/filemanagerhistory") local _FileManagerHistory_updateItemTable_orig = FileManagerHistory.updateItemTable local FileManager = require("apps/filemanager/filemanager") local _FileManager_tapPlus_orig = FileManager.tapPlus -- Available display modes local DISPLAY_MODES = { -- nil or "" -- classic : filename only mosaic_image = true, -- 3x3 grid covers with images mosaic_text = true, -- 3x3 grid covers text only list_image_meta = true, -- image with metadata (title/authors) list_only_meta = true, -- metadata with no image list_image_filename = true, -- image with filename (no metadata) } -- Store some states as locals, to be permanent across instantiations local init_done = false local filemanager_display_mode = false -- not initialized yet local history_display_mode = false -- not initialized yet local CoverBrowser = InputContainer:new{ name = "coverbrowser", } function CoverBrowser:init() self.full_featured = true -- (Could be set to false for some platforms to provide a fallback -- option with only a menu for managing a few core settings) self.ui.menu:registerToMainMenu(self) if not self.full_featured then -- nothing else than menu registration return end if init_done then -- things already patched according to current modes return end -- Set up default display modes on first launch if not G_reader_settings:isTrue("coverbrowser_initial_default_setup_done") then -- Only if no display mode has been set yet if not BookInfoManager:getSetting("filemanager_display_mode") and not BookInfoManager:getSetting("history_display_mode") then logger.info("CoverBrowser: setting default display modes") BookInfoManager:saveSetting("filemanager_display_mode", "list_image_meta") BookInfoManager:saveSetting("history_display_mode", "mosaic_image") end G_reader_settings:saveSetting("coverbrowser_initial_default_setup_done", true) end self:setupFileManagerDisplayMode(BookInfoManager:getSetting("filemanager_display_mode")) self:setupHistoryDisplayMode(BookInfoManager:getSetting("history_display_mode")) init_done = true BookInfoManager:closeDbConnection() -- will be re-opened if needed end function CoverBrowser:addToMainMenu(menu_items) -- We add it only to FileManager menu if self.ui.view then -- Reader return end -- Items available even if not full_featured -- (settings used by core, that fit in this menu) local generic_items = { { text_func = function() local current_state = _("Show new files in bold") if G_reader_settings:readSetting("show_file_in_bold") == "opened" then current_state = _("Show opened files in bold") elseif G_reader_settings:readSetting("show_file_in_bold") == false then current_state = _("Show files in bold") -- with checkmark unchecked end if self.full_featured then -- Inform that this settings applies only to classic file chooser current_state = _("(Classic mode) ") .. current_state end return current_state end, checked_func = function() return G_reader_settings:readSetting("show_file_in_bold") ~= false end, sub_item_table = { { text = _("Don't show files in bold"), checked_func = function() return G_reader_settings:readSetting("show_file_in_bold") == false end, callback = function() G_reader_settings:saveSetting("show_file_in_bold", false) self:refreshFileManagerInstance() end, }, { text = _("Show opened files in bold"), checked_func = function() return G_reader_settings:readSetting("show_file_in_bold") == "opened" end, callback = function() G_reader_settings:saveSetting("show_file_in_bold", "opened") self:refreshFileManagerInstance() end, }, { text = _("Show new (not yet opened) files in bold"), checked_func = function() return G_reader_settings:readSetting("show_file_in_bold") ~= false and G_reader_settings:readSetting("show_file_in_bold") ~= "opened" end, callback = function() G_reader_settings:delSetting("show_file_in_bold") self:refreshFileManagerInstance() end, }, }, separator = true, }, { text = _("Shorten home directory to ~"), checked_func = function() return G_reader_settings:readSetting("home_dir_display_name") end, callback = function() if G_reader_settings:readSetting("home_dir_display_name") then G_reader_settings:delSetting("home_dir_display_name") if FileManager.instance then FileManager.instance:reinit() end else G_reader_settings:saveSetting("home_dir_display_name", "~") if FileManager.instance then FileManager.instance:reinit() end end end, }, { text = _("Auto-remove deleted or purged items from history"), checked_func = function() return G_reader_settings:readSetting("autoremove_deleted_items_from_history") end, callback = function() G_reader_settings:flipNilOrFalse("autoremove_deleted_items_from_history") end, }, { text = _("Show filename in Open last/previous menu items"), checked_func = function() return G_reader_settings:readSetting("open_last_menu_show_filename") end, callback = function() G_reader_settings:flipNilOrFalse("open_last_menu_show_filename") end, }, } if not self.full_featured then -- Make the generic items directly as 1st level items, -- and use alternate name for main menu, not mentionning -- "display mode" that are not available menu_items.filemanager_display_mode = { text = _("File browser settings"), sub_item_table = generic_items } return end menu_items.filemanager_display_mode = { text = _("Display mode"), sub_item_table = { -- selecting these does not close menu, which may be nice -- so one can see how they look below the menu { text = _("Classic (filename only)"), checked_func = function() return not filemanager_display_mode end, callback = function() self:setupFileManagerDisplayMode("") end, }, { text = _("Mosaic with cover images"), checked_func = function() return filemanager_display_mode == "mosaic_image" end, callback = function() self:setupFileManagerDisplayMode("mosaic_image") end, }, { text = _("Mosaic with text covers"), checked_func = function() return filemanager_display_mode == "mosaic_text" end, callback = function() self:setupFileManagerDisplayMode("mosaic_text") end, }, { text = _("Detailed list with cover images and metadata"), checked_func = function() return filemanager_display_mode == "list_image_meta" end, callback = function() self:setupFileManagerDisplayMode("list_image_meta") end, }, { text = _("Detailed list with metadata, no images"), checked_func = function() return filemanager_display_mode == "list_only_meta" end, callback = function() self:setupFileManagerDisplayMode("list_only_meta") end, }, { text = _("Detailed list with cover images and filenames"), checked_func = function() return filemanager_display_mode == "list_image_filename" end, callback = function() self:setupFileManagerDisplayMode("list_image_filename") end, separator = true, }, -- Plug the same choices for History here as a submenu -- (Any other suitable place for that ?) { text = _("History display mode"), sub_item_table = { { text = _("Classic (filename only)"), checked_func = function() return not history_display_mode end, callback = function() self:setupHistoryDisplayMode("") end, }, { text = _("Mosaic with cover images"), checked_func = function() return history_display_mode == "mosaic_image" end, callback = function() self:setupHistoryDisplayMode("mosaic_image") end, }, { text = _("Mosaic with text covers"), checked_func = function() return history_display_mode == "mosaic_text" end, callback = function() self:setupHistoryDisplayMode("mosaic_text") end, }, { text = _("Detailed list with cover images and metadata"), checked_func = function() return history_display_mode == "list_image_meta" end, callback = function() self:setupHistoryDisplayMode("list_image_meta") end, }, { text = _("Detailed list with metadata, no images"), checked_func = function() return history_display_mode == "list_only_meta" end, callback = function() self:setupHistoryDisplayMode("list_only_meta") end, }, { text = _("Detailed list with cover images and filenames"), checked_func = function() return history_display_mode == "list_image_filename" end, callback = function() self:setupHistoryDisplayMode("list_image_filename") end, separator = true, }, }, separator = true, }, -- Misc settings { text = _("Other settings"), sub_item_table = { { text = _("Show hint for books with description"), checked_func = function() return not BookInfoManager:getSetting("no_hint_description") end, callback = function() if BookInfoManager:getSetting("no_hint_description") then BookInfoManager:saveSetting("no_hint_description", false) else BookInfoManager:saveSetting("no_hint_description", true) end self:refreshFileManagerInstance() end, }, { text = _("Show hint for opened books in history"), checked_func = function() return BookInfoManager:getSetting("history_hint_opened") end, callback = function() if BookInfoManager:getSetting("history_hint_opened") then BookInfoManager:saveSetting("history_hint_opened", false) else BookInfoManager:saveSetting("history_hint_opened", true) end self:refreshFileManagerInstance() end, }, { text = _("Append series metadata to authors"), checked_func = function() return BookInfoManager:getSetting("append_series_to_authors") end, callback = function() if BookInfoManager:getSetting("append_series_to_authors") then BookInfoManager:saveSetting("append_series_to_authors", false) else BookInfoManager:saveSetting("append_series_to_authors", true) end self:refreshFileManagerInstance() end, }, { text = _("Append series metadata to title"), checked_func = function() return BookInfoManager:getSetting("append_series_to_title") end, callback = function() if BookInfoManager:getSetting("append_series_to_title") then BookInfoManager:saveSetting("append_series_to_title", false) else BookInfoManager:saveSetting("append_series_to_title", true) end self:refreshFileManagerInstance() end, }, -- generic_items will be inserted here }, }, { text = _("Book info cache management"), sub_item_table = { { text_func = function() -- add current db size to menu text local sstr = BookInfoManager:getDbSize() return _("Current cache size: ") .. sstr end, -- no callback, only for information }, { text = _("Prune cache of removed books"), callback = function() local ConfirmBox = require("ui/widget/confirmbox") UIManager:close(self.file_dialog) UIManager:show(ConfirmBox:new{ -- Checking file existences is quite fast, but deleting entries is slow. text = _("Are you sure that you want to prune cache of removed books?\n(This may take a while.)"), ok_text = _("Prune cache"), ok_callback = function() local InfoMessage = require("ui/widget/infomessage") local msg = InfoMessage:new{ text = _("Pruning cache of removed books…") } UIManager:show(msg) UIManager:nextTick(function() local summary = BookInfoManager:removeNonExistantEntries() UIManager:close(msg) UIManager:show( InfoMessage:new{ text = summary } ) end) end }) end, }, { text = _("Compact cache database"), callback = function() local ConfirmBox = require("ui/widget/confirmbox") UIManager:close(self.file_dialog) UIManager:show(ConfirmBox:new{ text = _("Are you sure that you want to compact cache database?\n(This may take a while.)"), ok_text = _("Compact database"), ok_callback = function() local InfoMessage = require("ui/widget/infomessage") local msg = InfoMessage:new{ text = _("Compacting cache database…") } UIManager:show(msg) UIManager:nextTick(function() local summary = BookInfoManager:compactDb() UIManager:close(msg) UIManager:show( InfoMessage:new{ text = summary } ) end) end }) end, }, { text = _("Delete cache database"), callback = function() local ConfirmBox = require("ui/widget/confirmbox") UIManager:close(self.file_dialog) UIManager:show(ConfirmBox:new{ text = _("Are you sure that you want to delete cover and metadata cache?\n(This will also reset your display mode settings.)"), ok_text = _("Purge"), ok_callback = function() BookInfoManager:deleteDb() end }) end, }, }, }, }, } -- Finally, insert the generic items at end of "Other settings" submenu local sub_item_table = menu_items.filemanager_display_mode.sub_item_table local generic_items_target = sub_item_table[#sub_item_table-1].sub_item_table -- second to last for _, item in pairs(generic_items) do table.insert(generic_items_target, item) end end function CoverBrowser:refreshFileManagerInstance(cleanup, post_init) local fm = FileManager.instance if fm then local fc = fm.file_chooser if cleanup then -- clean instance properties we may have set if fc.onFileHold_orig then -- remove our onFileHold that extended file_dialog with new buttons fc.onFileHold = fc.onFileHold_orig fc.onFileHold_orig = nil fc.onFileHold_ours = nil end end if filemanager_display_mode then if post_init then -- FileBrowser was initialized in classic mode, but we changed -- display mode: items per page may have changed, and we want -- to re-position on the focused_file fc:_recalculateDimen() fc:changeToPath(fc.path, fc.prev_focused_path) else fc:updateItems() end else -- classic file_chooser needs this for a full redraw fc:refreshPath() end end end function CoverBrowser:setupFileManagerDisplayMode(display_mode) if not DISPLAY_MODES[display_mode] then display_mode = nil -- unknow mode, fallback to classic end if init_done and display_mode == filemanager_display_mode then -- no change return end if init_done then -- save new mode in db BookInfoManager:saveSetting("filemanager_display_mode", display_mode) end -- remember current mode in module variable filemanager_display_mode = display_mode logger.dbg("CoverBrowser: setting FileManager display mode to:", display_mode or "classic") if not init_done and not display_mode then return -- starting in classic mode, nothing to patch end if not display_mode then -- classic mode -- Put back original methods FileChooser.updateItems = _FileChooser_updateItems_orig FileChooser.onCloseWidget = _FileChooser_onCloseWidget_orig FileChooser.onSwipe = _FileChooser_onSwipe_orig FileChooser._recalculateDimen = _FileChooser__recalculateDimen_orig FileManager.tapPlus = _FileManager_tapPlus_orig -- Also clean-up what we added, even if it does not bother original code FileChooser._updateItemsBuildUI = nil FileChooser._do_cover_images = nil FileChooser._do_filename_only = nil FileChooser._do_hint_opened = nil self:refreshFileManagerInstance(true) return end -- In both mosaic and list modes, replace original methods with those from -- our generic CoverMenu local CoverMenu = require("covermenu") FileChooser.updateItems = CoverMenu.updateItems FileChooser.onCloseWidget = CoverMenu.onCloseWidget FileChooser.onSwipe = CoverMenu.onSwipe if display_mode == "mosaic_image" or display_mode == "mosaic_text" then -- mosaic mode -- Replace some other original methods with those from our MosaicMenu local MosaicMenu = require("mosaicmenu") FileChooser._recalculateDimen = MosaicMenu._recalculateDimen FileChooser._updateItemsBuildUI = MosaicMenu._updateItemsBuildUI -- Set MosaicMenu behaviour: FileChooser._do_cover_images = display_mode ~= "mosaic_text" FileChooser._do_hint_opened = true -- dogear at bottom -- One could override default 3x3 grid here (put that as settings ?) -- FileChooser.nb_cols_portrait = 4 -- FileChooser.nb_rows_portrait = 4 -- FileChooser.nb_cols_landscape = 6 -- FileChooser.nb_rows_landscape = 3 elseif display_mode == "list_image_meta" or display_mode == "list_only_meta" or display_mode == "list_image_filename" then -- list modes -- Replace some other original methods with those from our ListMenu local ListMenu = require("listmenu") FileChooser._recalculateDimen = ListMenu._recalculateDimen FileChooser._updateItemsBuildUI = ListMenu._updateItemsBuildUI -- Set ListMenu behaviour: FileChooser._do_cover_images = display_mode ~= "list_only_meta" FileChooser._do_filename_only = display_mode == "list_image_filename" FileChooser._do_hint_opened = true -- dogear at bottom end -- Replace this FileManager method with the one from CoverMenu -- (but first, make the original method saved here as local available -- to CoverMenu) CoverMenu._FileManager_tapPlus_orig = _FileManager_tapPlus_orig FileManager.tapPlus = CoverMenu.tapPlus if init_done then self:refreshFileManagerInstance() else -- If KOReader has started directly to FileManager, the FileManager -- instance is being init()'ed and there is no FileManager.instance yet, -- but there'll be one at next tick. UIManager:nextTick(function() self:refreshFileManagerInstance(false, true) end) end end local function _FileManagerHistory_updateItemTable(self) -- 'self' here is the single FileManagerHistory instance -- FileManagerHistory has just created a new instance of Menu as 'hist_menu' -- at each display of History. Soon after instantiation, this method -- is called. The first time it is called, we replace some methods. local display_mode = self.display_mode local hist_menu = self.hist_menu if not hist_menu._coverbrowser_overridden then hist_menu._coverbrowser_overridden = true -- In both mosaic and list modes, replace original methods with those from -- our generic CoverMenu local CoverMenu = require("covermenu") hist_menu.updateItems = CoverMenu.updateItems hist_menu.onCloseWidget = CoverMenu.onCloseWidget hist_menu.onSwipe = CoverMenu.onSwipe -- Also replace original onMenuHold (it will use original method, so remember it) hist_menu.onMenuHold_orig = hist_menu.onMenuHold hist_menu.onMenuHold = CoverMenu.onHistoryMenuHold if display_mode == "mosaic_image" or display_mode == "mosaic_text" then -- mosaic mode -- Replace some other original methods with those from our MosaicMenu local MosaicMenu = require("mosaicmenu") hist_menu._recalculateDimen = MosaicMenu._recalculateDimen hist_menu._updateItemsBuildUI = MosaicMenu._updateItemsBuildUI -- Set MosaicMenu behaviour: hist_menu._do_cover_images = display_mode ~= "mosaic_text" -- no need for do_hint_opened with History elseif display_mode == "list_image_meta" or display_mode == "list_only_meta" or display_mode == "list_image_filename" then -- list modes -- Replace some other original methods with those from our ListMenu local ListMenu = require("listmenu") hist_menu._recalculateDimen = ListMenu._recalculateDimen hist_menu._updateItemsBuildUI = ListMenu._updateItemsBuildUI -- Set ListMenu behaviour: hist_menu._do_cover_images = display_mode ~= "list_only_meta" hist_menu._do_filename_only = display_mode == "list_image_filename" -- no need for do_hint_opened with History end hist_menu._do_hint_opened = BookInfoManager:getSetting("history_hint_opened") end -- And do now what the original does _FileManagerHistory_updateItemTable_orig(self) end function CoverBrowser:setupHistoryDisplayMode(display_mode) if not DISPLAY_MODES[display_mode] then display_mode = nil -- unknow mode, fallback to classic end if init_done and display_mode == history_display_mode then -- no change return end if init_done then -- save new mode in db BookInfoManager:saveSetting("history_display_mode", display_mode) end -- remember current mode in module variable history_display_mode = display_mode logger.dbg("CoverBrowser: setting History display mode to:", display_mode or "classic") if not init_done and not display_mode then return -- starting in classic mode, nothing to patch end -- We only need to replace one FileManagerHistory method if not display_mode then -- classic mode -- Put back original methods FileManagerHistory.updateItemTable = _FileManagerHistory_updateItemTable_orig FileManagerHistory.display_mode = nil else -- Replace original method with the one defined above FileManagerHistory.updateItemTable = _FileManagerHistory_updateItemTable -- And let it know which display_mode we should use FileManagerHistory.display_mode = display_mode end end return CoverBrowser