diff --git a/frontend/apps/filemanager/filemanagercollection.lua b/frontend/apps/filemanager/filemanagercollection.lua index 9ed402bb2..c4191530e 100644 --- a/frontend/apps/filemanager/filemanagercollection.lua +++ b/frontend/apps/filemanager/filemanagercollection.lua @@ -28,6 +28,8 @@ local FileManagerCollection = WidgetContainer:extend{ } function FileManagerCollection:init() + self.doc_props_cache = {} + self.updated_collections = {} self.ui.menu:registerToMainMenu(self) end @@ -46,6 +48,13 @@ function FileManagerCollection:addToMainMenu(menu_items) } end +function FileManagerCollection:getDocProps(file) + if self.doc_props_cache[file] == nil then + self.doc_props_cache[file] = self.ui.bookinfo:getDocProps(file, nil, true) -- do not open the document + end + return self.doc_props_cache[file] +end + -- collection function FileManagerCollection:getCollectionTitle(collection_name) @@ -66,6 +75,8 @@ end function FileManagerCollection:onShowColl(collection_name) collection_name = collection_name or ReadCollection.default_collection_name self.coll_menu = BookList:new{ + name = "collections", + path = collection_name, title_bar_left_icon = "appbar.menu", onLeftButtonTap = function() self:showCollDialog() end, onReturn = function() @@ -78,7 +89,6 @@ function FileManagerCollection:onShowColl(collection_name) ui = self.ui, _manager = self, _recreate_func = function() self:onShowColl(collection_name) end, - collection_name = collection_name, } table.insert(self.coll_menu.paths, true) -- enable onReturn button self.coll_menu.close_callback = function() @@ -87,25 +97,60 @@ function FileManagerCollection:onShowColl(collection_name) self.coll_menu = nil self.match_table = nil end + self:setCollate() self:updateItemTable() UIManager:show(self.coll_menu) return true end -function FileManagerCollection:updateItemTable(show_last_item, item_table) +function FileManagerCollection:updateItemTable(item_table, focused_file) if item_table == nil then item_table = {} - for _, item in pairs(ReadCollection.coll[self.coll_menu.collection_name]) do + for _, item in pairs(ReadCollection.coll[self.coll_menu.path]) do if self:isItemMatch(item) then - table.insert(item_table, item) + local item_tmp = { + file = item.file, + text = item.text, + order = item.order, + attr = item.attr, + mandatory = self.mandatory_func and self.mandatory_func(item) or util.getFriendlySize(item.attr.size or 0), + } + if self.item_func then + self.item_func(item_tmp, self:getDocProps(item_tmp.file)) + end + table.insert(item_table, item_tmp) end end if #item_table > 1 then - table.sort(item_table, function(v1, v2) return v1.order < v2.order end) + table.sort(item_table, self.sorting_func) end end - local collection_name = self:getCollectionTitle(self.coll_menu.collection_name) - local title = T("%1 (%2)", collection_name, #item_table) + local title, subtitle = self:getBookListTitle(item_table) + self.coll_menu:switchItemTable(title, item_table, -1, focused_file and { file = focused_file }, subtitle) +end + +function FileManagerCollection:isItemMatch(item) + if self.match_table then + if self.match_table.status then + if self.match_table.status ~= BookList.getBookStatus(item.file) then + return false + end + end + if self.match_table.props then + local doc_props = self:getDocProps(item.file) + for prop, value in pairs(self.match_table.props) do + if (doc_props[prop] or self.empty_prop) ~= value then + return false + end + end + end + end + return true +end + +function FileManagerCollection:getBookListTitle(item_table) + local collection_title = self:getCollectionTitle(self.coll_menu.path) + local title = T("%1 (%2)", collection_title, #item_table) local subtitle = "" if self.match_table then subtitle = {} @@ -125,27 +170,7 @@ function FileManagerCollection:updateItemTable(show_last_item, item_table) subtitle = table.concat(subtitle, " | ") end end - local item_number = show_last_item and #item_table or -1 - self.coll_menu:switchItemTable(title, item_table, item_number, nil, subtitle) -end - -function FileManagerCollection:isItemMatch(item) - if self.match_table then - if self.match_table.status then - if self.match_table.status ~= BookList.getBookStatus(item.file) then - return false - end - end - if self.match_table.props then - local doc_props = self.ui.bookinfo:getDocProps(item.file, nil, true) - for prop, value in pairs(self.match_table.props) do - if (doc_props[prop] or self.empty_prop) ~= value then - return false - end - end - end - end - return true + return title, subtitle end function FileManagerCollection:onSetDimensions(dimen) @@ -219,7 +244,8 @@ function FileManagerCollection:onMenuHold(item) { text = _("Remove from collection"), callback = function() - ReadCollection:removeItem(file, self.collection_name) + self._manager.updated_collections[self.path] = true + ReadCollection:removeItem(file, self.path, true) close_dialog_update_callback() end, }, @@ -263,6 +289,7 @@ function FileManagerCollection.getMenuInstance() end function FileManagerCollection:showCollDialog() + local collection_name = self.coll_menu.path local coll_not_empty = #self.coll_menu.item_table > 0 local coll_dialog local function genFilterByStatusButton(button_status) @@ -284,7 +311,7 @@ function FileManagerCollection:showCollDialog() UIManager:close(coll_dialog) local prop_values = {} for idx, item in ipairs(self.coll_menu.item_table) do - local doc_prop = self.ui.bookinfo:getDocProps(item.file, nil, true)[button_prop] + local doc_prop = self:getDocProps(item.file)[button_prop] if doc_prop == nil then doc_prop = { self.empty_prop } elseif button_prop == "series" then @@ -344,18 +371,18 @@ function FileManagerCollection:showCollDialog() enabled = coll_not_empty, callback = function() UIManager:close(coll_dialog) - self:onShowCollectionsSearchDialog(nil, self.coll_menu.collection_name) + self:onShowCollectionsSearchDialog(nil, collection_name) + end, + }}, + {{ + text = _("Arrange books in collection"), + enabled = coll_not_empty and self.match_table == nil, + callback = function() + UIManager:close(coll_dialog) + self:showArrangeBooksDialog() end, }}, {}, -- separator - {{ - text = _("Arrange books in collection"), - enabled = coll_not_empty, - callback = function() - UIManager:close(coll_dialog) - self:sortCollection() - end, - }}, {{ text = _("Add all books from a folder"), callback = function() @@ -379,9 +406,10 @@ function FileManagerCollection:showCollDialog() path = G_reader_settings:readSetting("home_dir"), select_directory = false, onConfirm = function(file) - if not ReadCollection:isFileInCollection(file, self.coll_menu.collection_name) then - ReadCollection:addItem(file, self.coll_menu.collection_name) - self:updateItemTable(true) -- show added item + if not ReadCollection:isFileInCollection(file, collection_name) then + self.updated_collections[collection_name] = true + ReadCollection:addItem(file, collection_name) + self:updateItemTable(nil, file) -- show added item self.files_updated = true end end, @@ -392,19 +420,21 @@ function FileManagerCollection:showCollDialog() } if self.ui.document then local file = self.ui.document.file - local is_in_collection = ReadCollection:isFileInCollection(file, self.coll_menu.collection_name) + local is_in_collection = ReadCollection:isFileInCollection(file, collection_name) table.insert(buttons, {{ text_func = function() return is_in_collection and _("Remove current book from collection") or _("Add current book to collection") end, callback = function() UIManager:close(coll_dialog) + self.updated_collections[collection_name] = true if is_in_collection then - ReadCollection:removeItem(file, self.coll_menu.collection_name) + ReadCollection:removeItem(file, collection_name, true) + file = nil else - ReadCollection:addItem(file, self.coll_menu.collection_name) + ReadCollection:addItem(file, collection_name) end - self:updateItemTable(not is_in_collection) + self:updateItemTable(nil, file) self.files_updated = true end, }}) @@ -429,7 +459,7 @@ function FileManagerCollection:showPropValueList(prop, prop_values) for _, idx in ipairs(item_idxs) do table.insert(item_table, self.coll_menu.item_table[idx]) end - self:updateItemTable(nil, item_table) + self:updateItemTable(item_table) end, }) end @@ -447,17 +477,104 @@ function FileManagerCollection:showPropValueList(prop, prop_values) UIManager:show(prop_menu) end -function FileManagerCollection:sortCollection() - local sort_widget - sort_widget = SortWidget:new{ - title = _("Arrange books in collection"), - item_table = ReadCollection:getOrderedCollection(self.coll_menu.collection_name), - callback = function() - ReadCollection:updateCollectionOrder(self.coll_menu.collection_name, sort_widget.item_table) - self:updateItemTable() +function FileManagerCollection:setCollate(collate_id, collate_reverse) + local coll_settings = ReadCollection.coll_settings[self.coll_menu.path] + if collate_id == nil then + collate_id = coll_settings.collate + else + coll_settings.collate = collate_id or nil + end + if collate_reverse == nil then + collate_reverse = coll_settings.collate_reverse + else + coll_settings.collate_reverse = collate_reverse or nil + end + if collate_id then + local collate = BookList.metadata_collates[collate_id] or BookList.collates[collate_id] + self.item_func = collate.item_func + self.mandatory_func = collate.mandatory_func + self.sorting_func, self.sort_cache = collate.init_sort_func(self.sort_cache) + if collate_reverse then + local sorting_func_unreversed = self.sorting_func + self.sorting_func = function(a, b) return sorting_func_unreversed(b, a) end end + else -- manual + self.item_func = nil + self.mandatory_func = nil + self.sorting_func = function(a, b) return a.order < b.order end + end +end + +function FileManagerCollection:showArrangeBooksDialog() + local collection_name = self.coll_menu.path + local coll_settings = ReadCollection.coll_settings[collection_name] + local curr_collate_id = coll_settings.collate + local arrange_dialog + local function genCollateButton(collate_id) + local collate = BookList.metadata_collates[collate_id] or BookList.collates[collate_id] + return { + text = collate.text .. (curr_collate_id == collate_id and " ✓" or ""), + callback = function() + if curr_collate_id ~= collate_id then + UIManager:close(arrange_dialog) + self.updated_collections[collection_name] = true + self:setCollate(collate_id) + self:updateItemTable() + end + end, + } + end + local buttons = { + { + genCollateButton("authors"), + genCollateButton("title"), + }, + { + genCollateButton("keywords"), + genCollateButton("series"), + }, + { + genCollateButton("natural"), + genCollateButton("strcoll"), + }, + { + genCollateButton("size"), + genCollateButton("access"), + }, + {{ + text = _("Reverse sorting") .. (coll_settings.collate_reverse and " ✓" or ""), + enabled = curr_collate_id and true or false, -- disabled for manual sorting + callback = function() + UIManager:close(arrange_dialog) + self.updated_collections[collection_name] = true + self:setCollate(nil, not coll_settings.collate_reverse) + self:updateItemTable() + end, + }}, + {}, -- separator + {{ + text = _("Manual sorting") .. (curr_collate_id == nil and " ✓" or ""), + callback = function() + UIManager:close(arrange_dialog) + UIManager:show(SortWidget:new{ + title = _("Arrange books in collection"), + item_table = self.coll_menu.item_table, + callback = function() + ReadCollection:updateCollectionOrder(collection_name, self.coll_menu.item_table) + self.updated_collections[collection_name] = true + self:setCollate(false, false) + self:updateItemTable() + end, + }) + end, + }}, } - UIManager:show(sort_widget) + arrange_dialog = ButtonDialog:new{ + title = _("Sort by"), + title_align = "center", + buttons = buttons, + } + UIManager:show(arrange_dialog) end function FileManagerCollection:addBooksFromFolder(include_subfolders) @@ -470,11 +587,12 @@ function FileManagerCollection:addBooksFromFolder(include_subfolders) util.findFiles(folder, function(file) files_found[file] = DocumentRegistry:hasProvider(file) or nil end, include_subfolders) - local count = ReadCollection:addItemsMultiple(files_found, { [self.coll_menu.collection_name] = true }) + local count = ReadCollection:addItemsMultiple(files_found, { [self.coll_menu.path] = true }) local text if count == 0 then text = _("No books added to collection") else + self.updated_collections[self.coll_menu.path] = true text = T(N_("1 book added to collection", "%1 books added to collection", count), count) self:updateItemTable() self.files_updated = true @@ -485,9 +603,14 @@ function FileManagerCollection:addBooksFromFolder(include_subfolders) UIManager:show(path_chooser) end -function FileManagerCollection:onBookMetadataChanged() +function FileManagerCollection:onBookMetadataChanged(prop_updated) + local file + if prop_updated then + file = prop_updated.filepath + self.doc_props_cache[file] = prop_updated.doc_props + end if self.coll_menu then - self.coll_menu:updateItems() + self:updateItemTable(nil, file) -- keep showing the changed file end end @@ -507,6 +630,7 @@ function FileManagerCollection:onShowCollList(file_or_selected_collections, call self.selected_collections = nil end self.coll_list = Menu:new{ + path = true, -- draw focus subtitle = "", covers_fullscreen = true, is_borderless = true, @@ -547,7 +671,7 @@ function FileManagerCollection:updateCollListItemTable(do_init, item_number) text = self:getCollectionTitle(name), mandatory = mandatory, name = name, - order = ReadCollection.coll_order[name], + order = ReadCollection.coll_settings[name].order, }) end if #item_table > 1 then @@ -571,7 +695,6 @@ function FileManagerCollection:updateCollListItemTable(do_init, item_number) end elseif self.from_collection_name ~= nil then itemmatch = { text = self.from_collection_name } - self.coll_list.path = true -- draw focus self.from_collection_name = nil end self.coll_list:switchItemTable(title, item_table, item_number or -1, itemmatch, subtitle) @@ -747,6 +870,7 @@ end function FileManagerCollection:addCollection() local editCallback = function(name) + self.updated_collections[name] = true ReadCollection:addCollection(name) local mandatory if self.selected_collections then @@ -759,7 +883,7 @@ function FileManagerCollection:addCollection() text = name, mandatory = mandatory, name = name, - order = ReadCollection.coll_order[name], + order = ReadCollection.coll_settings[name].order, }) self:updateCollListItemTable(false, #self.coll_list.item_table) -- show added item end @@ -768,6 +892,7 @@ end function FileManagerCollection:renameCollection(item) local editCallback = function(name) + self.updated_collections[name] = true ReadCollection:renameCollection(item.name, name) self.coll_list.item_table[item.idx].text = name self.coll_list.item_table[item.idx].name = name @@ -781,6 +906,7 @@ function FileManagerCollection:removeCollection(item) text = _("Remove collection?") .. "\n\n" .. item.text, ok_text = _("Remove"), ok_callback = function() + self.updated_collections[item.name] = true ReadCollection:removeCollection(item.name) table.remove(self.coll_list.item_table, item.idx) self:updateCollListItemTable() @@ -795,6 +921,7 @@ function FileManagerCollection:sortCollections() title = _("Arrange collections"), item_table = util.tableDeepCopy(self.coll_list.item_table), callback = function() + self.updated_collections = { true } -- all ReadCollection:updateCollectionListOrder(sort_widget.item_table) self:updateCollListItemTable(true) -- init end, @@ -867,8 +994,7 @@ function FileManagerCollection:searchCollections(coll_name) if not DocumentRegistry:hasProvider(file) then return false end - local book_props = self.ui.bookinfo:getDocProps(file, nil, true) -- do not open the document - book_props.display_title = nil + local book_props = self:getDocProps(file) if next(book_props) ~= nil and self.ui.bookinfo:findInProps(book_props, self.search_str, self.case_sensitive) then return true end @@ -907,7 +1033,7 @@ function FileManagerCollection:searchCollections(coll_name) return false end - local collections = coll_name and { coll_name = ReadCollection.coll[coll_name] } or ReadCollection.coll + local collections = coll_name and { [coll_name] = ReadCollection.coll[coll_name] } or ReadCollection.coll local Trapper = require("ui/trapper") local info = InfoMessage:new{ text = _("Searching… (tap to cancel)") } UIManager:show(info) @@ -915,7 +1041,7 @@ function FileManagerCollection:searchCollections(coll_name) local completed, files_found, files_found_order = Trapper:dismissableRunInSubprocess(function() local match_cache, _files_found, _files_found_order = {}, {}, {} for collection_name, coll in pairs(collections) do - local coll_order = ReadCollection.coll_order[collection_name] + local coll_order = ReadCollection.coll_settings[collection_name].order for _, item in pairs(coll) do local file = item.file if match_cache[file] == nil then -- a book can be included to several collections @@ -960,9 +1086,10 @@ function FileManagerCollection:searchCollections(coll_name) new_coll_name = new_coll_name .. " " .. T(_"(in %1)", coll_name) self.coll_menu.close_callback() end - ReadCollection:removeCollection(new_coll_name, true) - ReadCollection:addCollection(new_coll_name, true) - ReadCollection:addItemsMultiple(files_found, { [new_coll_name] = true }, true) + self.updated_collections[new_coll_name] = true + ReadCollection:removeCollection(new_coll_name) + ReadCollection:addCollection(new_coll_name) + ReadCollection:addItemsMultiple(files_found, { [new_coll_name] = true }) ReadCollection:updateCollectionOrder(new_coll_name, files_found_order) if self.coll_list ~= nil then UIManager:close(self.coll_list) @@ -972,6 +1099,12 @@ function FileManagerCollection:searchCollections(coll_name) end end +function FileManagerCollection:onCloseWidget() + if next(self.updated_collections) then + ReadCollection:write(self.updated_collections) + end +end + -- external function FileManagerCollection:genAddToCollectionButton(file_or_files, caller_pre_callback, caller_post_callback, button_disabled) @@ -984,6 +1117,9 @@ function FileManagerCollection:genAddToCollectionButton(file_or_files, caller_pr caller_pre_callback() end local caller_callback = function(selected_collections) + for name in pairs(selected_collections) do + self.updated_collections[name] = true + end if is_single_file then ReadCollection:addRemoveItemMultiple(file_or_files, selected_collections) else -- selected files diff --git a/frontend/apps/reader/readerui.lua b/frontend/apps/reader/readerui.lua index 942dc579a..c73ebb170 100644 --- a/frontend/apps/reader/readerui.lua +++ b/frontend/apps/reader/readerui.lua @@ -813,6 +813,7 @@ function ReaderUI:onClose(full_refresh) if self.document ~= nil then file = self.document.file require("readhistory"):updateLastBookTime(self.tearing_down) + require("readcollection"):updateLastBookTime(file) -- Serialize the most recently displayed page for later launch DocCache:serialize(file) logger.dbg("closing document") diff --git a/frontend/readcollection.lua b/frontend/readcollection.lua index a37b48316..1d678bd74 100644 --- a/frontend/readcollection.lua +++ b/frontend/readcollection.lua @@ -9,27 +9,26 @@ local collection_file = DataStorage:getSettingsDir() .. "/collection.lua" local ReadCollection = { coll = nil, -- hash table - coll_order = nil, -- hash table + coll_settings = nil, -- hash table last_read_time = 0, default_collection_name = "favorites", } -- read, write -local function buildEntry(file, order, mandatory) +local function buildEntry(file, order, attr) file = ffiUtil.realpath(file) - if not file then return end - if not mandatory then -- new item - local attr = lfs.attributes(file) - if not attr or attr.mode ~= "file" then return end - mandatory = util.getFriendlySize(attr.size or 0) + if file then + attr = attr or lfs.attributes(file) + if attr and attr.mode == "file" then + return { + file = file, + text = file:gsub(".*/", ""), + order = order, + attr = attr, + } + end end - return { - file = file, - text = file:gsub(".*/", ""), - mandatory = mandatory, - order = order, - } end function ReadCollection:_read() @@ -44,7 +43,7 @@ function ReadCollection:_read() end logger.dbg("ReadCollection: reading from collection file") self.coll = {} - self.coll_order = {} + self.coll_settings = {} for coll_name, collection in pairs(collections.data) do local coll = {} for _, v in ipairs(collection) do @@ -54,14 +53,11 @@ function ReadCollection:_read() end end self.coll[coll_name] = coll - if not collection.settings then -- favorites, first run - collection.settings = { order = 1 } - end - self.coll_order[coll_name] = collection.settings.order + self.coll_settings[coll_name] = collection.settings or { order = 1 } -- favorites, first run end end -function ReadCollection:write(collection_name) +function ReadCollection:write(updated_collections) local collections = LuaSettings:open(collection_file) for coll_name in pairs(collections.data) do if not self.coll[coll_name] then @@ -69,10 +65,11 @@ function ReadCollection:write(collection_name) end end for coll_name, coll in pairs(self.coll) do - if not collection_name or coll_name == collection_name then - local data = { settings = { order = self.coll_order[coll_name] } } + if updated_collections == nil or updated_collections[1] or updated_collections[coll_name] then + local is_manual_collate = not self.coll_settings[coll_name].collate or nil + local data = { settings = self.coll_settings[coll_name] } for _, item in pairs(coll) do - table.insert(data, { file = item.file, order = item.order }) + table.insert(data, { file = item.file, order = is_manual_collate and item.order }) end collections:saveSetting(coll_name, data) end @@ -81,6 +78,18 @@ function ReadCollection:write(collection_name) collections:flush() end +function ReadCollection:updateLastBookTime(file) + file = ffiUtil.realpath(file) + if file then + local now = os.time() + for _, coll in pairs(self.coll) do + if coll[file] then + coll[file].attr.access = now + end + end + end +end + -- info function ReadCollection:isFileInCollection(file, collection_name) @@ -111,23 +120,22 @@ function ReadCollection:getCollectionsWithFile(file) return collections end -function ReadCollection:getCollectionMaxOrder(collection_name) +function ReadCollection:getCollectionNextOrder(collection_name) + if self.coll_settings[collection_name].collate then return end local max_order = 0 for _, item in pairs(self.coll[collection_name]) do if max_order < item.order then max_order = item.order end end - return max_order + return max_order + 1 end -- manage items function ReadCollection:addItem(file, collection_name) - local max_order = self:getCollectionMaxOrder(collection_name) - local item = buildEntry(file, max_order + 1) + local item = buildEntry(file, self:getCollectionNextOrder(collection_name)) self.coll[collection_name][item.file] = item - self:write(collection_name) end function ReadCollection:addRemoveItemMultiple(file, collections_to_add) @@ -135,8 +143,7 @@ function ReadCollection:addRemoveItemMultiple(file, collections_to_add) for coll_name, coll in pairs(self.coll) do if collections_to_add[coll_name] then if not coll[file] then - local max_order = self:getCollectionMaxOrder(coll_name) - coll[file] = buildEntry(file, max_order + 1) + coll[file] = buildEntry(file, self:getCollectionNextOrder(coll_name)) end else if coll[file] then @@ -144,25 +151,20 @@ function ReadCollection:addRemoveItemMultiple(file, collections_to_add) end end end - self:write() end -function ReadCollection:addItemsMultiple(files, collections_to_add, no_write) +function ReadCollection:addItemsMultiple(files, collections_to_add) local count = 0 for file in pairs(files) do file = ffiUtil.realpath(file) or file for coll_name in pairs(collections_to_add) do local coll = self.coll[coll_name] if not coll[file] then - local max_order = self:getCollectionMaxOrder(coll_name) - coll[file] = buildEntry(file, max_order + 1) + coll[file] = buildEntry(file, self:getCollectionNextOrder(coll_name)) count = count + 1 end end end - if not no_write and count > 0 then - self:write() - end return count end @@ -172,7 +174,7 @@ function ReadCollection:removeItem(file, collection_name, no_write) -- FM: delet if self.coll[collection_name][file] then self.coll[collection_name][file] = nil if not no_write then - self:write(collection_name) + self:write({ collection_name = true }) end return true end @@ -223,11 +225,10 @@ end function ReadCollection:_updateItem(coll_name, file_name, new_filepath, new_path) local coll = self.coll[coll_name] local item_old = coll[file_name] - local order, mandatory = item_old.order, item_old.mandatory new_filepath = new_filepath or new_path .. "/" .. item_old.text - coll[file_name] = nil - local item = buildEntry(new_filepath, order, mandatory) -- no lfs call + local item = buildEntry(new_filepath, item_old.order, item_old.attr) -- no lfs call coll[item.file] = item + coll[file_name] = nil end function ReadCollection:updateItem(file, new_filepath) -- FM: rename file, move file @@ -290,46 +291,37 @@ function ReadCollection:updateCollectionOrder(collection_name, ordered_coll) for i, item in ipairs(ordered_coll) do coll[item.file].order = i end - self:write(collection_name) end -- manage collections -function ReadCollection:addCollection(coll_name, no_write) +function ReadCollection:addCollection(coll_name) local max_order = 0 - for _, order in pairs(self.coll_order) do - if max_order < order then - max_order = order + for _, settings in pairs(self.coll_settings) do + if max_order < settings.order then + max_order = settings.order end end - self.coll_order[coll_name] = max_order + 1 + self.coll_settings[coll_name] = { order = max_order + 1 } self.coll[coll_name] = {} - if not no_write then - self:write() - end end function ReadCollection:renameCollection(coll_name, new_name) - self.coll_order[new_name] = self.coll_order[coll_name] + self.coll_settings[new_name] = self.coll_settings[coll_name] self.coll[new_name] = self.coll[coll_name] - self.coll_order[coll_name] = nil + self.coll_settings[coll_name] = nil self.coll[coll_name] = nil - self:write(new_name) end -function ReadCollection:removeCollection(coll_name, no_write) - self.coll_order[coll_name] = nil +function ReadCollection:removeCollection(coll_name) + self.coll_settings[coll_name] = nil self.coll[coll_name] = nil - if not no_write then - self:write() - end end function ReadCollection:updateCollectionListOrder(ordered_coll) for i, item in ipairs(ordered_coll) do - self.coll_order[item.name] = i + self.coll_settings[item.name].order = i end - self:write() end ReadCollection:_read() diff --git a/frontend/ui/widget/booklist.lua b/frontend/ui/widget/booklist.lua index ee9f3db3f..b8eaf9818 100644 --- a/frontend/ui/widget/booklist.lua +++ b/frontend/ui/widget/booklist.lua @@ -1,6 +1,10 @@ local DocSettings = require("docsettings") local Menu = require("ui/widget/menu") +local Utf8Proc = require("ffi/utf8proc") +local datetime = require("datetime") local ffiUtil = require("ffi/util") +local sort = require("sort") +local util = require("util") local _ = require("gettext") local T = ffiUtil.template @@ -9,6 +13,232 @@ local BookList = Menu:extend{ is_borderless = true, is_popout = false, book_info_cache = {}, -- cache in the base class + metadata_collates = { + title = { + text = _("Title"), + item_func = function(item, doc_props) + item.doc_props = doc_props + end, + init_sort_func = function() + return function(a, b) + return ffiUtil.strcoll(a.doc_props.display_title, b.doc_props.display_title) + end + end, + }, + authors = { + text = _("Authors"), + item_func = function(item, doc_props) + doc_props.authors = doc_props.authors or "\u{FFFF}" -- sorted last + item.doc_props = doc_props + end, + init_sort_func = function() + return function(a, b) + if a.doc_props.authors ~= b.doc_props.authors then + return ffiUtil.strcoll(a.doc_props.authors, b.doc_props.authors) + end + return ffiUtil.strcoll(a.doc_props.display_title, b.doc_props.display_title) + end + end, + }, + series = { + text = _("Series"), + item_func = function(item, doc_props) + doc_props.series = doc_props.series or "\u{FFFF}" + item.doc_props = doc_props + end, + init_sort_func = function() + return function(a, b) + if a.doc_props.series ~= b.doc_props.series then + return ffiUtil.strcoll(a.doc_props.series, b.doc_props.series) + end + if a.doc_props.series_index and b.doc_props.series_index then + return a.doc_props.series_index < b.doc_props.series_index + end + return ffiUtil.strcoll(a.doc_props.display_title, b.doc_props.display_title) + end + end, + }, + keywords = { + text = _("Keywords"), + item_func = function(item, doc_props) + doc_props.keywords = doc_props.keywords or "\u{FFFF}" + item.doc_props = doc_props + end, + init_sort_func = function() + return function(a, b) + return ffiUtil.strcoll(a.doc_props.keywords, b.doc_props.keywords) + end + end, + }, + }, + collates = { + strcoll = { + text = _("name"), + menu_order = 10, + can_collate_mixed = true, + init_sort_func = function() + return function(a, b) + return ffiUtil.strcoll(a.text, b.text) + end + end, + }, + natural = { + text = _("name (natural sorting)"), + menu_order = 20, + can_collate_mixed = true, + init_sort_func = function(cache) + local natsort + natsort, cache = sort.natsort_cmp(cache) + return function(a, b) + return natsort(a.text, b.text) + end, cache + end, + }, + access = { + text = _("last read date"), + menu_order = 30, + can_collate_mixed = true, + init_sort_func = function() + return function(a, b) + return a.attr.access > b.attr.access + end + end, + mandatory_func = function(item) + return datetime.secondsToDateTime(item.attr.access) + end, + }, + date = { + text = _("date modified"), + menu_order = 40, + can_collate_mixed = true, + init_sort_func = function() + return function(a, b) + return a.attr.modification > b.attr.modification + end + end, + mandatory_func = function(item) + return datetime.secondsToDateTime(item.attr.modification) + end, + }, + size = { + text = _("size"), + menu_order = 50, + can_collate_mixed = false, + init_sort_func = function() + return function(a, b) + return a.attr.size < b.attr.size + end + end, + }, + type = { + text = _("type"), + menu_order = 60, + can_collate_mixed = false, + init_sort_func = function() + return function(a, b) + if (a.suffix or b.suffix) and a.suffix ~= b.suffix then + return ffiUtil.strcoll(a.suffix, b.suffix) + end + return ffiUtil.strcoll(a.text, b.text) + end + end, + item_func = function(item) + item.suffix = util.getFileNameSuffix(item.text) + end, + }, + percent_unopened_first = { + text = _("percent - unopened first"), + bookinfo_required = true, + menu_order = 70, + can_collate_mixed = false, + init_sort_func = function() + return function(a, b) + if a.opened == b.opened then + if a.opened then + return a.percent_finished < b.percent_finished + end + return ffiUtil.strcoll(a.text, b.text) + end + return b.opened + end + end, + item_func = function(item, book_info) + item.opened = book_info.been_opened + -- smooth 2 decimal points (0.00) instead of 16 decimal points + item.percent_finished = util.round_decimal(book_info.percent_finished or 0, 2) + end, + mandatory_func = function(item) + return item.opened and string.format("%d\u{202F}%%", 100 * item.percent_finished) or "–" + end, + }, + percent_unopened_last = { + text = _("percent - unopened last"), + bookinfo_required = true, + menu_order = 80, + can_collate_mixed = false, + init_sort_func = function() + return function(a, b) + if a.opened == b.opened then + if a.opened then + return a.percent_finished < b.percent_finished + end + return ffiUtil.strcoll(a.text, b.text) + end + return a.opened + end + end, + item_func = function(item, book_info) + item.opened = book_info.been_opened + -- smooth 2 decimal points (0.00) instead of 16 decimal points + item.percent_finished = util.round_decimal(book_info.percent_finished or 0, 2) + end, + mandatory_func = function(item) + return item.opened and string.format("%d\u{202F}%%", 100 * item.percent_finished) or "–" + end, + }, + percent_natural = { + -- sort 90% > 50% > 0% > on hold > unopened > 100% or finished + text = _("percent – unopened – finished last"), + bookinfo_required = true, + menu_order = 90, + can_collate_mixed = false, + init_sort_func = function(cache) + local natsort + natsort, cache = sort.natsort_cmp(cache) + local sortfunc = function(a, b) + if a.sort_percent == b.sort_percent then + return natsort(a.text, b.text) + elseif a.sort_percent == 1 then + return false + elseif b.sort_percent == 1 then + return true + else + return a.sort_percent > b.sort_percent + end + end + return sortfunc, cache + end, + item_func = function(item, book_info) + item.opened = book_info.been_opened + local percent_finished = book_info.percent_finished + local sort_percent + if item.opened then + -- books marked as "finished" or "on hold" should be considered the same as 100% and less than 0% respectively + if book_info.status == "complete" then + sort_percent = 1.0 + elseif book_info.status == "abandoned" then + sort_percent = -0.01 + end + end + -- smooth 2 decimal points (0.00) instead of 16 decimal points + item.sort_percent = sort_percent or util.round_decimal(percent_finished or -1, 2) + item.percent_finished = percent_finished or 0 + end, + mandatory_func = function(item) + return item.opened and string.format("%d\u{202F}%%", 100 * item.percent_finished) or "–" + end, + }, + }, } function BookList:init() @@ -113,7 +343,13 @@ local status_strings = { function BookList.getBookStatusString(status, with_prefix) local status_string = status and status_strings[status] - return status_string and (with_prefix and T(_("Status: %1"), status_string:lower()) or status_string) + if status_string then + if with_prefix then + status_string = Utf8Proc.lowercase(util.fixUtf8(status_string, "?")) + return T(_("Status: %1"), status_string) + end + return status_string + end end return BookList diff --git a/frontend/ui/widget/filechooser.lua b/frontend/ui/widget/filechooser.lua index fb2036c37..53a3765b7 100644 --- a/frontend/ui/widget/filechooser.lua +++ b/frontend/ui/widget/filechooser.lua @@ -6,12 +6,10 @@ local Event = require("ui/event") local FileManagerShortcuts = require("apps/filemanager/filemanagershortcuts") local ReadCollection = require("readcollection") local UIManager = require("ui/uimanager") -local datetime = require("datetime") local ffi = require("ffi") local ffiUtil = require("ffi/util") local filemanagerutil = require("apps/filemanager/filemanagerutil") local lfs = require("libs/libkoreader-lfs") -local sort = require("sort") local util = require("util") local _ = require("gettext") local Screen = Device.screen @@ -76,175 +74,6 @@ local FileChooser = BookList:extend{ }, path_items = nil, -- hash, store last browsed location (item index) for each path goto_letter = true, - collates = { - strcoll = { - text = _("name"), - menu_order = 10, - can_collate_mixed = true, - init_sort_func = function(cache) - return function(a, b) - return ffiUtil.strcoll(a.text, b.text) - end, cache - end, - }, - natural = { - text = _("name (natural sorting)"), - menu_order = 20, - can_collate_mixed = true, - init_sort_func = function(cache) - local natsort - natsort, cache = sort.natsort_cmp(cache) - return function(a, b) - return natsort(a.text, b.text) - end, cache - end - }, - access = { - text = _("last read date"), - menu_order = 30, - can_collate_mixed = true, - init_sort_func = function(cache) - return function(a, b) - return a.attr.access > b.attr.access - end, cache - end, - mandatory_func = function(item) - return datetime.secondsToDateTime(item.attr.access) - end, - }, - date = { - text = _("date modified"), - menu_order = 40, - can_collate_mixed = true, - init_sort_func = function(cache) - return function(a, b) - return a.attr.modification > b.attr.modification - end, cache - end, - mandatory_func = function(item) - return datetime.secondsToDateTime(item.attr.modification) - end, - }, - size = { - text = _("size"), - menu_order = 50, - can_collate_mixed = false, - init_sort_func = function(cache) - return function(a, b) - return a.attr.size < b.attr.size - end, cache - end, - }, - type = { - text = _("type"), - menu_order = 60, - can_collate_mixed = false, - init_sort_func = function(cache) - return function(a, b) - if (a.suffix or b.suffix) and a.suffix ~= b.suffix then - return ffiUtil.strcoll(a.suffix, b.suffix) - end - return ffiUtil.strcoll(a.text, b.text) - end, cache - end, - item_func = function(item) - item.suffix = util.getFileNameSuffix(item.text) - end, - }, - percent_unopened_first = { - text = _("percent - unopened first"), - menu_order = 70, - can_collate_mixed = false, - init_sort_func = function(cache) - return function(a, b) - if a.opened == b.opened then - if a.opened then - return a.percent_finished < b.percent_finished - end - return ffiUtil.strcoll(a.text, b.text) - end - return b.opened - end, cache - end, - item_func = function(item) - local book_info = BookList.getBookInfo(item.path) - item.opened = book_info.been_opened - -- smooth 2 decimal points (0.00) instead of 16 decimal points - item.percent_finished = util.round_decimal(book_info.percent_finished or 0, 2) - end, - mandatory_func = function(item) - return item.opened and string.format("%d\u{202F}%%", 100 * item.percent_finished) or "–" - end, - }, - percent_unopened_last = { - text = _("percent - unopened last"), - menu_order = 80, - can_collate_mixed = false, - init_sort_func = function(cache) - return function(a, b) - if a.opened == b.opened then - if a.opened then - return a.percent_finished < b.percent_finished - end - return ffiUtil.strcoll(a.text, b.text) - end - return a.opened - end, cache - end, - item_func = function(item) - local book_info = BookList.getBookInfo(item.path) - item.opened = book_info.been_opened - -- smooth 2 decimal points (0.00) instead of 16 decimal points - item.percent_finished = util.round_decimal(book_info.percent_finished or 0, 2) - end, - mandatory_func = function(item) - return item.opened and string.format("%d\u{202F}%%", 100 * item.percent_finished) or "–" - end, - }, - percent_natural = { - -- sort 90% > 50% > 0% > on hold > unopened > 100% or finished - text = _("percent – unopened – finished last"), - menu_order = 90, - can_collate_mixed = false, - init_sort_func = function(cache) - local natsort - natsort, cache = sort.natsort_cmp(cache) - local sortfunc = function(a, b) - if a.sort_percent == b.sort_percent then - return natsort(a.text, b.text) - elseif a.sort_percent == 1 then - return false - elseif b.sort_percent == 1 then - return true - else - return a.sort_percent > b.sort_percent - end - end - - return sortfunc, cache - end, - item_func = function(item) - local book_info = BookList.getBookInfo(item.path) - item.opened = book_info.been_opened - local percent_finished = book_info.percent_finished - local sort_percent - if item.opened then - -- books marked as "finished" or "on hold" should be considered the same as 100% and less than 0% respectively - if book_info.status == "complete" then - sort_percent = 1.0 - elseif book_info.status == "abandoned" then - sort_percent = -0.01 - end - end - -- smooth 2 decimal points (0.00) instead of 16 decimal points - item.sort_percent = sort_percent or util.round_decimal(percent_finished or -1, 2) - item.percent_finished = percent_finished or 0 - end, - mandatory_func = function(item) - return item.opened and string.format("%d\u{202F}%%", 100 * item.percent_finished) or "–" - end, - }, - }, } -- Cache of content we knew of for directories that are not readable @@ -336,7 +165,8 @@ function FileChooser:getListItem(dirpath, f, fullpath, attributes, collate) item.bidi_wrap_func = BD.filename item.is_file = true if collate.item_func ~= nil then - collate.item_func(item) + local book_info = collate.bookinfo_required and BookList.getBookInfo(item.path) + collate.item_func(item, book_info) end if show_file_in_bold ~= false then if item.opened == nil then -- could be set in item_func @@ -357,7 +187,7 @@ function FileChooser:getListItem(dirpath, f, fullpath, attributes, collate) item.text = item.text.."/" item.bidi_wrap_func = BD.directory if collate.can_collate_mixed and collate.item_func ~= nil then - collate.item_func(item, self) + collate.item_func(item) end if dirpath then -- file browser or PathChooser item.mandatory = self:getMenuItemMandatory(item) diff --git a/plugins/coverbrowser.koplugin/mosaicmenu.lua b/plugins/coverbrowser.koplugin/mosaicmenu.lua index d06bd1428..af7b67bb5 100644 --- a/plugins/coverbrowser.koplugin/mosaicmenu.lua +++ b/plugins/coverbrowser.koplugin/mosaicmenu.lua @@ -713,7 +713,7 @@ function MosaicMenuItem:paintTo(bb, x, y) -- other paintings are anchored to the sub-widget (cover image) local target = self[1][1][1] - if self.entry.order == nil -- File manager, History + if self.menu.name ~= "collections" -- do not show collection mark in collections and ReadCollection:isFileInCollections(self.filepath) then -- top right corner local ix, rect_ix