From 371e3336a5409ae41d8767c99ab35bbfceb84772 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 6 Nov 2019 00:17:28 +0100 Subject: [PATCH] [feat] Favorites: organize book into collections (#5527) View, add, remove, sort, open book to/from collections. For now, only one collection named Favorites. --- frontend/apps/filemanager/filemanager.lua | 65 ++++-- .../filemanager/filemanagercollection.lua | 124 ++++++++++++ frontend/apps/reader/readerui.lua | 6 + frontend/readcollection.lua | 159 +++++++++++++++ .../ui/elements/filemanager_menu_order.lua | 2 + frontend/ui/elements/reader_menu_order.lua | 2 + frontend/ui/widget/sortwidget.lua | 2 +- .../coverbrowser.koplugin/bookinfomanager.lua | 5 +- plugins/coverbrowser.koplugin/covermenu.lua | 133 ++++++++++++- plugins/coverbrowser.koplugin/main.lua | 186 ++++++++++++++++-- 10 files changed, 638 insertions(+), 46 deletions(-) create mode 100644 frontend/apps/filemanager/filemanagercollection.lua create mode 100644 frontend/readcollection.lua diff --git a/frontend/apps/filemanager/filemanager.lua b/frontend/apps/filemanager/filemanager.lua index e4f8f5c3a..14decd523 100644 --- a/frontend/apps/filemanager/filemanager.lua +++ b/frontend/apps/filemanager/filemanager.lua @@ -9,6 +9,7 @@ local DocumentRegistry = require("document/documentregistry") local Event = require("ui/event") local FileChooser = require("ui/widget/filechooser") local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo") +local FileManagerCollection = require("apps/filemanager/filemanagercollection") local FileManagerConverter = require("apps/filemanager/filemanagerconverter") local FileManagerFileSearcher = require("apps/filemanager/filemanagerfilesearcher") local FileManagerHistory = require("apps/filemanager/filemanagerhistory") @@ -23,6 +24,7 @@ local InputContainer = require("ui/widget/container/inputcontainer") local InputDialog = require("ui/widget/inputdialog") local MultiConfirmBox = require("ui/widget/multiconfirmbox") local PluginLoader = require("pluginloader") +local ReadCollection = require("readcollection") local ReaderDeviceStatus = require("apps/reader/modules/readerdevicestatus") local ReaderDictionary = require("apps/reader/modules/readerdictionary") local ReaderGesture = require("apps/reader/modules/readergesture") @@ -288,35 +290,59 @@ function FileManager:init() }, -- a little hack to get visual functionality grouping {}, - { + } + if lfs.attributes(file, "mode") == "file" then + table.insert(buttons, { { text = _("Open with…"), - enabled = lfs.attributes(file, "mode") == "file" and (DocumentRegistry:getProviders(file) == nil - or #(DocumentRegistry:getProviders(file)) > 1), + enabled = DocumentRegistry:getProviders(file) == nil or #(DocumentRegistry:getProviders(file)) > 1, callback = function() UIManager:close(self.file_dialog) self:showSetProviderButtons(file, FileManager.instance, ReaderUI) end, }, { - text = _("Convert"), - enabled = lfs.attributes(file, "mode") == "file" - and FileManagerConverter:isSupported(file), + text = _("Book information"), + enabled = FileManagerBookInfo:isSupported(file), callback = function() + FileManagerBookInfo:show(file) UIManager:close(self.file_dialog) - FileManagerConverter:showConvertButtons(file, self) end, - }, + } + }) + table.insert(buttons, { { - text = _("Book information"), - enabled = FileManagerBookInfo:isSupported(file), + text_func = function() + if ReadCollection:checkItemExist(file) then + return _("Remove from favorites") + else + return _("Add to favorites") + end + end, + enabled = DocumentRegistry:getProviders(file) ~= nil, callback = function() - FileManagerBookInfo:show(file) + if ReadCollection:checkItemExist(file) then + ReadCollection:removeItem(file) + else + ReadCollection:addItem(file) + end UIManager:close(self.file_dialog) end, }, - }, - } + }) + if FileManagerConverter:isSupported(file) then + table.insert(buttons, { + { + text = _("Convert"), + enabled = true, + callback = function() + UIManager:close(self.file_dialog) + FileManagerConverter:showConvertButtons(file, self) + end, + } + }) + end + end if lfs.attributes(file, "mode") == "directory" then local realpath = util.realpath(file) table.insert(buttons, { @@ -361,6 +387,9 @@ function FileManager:init() table.insert(self, FileManagerHistory:new{ ui = self, }) + table.insert(self, FileManagerCollection:new{ + ui = self, + }) table.insert(self, FileManagerFileSearcher:new{ ui = self }) table.insert(self, FileManagerShortcuts:new{ ui = self }) table.insert(self, ReaderDictionary:new{ ui = self }) @@ -695,10 +724,11 @@ function FileManager:pasteHere(file) self:moveFile(DocSettings:getSidecarDir(orig), dest) -- dest is always a directory end if self:moveFile(orig, dest) then - --update history + -- Update history and collections. local dest_file = string.format("%s/%s", dest, util.basename(orig)) require("readhistory"):updateItemByPath(orig, dest_file) - --update last open file + ReadCollection:updateItemByPath(orig, dest_file) + -- Update last open file. if G_reader_settings:readSetting("lastfile") == orig then G_reader_settings:saveSetting("lastfile", dest_file) end @@ -764,7 +794,7 @@ function FileManager:createFolder(curr_folder, new_folder) end function FileManager:deleteFile(file) - local ok, err + local ok, err, is_dir local file_abs_path = util.realpath(file) if file_abs_path == nil then UIManager:show(InfoMessage:new{ @@ -778,6 +808,7 @@ function FileManager:deleteFile(file) ok, err = os.remove(file_abs_path) else ok, err = util.purgeDir(file_abs_path) + is_dir = true end if ok and not err then if is_doc then @@ -789,6 +820,7 @@ function FileManager:deleteFile(file) end doc_settings:purge() end + ReadCollection:removeItemByPath(file, is_dir) UIManager:show(InfoMessage:new{ text = util.template(_("Deleted %1"), file), timeout = 2, @@ -804,6 +836,7 @@ function FileManager:renameFile(file) if util.basename(file) ~= self.rename_dialog:getInputText() then local dest = util.joinPath(util.dirname(file), self.rename_dialog:getInputText()) if self:moveFile(file, dest) then + ReadCollection:updateItemByPath(file, dest) if lfs.attributes(dest, "mode") == "file" then local doc = require("docsettings") local move_history = true; diff --git a/frontend/apps/filemanager/filemanagercollection.lua b/frontend/apps/filemanager/filemanagercollection.lua new file mode 100644 index 000000000..36387cbe3 --- /dev/null +++ b/frontend/apps/filemanager/filemanagercollection.lua @@ -0,0 +1,124 @@ +local ButtonDialogTitle = require("ui/widget/buttondialogtitle") +local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo") +local InputContainer = require("ui/widget/container/inputcontainer") +local Menu = require("ui/widget/menu") +local ReadCollection = require("readcollection") +local UIManager = require("ui/uimanager") +local Screen = require("device").screen +local _ = require("gettext") + +local FileManagerCollection = InputContainer:extend{ + coll_menu_title = _("Favorites"), +} + +function FileManagerCollection:init() + self.ui.menu:registerToMainMenu(self) +end + +function FileManagerCollection:addToMainMenu(menu_items) + menu_items.collections = { + text = self.coll_menu_title, + callback = function() + self:onShowColl("favorites") + end, + } +end + +function FileManagerCollection:updateItemTable() + -- Try to stay on current page. + local select_number = nil + if self.coll_menu.page and self.coll_menu.perpage then + select_number = (self.coll_menu.page - 1) * self.coll_menu.perpage + 1 + end + self.coll_menu:switchItemTable(self.coll_menu_title, + ReadCollection:prepareList(self.coll_menu.collection), select_number) +end + +function FileManagerCollection:onMenuHold(item) + self.collfile_dialog = nil + local buttons = { + { + { + text = _("Sort"), + callback = function() + UIManager:close(self.collfile_dialog) + local item_table = {} + for i=1, #self._manager.coll_menu.item_table do + table.insert(item_table, {text = self._manager.coll_menu.item_table[i].text, label = self._manager.coll_menu.item_table[i].file}) + end + local SortWidget = require("ui/widget/sortwidget") + local sort_item + sort_item = SortWidget:new{ + title = _("Sort favorites"), + item_table = item_table, + callback = function() + local new_order_table = {} + for i=1, #sort_item.item_table do + table.insert(new_order_table, { + file = sort_item.item_table[i].label, + order = i + }) + end + ReadCollection:writeCollection(new_order_table, self._manager.coll_menu.collection) + self._manager:updateItemTable() + end + } + UIManager:show(sort_item) + + end, + }, + { + text = _("Remove from collection"), + callback = function() + ReadCollection:removeItem(item.file, self._manager.coll_menu.collection) + self._manager:updateItemTable() + UIManager:close(self.collfile_dialog) + end, + }, + }, + { + { + text = _("Book information"), + enabled = FileManagerBookInfo:isSupported(item.file), + callback = function() + FileManagerBookInfo:show(item.file) + UIManager:close(self.collfile_dialog) + end, + }, + }, + } + self.collfile_dialog = ButtonDialogTitle:new{ + title = item.text:match("([^/]+)$"), + title_align = "center", + buttons = buttons, + } + UIManager:show(self.collfile_dialog) + return true +end + +function FileManagerCollection:onShowColl(collection) + self.coll_menu = Menu:new{ + ui = self.ui, + width = Screen:getWidth(), + height = Screen:getHeight(), + covers_fullscreen = true, -- hint for UIManager:_repaint() + is_borderless = true, + is_popout = false, + onMenuHold = self.onMenuHold, + _manager = self, + collection = collection, + } + self:updateItemTable() + self.coll_menu.close_callback = function() + -- Close it at next tick so it stays displayed + -- while a book is opening (avoids a transient + -- display of the underlying File Browser) + UIManager:nextTick(function() + UIManager:close(self.coll_menu) + end) + end + UIManager:show(self.coll_menu) + return true +end + +return FileManagerCollection diff --git a/frontend/apps/reader/readerui.lua b/frontend/apps/reader/readerui.lua index 4371cb886..5f947cf67 100644 --- a/frontend/apps/reader/readerui.lua +++ b/frontend/apps/reader/readerui.lua @@ -11,6 +11,7 @@ local DocSettings = require("docsettings") local DocumentRegistry = require("document/documentregistry") local Event = require("ui/event") local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo") +local FileManagerCollection = require("apps/filemanager/filemanagercollection") local FileManagerHistory = require("apps/filemanager/filemanagerhistory") local FileManagerFileSearcher = require("apps/filemanager/filemanagerfilesearcher") local FileManagerShortcuts = require("apps/filemanager/filemanagershortcuts") @@ -351,6 +352,11 @@ function ReaderUI:init() dialog = self.dialog, ui = self, }) + -- collections/favorites view + self:registerModule("collections", FileManagerCollection:new{ + dialog = self.dialog, + ui = self, + }) -- book info self:registerModule("bookinfo", FileManagerBookInfo:new{ dialog = self.dialog, diff --git a/frontend/readcollection.lua b/frontend/readcollection.lua new file mode 100644 index 000000000..c213a3409 --- /dev/null +++ b/frontend/readcollection.lua @@ -0,0 +1,159 @@ +local DataStorage = require("datastorage") +local LuaSettings = require("luasettings") +local getFriendlySize = require("util").getFriendlySize +local lfs = require("libs/libkoreader-lfs") +local realpath = require("ffi/util").realpath +local util = require("util") + +local DEFAULT_COLLECTION_NAME = "favorites" +local collection_file = DataStorage:getSettingsDir() .. "/collection.lua" + +local ReadCollection = {} + +function ReadCollection:read(collection_name) + if not collection_name then collection_name = DEFAULT_COLLECTION_NAME end + local collections = LuaSettings:open(collection_file) + local coll = collections:readSetting(collection_name) or {} + local coll_max_item = 0 + + for _, v in pairs(coll) do + if v.order > coll_max_item then + coll_max_item = v.order + end + end + return coll, coll_max_item +end + +function ReadCollection:readAllCollection() + local collection = LuaSettings:open(collection_file) + if collection and collection.data then + return collection.data + else + return {} + end +end + +function ReadCollection:prepareList(collection_name) + local data = self:read(collection_name) + local list = {} + for _, v in pairs(data) do + local file_exists = lfs.attributes(v.file, "mode") == "file" + table.insert(list, { + order = v.order, + text = v.file:gsub(".*/", ""), + file = realpath(v.file) or v.file, -- keep orig file path of deleted files + dim = not file_exists, -- "dim", as expected by Menu + mandatory = file_exists and getFriendlySize(lfs.attributes(v.file, "size") or 0), + callback = function() + local ReaderUI = require("apps/reader/readerui") + ReaderUI:showReader(v.file) + end + }) + end + table.sort(list, function(v1,v2) + return v1.order < v2.order + end) + return list +end + +function ReadCollection:removeItemByPath(path, is_dir) + local dir + local should_write = false + if is_dir then + path = path .. "/" + end + local coll = self:readAllCollection() + for i, _ in pairs(coll) do + local single_collection = coll[i] + for item = #single_collection, 1, -1 do + if not is_dir and single_collection[item].file == path then + should_write = true + table.remove(single_collection, item) + elseif is_dir then + dir = util.splitFilePathName(single_collection[item].file) + if dir == path then + should_write = true + table.remove(single_collection, item) + end + end + end + end + if should_write then + local collection = LuaSettings:open(collection_file) + collection.data = coll + collection:flush() + end +end + +function ReadCollection:updateItemByPath(old_path, new_path) + local is_dir = false + local dir, file + if lfs.attributes(new_path, "mode") == "directory" then + is_dir = true + old_path = old_path .. "/" + end + local should_write = false + local coll = self:readAllCollection() + for i, j in pairs(coll) do + for k, v in pairs(j) do + if not is_dir and v.file == old_path then + should_write = true + coll[i][k].file = new_path + elseif is_dir then + dir, file = util.splitFilePathName(v.file) + if dir == old_path then + should_write = true + coll[i][k].file = string.format("%s/%s", new_path, file) + end + end + end + end + if should_write then + local collection = LuaSettings:open(collection_file) + collection.data = coll + collection:flush() + end +end + +function ReadCollection:removeItem(item, collection_name) + local coll = self:read(collection_name) + for k, v in pairs(coll) do + if v.file == item then + table.remove(coll, k) + break + end + end + self:writeCollection(coll, collection_name) +end + +function ReadCollection:writeCollection(coll_items, collection_name) + if not collection_name then collection_name = DEFAULT_COLLECTION_NAME end + local collection = LuaSettings:open(collection_file) + collection:saveSetting(collection_name, coll_items) + collection:flush() +end + +function ReadCollection:addItem(file, collection_name) + local coll, coll_max_item = self:read(collection_name) + coll_max_item = coll_max_item + 1 + local collection_item = + { + file = file, + order = coll_max_item + } + table.insert(coll, collection_item) + self:writeCollection(coll, collection_name) +end + +function ReadCollection:checkItemExist(item, collection_name) + local coll = self:read(collection_name) + for _, v in pairs(coll) do + if v.file == item then + return true + end + end + return false +end + +return ReadCollection + diff --git a/frontend/ui/elements/filemanager_menu_order.lua b/frontend/ui/elements/filemanager_menu_order.lua index 6ff864630..a3f2bb4a4 100644 --- a/frontend/ui/elements/filemanager_menu_order.lua +++ b/frontend/ui/elements/filemanager_menu_order.lua @@ -130,6 +130,8 @@ local order = { "history", "open_last_document", "----------------------------", + "collections", + "----------------------------", "system_statistics", "mass_storage_actions", "----------------------------", diff --git a/frontend/ui/elements/reader_menu_order.lua b/frontend/ui/elements/reader_menu_order.lua index 6b45cebf6..bca477d3e 100644 --- a/frontend/ui/elements/reader_menu_order.lua +++ b/frontend/ui/elements/reader_menu_order.lua @@ -151,6 +151,8 @@ local order = { "history", "open_previous_document", "----------------------------", + "collections", + "----------------------------", "book_status", "book_info", "----------------------------", diff --git a/frontend/ui/widget/sortwidget.lua b/frontend/ui/widget/sortwidget.lua index ba4a740c8..56299079f 100644 --- a/frontend/ui/widget/sortwidget.lua +++ b/frontend/ui/widget/sortwidget.lua @@ -406,7 +406,7 @@ function SortWidget:_populateItems() height = self.item_height, width = self.item_width, text = self.item_table[idx].text, - lable = self.item_table[idx].label, + label = self.item_table[idx].label, invert = invert_status, index = idx, show_parent = self, diff --git a/plugins/coverbrowser.koplugin/bookinfomanager.lua b/plugins/coverbrowser.koplugin/bookinfomanager.lua index 6a549e244..1bc0c70d3 100644 --- a/plugins/coverbrowser.koplugin/bookinfomanager.lua +++ b/plugins/coverbrowser.koplugin/bookinfomanager.lua @@ -267,7 +267,8 @@ function BookInfoManager:getBookInfo(filepath, get_cover) -- files with unknown book extension. If not a supported extension, -- returns a bookinfo like-object enough for a correct display and -- to not trigger extraction, so we don't clutter DB with such files. - if not DocumentRegistry:hasProvider(filepath) then + local is_directory = lfs.attributes(filepath, "mode") == "directory" + if is_directory or not DocumentRegistry:hasProvider(filepath) then return { directory = directory, filename = filename, @@ -277,6 +278,8 @@ function BookInfoManager:getBookInfo(filepath, get_cover) has_cover = nil, ignore_meta = "Y", ignore_cover = "Y", + -- for CoverMenu to *not* extend the onHold dialog: + _is_directory = is_directory, -- for ListMenu to show the filename *with* suffix: _no_provider = true } diff --git a/plugins/coverbrowser.koplugin/covermenu.lua b/plugins/coverbrowser.koplugin/covermenu.lua index fd4bbf0b8..3581816dd 100644 --- a/plugins/coverbrowser.koplugin/covermenu.lua +++ b/plugins/coverbrowser.koplugin/covermenu.lua @@ -204,8 +204,8 @@ function CoverMenu:updateItems(select_number) self.onFileHold_orig(self, file) local bookinfo = BookInfoManager:getBookInfo(file) - if not bookinfo then - -- If no bookinfo (yet) about this file, let the original dialog be + if not bookinfo or bookinfo._is_directory then + -- If no bookinfo (yet) about this file, or it's a directory, let the original dialog be return true end @@ -220,7 +220,7 @@ function CoverMenu:updateItems(select_number) UIManager:clearRenderStack() -- Replace Book information callback to use directly our bookinfo - orig_buttons[4][3].callback = function() + orig_buttons[4][2].callback = function() FileManagerBookInfo:show(file, bookinfo) UIManager:close(self.file_dialog) end @@ -237,7 +237,7 @@ function CoverMenu:updateItems(select_number) end -- Add some new buttons to original buttons set - table.insert(orig_buttons, { + table.insert(orig_buttons[5], 1, { -- Mark the book as read/unread text_func = function() -- If the book has a cache entry, it means it has a sidecar file, and it *may* have the info we need. @@ -289,11 +289,8 @@ function CoverMenu:updateItems(select_number) UIManager:close(self.file_dialog) self:updateItems() end, - }, - }) - - -- Move the "Convert" button ([4][2]) to the left of the "Mark as..." button [5][1] we've just added - table.insert(orig_buttons[5], 1, table.remove(orig_buttons[4], 2)) + } + ) -- Keep on adding new buttons table.insert(orig_buttons, { @@ -521,6 +518,124 @@ function CoverMenu:onHistoryMenuHold(item) return true end +-- Similar to onFileHold setup just above, but for Collections, +-- which is plugged in main.lua _FileManagerCollections_updateItemTable() +function CoverMenu:onCollectionsMenuHold(item) + -- Call original function: it will create a ButtonDialog + -- and store it as self.collfile_dialog, and UIManager:show() it. + self.onMenuHold_orig(self, item) + local file = item.file + + local bookinfo = BookInfoManager:getBookInfo(file) + if not bookinfo then + -- If no bookinfo (yet) about this file, let the original dialog be + return true + end + + -- Remember some of this original ButtonDialogTitle properties + local orig_title = self.collfile_dialog.title + local orig_title_align = self.collfile_dialog.title_align + local orig_buttons = self.collfile_dialog.buttons + -- Close original ButtonDialog (it has not yet been painted + -- on screen, so we won't see it) + UIManager:close(self.collfile_dialog) + UIManager:clearRenderStack() + + -- Replace Book information callback to use directly our bookinfo + orig_buttons[2][1].callback = function() + FileManagerBookInfo:show(file, bookinfo) + UIManager:close(self.collfile_dialog) + end + + -- Add some new buttons to original buttons set + table.insert(orig_buttons, { + { -- Allow user to view real size cover in ImageViewer + text = _("View full size cover"), + enabled = bookinfo.has_cover and true or false, + callback = function() + local document = DocumentRegistry:openDocument(file) + if document then + if document.loadDocument then -- needed for crengine + document:loadDocument(false) -- load only metadata + end + local cover_bb = document:getCoverPageImage() + if cover_bb then + local imgviewer = ImageViewer:new{ + image = cover_bb, + with_title_bar = false, + fullscreen = true, + } + UIManager:show(imgviewer) + else + UIManager:show(InfoMessage:new{ + text = _("No cover image available."), + }) + end + UIManager:close(self.collfile_dialog) + DocumentRegistry:closeDocument(file) + end + end, + }, + { -- Allow user to directly view description in TextViewer + text = bookinfo.description and _("View book description") or _("No book description"), + enabled = bookinfo.description and true or false, + callback = function() + local description = require("util").htmlToPlainTextIfHtml(bookinfo.description) + local textviewer = TextViewer:new{ + title = bookinfo.title, + text = description, + } + UIManager:close(self.collfile_dialog) + UIManager:show(textviewer) + end, + }, + }) + table.insert(orig_buttons, { + { -- Allow user to ignore some offending cover image + text = bookinfo.ignore_cover and _("Unignore cover") or _("Ignore cover"), + enabled = bookinfo.has_cover and true or false, + callback = function() + BookInfoManager:setBookInfoProperties(file, { + ["ignore_cover"] = not bookinfo.ignore_cover and 'Y' or false, + }) + UIManager:close(self.collfile_dialog) + self:updateItems() + end, + }, + { -- Allow user to ignore some bad metadata (filename will be used instead) + text = bookinfo.ignore_meta and _("Unignore metadata") or _("Ignore metadata"), + enabled = bookinfo.has_meta and true or false, + callback = function() + BookInfoManager:setBookInfoProperties(file, { + ["ignore_meta"] = not bookinfo.ignore_meta and 'Y' or false, + }) + UIManager:close(self.collfile_dialog) + self:updateItems() + end, + }, + }) + table.insert(orig_buttons, { + { -- Allow a new extraction (multiple interruptions, book replaced)... + text = _("Refresh cached book information"), + enabled = bookinfo and true or false, + callback = function() + BookInfoManager:deleteBookInfo(file) + UIManager:close(self.collfile_dialog) + self:updateItems() + end, + }, + }) + -- Create the new ButtonDialog, and let UIManager show it + local ButtonDialogTitle = require("ui/widget/buttondialogtitle") + self.collfile_dialog = ButtonDialogTitle:new{ + title = orig_title, + title_align = orig_title_align, + buttons = orig_buttons, + } + UIManager:show(self.collfile_dialog) + return true +end + function CoverMenu:onCloseWidget() -- Due to close callback in FileManagerHistory:onShowHist, we may be called -- multiple times (witnessed that with print(debug.traceback()) diff --git a/plugins/coverbrowser.koplugin/main.lua b/plugins/coverbrowser.koplugin/main.lua index df99cb533..5d3bef94e 100644 --- a/plugins/coverbrowser.koplugin/main.lua +++ b/plugins/coverbrowser.koplugin/main.lua @@ -23,6 +23,9 @@ local _FileChooser_onCloseWidget_orig = FileChooser.onCloseWidget local FileManagerHistory = require("apps/filemanager/filemanagerhistory") local _FileManagerHistory_updateItemTable_orig = FileManagerHistory.updateItemTable +local FileManagerCollection = require("apps/filemanager/filemanagercollection") +local _FileManagerCollection_updateItemTable_orig = FileManagerCollection.updateItemTable + local FileManager = require("apps/filemanager/filemanager") local _FileManager_tapPlus_orig = FileManager.tapPlus @@ -40,6 +43,7 @@ local DISPLAY_MODES = { local init_done = false local filemanager_display_mode = false -- not initialized yet local history_display_mode = false -- not initialized yet +local collection_display_mode = false -- not initialized yet local series_mode = nil -- defaults to not display series local CoverBrowser = InputContainer:new{ @@ -75,6 +79,7 @@ function CoverBrowser:init() self:setupFileManagerDisplayMode(BookInfoManager:getSetting("filemanager_display_mode")) self:setupHistoryDisplayMode(BookInfoManager:getSetting("history_display_mode")) + self:setupCollectionDisplayMode(BookInfoManager:getSetting("collection_display_mode")) series_mode = BookInfoManager:getSetting("series_mode") init_done = true @@ -265,35 +270,100 @@ function CoverBrowser:addToMainMenu(menu_items) separator = true, }, }, - separator = true, }, - -- Misc settings { - text = _("Other settings"), + text = _("Favorites display mode"), sub_item_table = { { - text = _("Show hint for books with description"), - checked_func = function() return not BookInfoManager:getSetting("no_hint_description") end, + text = _("Classic (filename only)"), + checked_func = function() return not collection_display_mode 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() + self:setupCollectionDisplayMode("") end, }, { - text = _("Show hint for opened books in history"), - checked_func = function() return BookInfoManager:getSetting("history_hint_opened") end, + text = _("Mosaic with cover images"), + checked_func = function() return collection_display_mode == "mosaic_image" 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() + self:setupCollectionDisplayMode("mosaic_image") + end, + }, + { + text = _("Mosaic with text covers"), + checked_func = function() return collection_display_mode == "mosaic_text" end, + callback = function() + self:setupCollectionDisplayMode("mosaic_text") + end, + }, + { + text = _("Detailed list with cover images and metadata"), + checked_func = function() return collection_display_mode == "list_image_meta" end, + callback = function() + self:setupCollectionDisplayMode("list_image_meta") + end, + }, + { + text = _("Detailed list with metadata, no images"), + checked_func = function() return collection_display_mode == "list_only_meta" end, + callback = function() + self:setupCollectionDisplayMode("list_only_meta") + end, + }, + { + text = _("Detailed list with cover images and filenames"), + checked_func = function() return collection_display_mode == "list_image_filename" end, + callback = function() + self:setupCollectionDisplayMode("list_image_filename") end, + separator = true, + }, + }, + separator = true, + }, + -- Misc settings + { + text = _("Other settings"), + sub_item_table = { + { + text = _("Display hints"), + 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 = _("Show hint for opened books in favorites"), + checked_func = function() return BookInfoManager:getSetting("collections_hint_opened") end, + callback = function() + if BookInfoManager:getSetting("collections_hint_opened") then + BookInfoManager:saveSetting("collections_hint_opened", false) + else + BookInfoManager:saveSetting("collections_hint_opened", true) + end + self:refreshFileManagerInstance() + end, + } + } }, { text = _("Series "), @@ -666,4 +736,82 @@ function CoverBrowser:setupHistoryDisplayMode(display_mode) end end +local function _FileManagerCollections_updateItemTable(self) + -- 'self' here is the single FileManagerCollections instance + -- FileManagerCollections has just created a new instance of Menu as 'coll_menu' + -- at each display of Collection/Favorites. Soon after instantiation, this method + -- is called. The first time it is called, we replace some methods. + local display_mode = self.display_mode + local coll_menu = self.coll_menu + + if not coll_menu._coverbrowser_overridden then + coll_menu._coverbrowser_overridden = true + + -- In both mosaic and list modes, replace original methods with those from + -- our generic CoverMenu + local CoverMenu = require("covermenu") + coll_menu.updateItems = CoverMenu.updateItems + coll_menu.onCloseWidget = CoverMenu.onCloseWidget + -- Also replace original onMenuHold (it will use original method, so remember it) + coll_menu.onMenuHold_orig = coll_menu.onMenuHold + coll_menu.onMenuHold = CoverMenu.onCollectionsMenuHold + + 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") + coll_menu._recalculateDimen = MosaicMenu._recalculateDimen + coll_menu._updateItemsBuildUI = MosaicMenu._updateItemsBuildUI + -- Set MosaicMenu behaviour: + coll_menu._do_cover_images = display_mode ~= "mosaic_text" + + 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") + coll_menu._recalculateDimen = ListMenu._recalculateDimen + coll_menu._updateItemsBuildUI = ListMenu._updateItemsBuildUI + -- Set ListMenu behaviour: + coll_menu._do_cover_images = display_mode ~= "list_only_meta" + coll_menu._do_filename_only = display_mode == "list_image_filename" + + end + coll_menu._do_hint_opened = BookInfoManager:getSetting("collections_hint_opened") + end + + -- And do now what the original does + _FileManagerCollection_updateItemTable_orig(self) +end + + +function CoverBrowser:setupCollectionDisplayMode(display_mode) + if not DISPLAY_MODES[display_mode] then + display_mode = nil -- unknow mode, fallback to classic + end + if init_done and display_mode == collection_display_mode then -- no change + return + end + if init_done then -- save new mode in db + BookInfoManager:saveSetting("collection_display_mode", display_mode) + end + -- remember current mode in module variable + collection_display_mode = display_mode + logger.dbg("CoverBrowser: setting Collection 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 FileManagerCollection method + if not display_mode then -- classic mode + -- Put back original methods + FileManagerCollection.updateItemTable = _FileManagerCollection_updateItemTable_orig + FileManagerCollection.display_mode = nil + else + -- Replace original method with the one defined above + FileManagerCollection.updateItemTable = _FileManagerCollections_updateItemTable + -- And let it know which display_mode we should use + FileManagerCollection.display_mode = display_mode + end +end + return CoverBrowser