Collections: sort (#13264)
Some checks are pending
macos / macOS 13 x86-64 🔨15.2 🎯10.15 (push) Waiting to run
macos / macOS 14 ARM64 🔨15.4 🎯11.0 (push) Waiting to run

This commit is contained in:
hius07
2025-02-22 10:17:19 +02:00
committed by GitHub
parent a641207f79
commit 7c45700ceb
6 changed files with 498 additions and 303 deletions

View File

@@ -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

View File

@@ -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")

View File

@@ -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()

View File

@@ -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

View File

@@ -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)

View File

@@ -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