2
0
mirror of https://github.com/koreader/koreader synced 2024-11-18 03:25:46 +00:00
koreader/frontend/apps/filemanager/filemanagerhistory.lua

387 lines
14 KiB
Lua
Raw Normal View History

local BD = require("ui/bidi")
2023-06-08 05:27:52 +00:00
local ButtonDialog = require("ui/widget/buttondialog")
2023-11-09 05:34:56 +00:00
local CheckButton = require("ui/widget/checkbutton")
local ConfirmBox = require("ui/widget/confirmbox")
2023-11-09 05:34:56 +00:00
local InputDialog = require("ui/widget/inputdialog")
2014-08-14 11:49:42 +00:00
local Menu = require("ui/widget/menu")
local UIManager = require("ui/uimanager")
Clarify our OOP semantics across the codebase (#9586) Basically: * Use `extend` for class definitions * Use `new` for object instantiations That includes some minor code cleanups along the way: * Updated `Widget`'s docs to make the semantics clearer. * Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283) * Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass). * Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events. * Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier. * Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references. * ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak). * Terminal: Make sure the shell is killed on plugin teardown. * InputText: Fix Home/End/Del physical keys to behave sensibly. * InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...). * OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of. * ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed! * Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local Screen = require("device").screen
2023-11-09 05:34:56 +00:00
local Utf8Proc = require("ffi/utf8proc")
local filemanagerutil = require("apps/filemanager/filemanagerutil")
2023-11-09 05:34:56 +00:00
local util = require("util")
2013-10-18 20:38:07 +00:00
local _ = require("gettext")
local C_ = _.pgettext
local T = require("ffi/util").template
Clarify our OOP semantics across the codebase (#9586) Basically: * Use `extend` for class definitions * Use `new` for object instantiations That includes some minor code cleanups along the way: * Updated `Widget`'s docs to make the semantics clearer. * Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283) * Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass). * Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events. * Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier. * Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references. * ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak). * Terminal: Make sure the shell is killed on plugin teardown. * InputText: Fix Home/End/Del physical keys to behave sensibly. * InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...). * OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of. * ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed! * Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
local FileManagerHistory = WidgetContainer:extend{
2014-03-13 13:52:43 +00:00
hist_menu_title = _("History"),
2013-08-14 09:29:05 +00:00
}
local filter_text = {
2023-11-09 05:34:56 +00:00
all = C_("Book status filter", "All"),
reading = C_("Book status filter", "Reading"),
abandoned = C_("Book status filter", "On hold"),
2023-11-09 05:34:56 +00:00
complete = C_("Book status filter", "Finished"),
deleted = C_("Book status filter", "Deleted"),
new = C_("Book status filter", "New"),
2022-03-12 10:26:11 +00:00
}
2013-08-14 09:29:05 +00:00
function FileManagerHistory:init()
2014-03-13 13:52:43 +00:00
self.ui.menu:registerToMainMenu(self)
2013-08-14 09:29:05 +00:00
end
function FileManagerHistory:addToMainMenu(menu_items)
menu_items.history = {
2016-02-17 07:10:23 +00:00
text = self.hist_menu_title,
callback = function()
self:onShowHist()
end,
}
2016-02-17 07:10:23 +00:00
end
function FileManagerHistory:fetchStatuses(count)
for _, v in ipairs(require("readhistory").hist) do
local status
if v.dim then -- deleted file
status = "deleted"
elseif v.file == (self.ui.document and self.ui.document.file) then -- currently opened file
status = self.ui.doc_settings:readSetting("summary").status
else
status = filemanagerutil.getStatus(v.file)
end
if not filter_text[status] then
status = "reading"
end
if count then
self.count[status] = self.count[status] + 1
end
v.status = status
end
self.statuses_fetched = true
end
2016-02-17 07:10:23 +00:00
function FileManagerHistory:updateItemTable()
-- try to stay on current page
local select_number = nil
2022-03-12 10:26:11 +00:00
if self.hist_menu.page and self.hist_menu.perpage and self.hist_menu.page > 0 then
select_number = (self.hist_menu.page - 1) * self.hist_menu.perpage + 1
end
2022-03-12 10:26:11 +00:00
self.count = { all = #require("readhistory").hist,
reading = 0, abandoned = 0, complete = 0, deleted = 0, new = 0, }
local item_table = {}
for _, v in ipairs(require("readhistory").hist) do
2023-11-09 05:34:56 +00:00
if self:isItemMatch(v) then
if self.is_frozen and v.status == "complete" then
v.mandatory_dim = true
end
2022-03-12 10:26:11 +00:00
table.insert(item_table, v)
end
if self.statuses_fetched then
self.count[v.status] = self.count[v.status] + 1
end
end
local subtitle
2023-11-09 05:34:56 +00:00
if self.search_string then
subtitle = T(_("Search results (%1)"), #item_table)
2023-11-09 05:34:56 +00:00
elseif self.filter ~= "all" then
subtitle = T(_("Status: %1 (%2)"), filter_text[self.filter]:lower(), #item_table)
2023-11-09 05:34:56 +00:00
end
self.hist_menu:switchItemTable(nil, item_table, select_number, nil, subtitle or "")
2016-02-17 07:10:23 +00:00
end
2023-11-09 05:34:56 +00:00
function FileManagerHistory:isItemMatch(item)
if self.search_string then
local filename = self.case_sensitive and item.text or Utf8Proc.lowercase(util.fixUtf8(item.text, "?"))
if not filename:find(self.search_string) then
local book_props
if self.ui.coverbrowser then
book_props = self.ui.coverbrowser:getBookInfo(item.file)
end
if not book_props then
book_props = self.ui.bookinfo.getDocProps(item.file, nil, true) -- do not open the document
end
if not self.ui.bookinfo:findInProps(book_props, self.search_string, self.case_sensitive) then
return false
end
end
end
return self.filter == "all" or item.status == self.filter
end
2013-08-14 09:29:05 +00:00
function FileManagerHistory:onSetDimensions(dimen)
2014-03-13 13:52:43 +00:00
self.dimen = dimen
2013-08-14 09:29:05 +00:00
end
function FileManagerHistory:onMenuChoice(item)
2023-11-09 05:34:56 +00:00
if self.ui.document then
if self.ui.document.file ~= item.file then
self.ui:switchDocument(item.file)
end
else
local ReaderUI = require("apps/reader/readerui")
ReaderUI:showReader(item.file)
end
end
function FileManagerHistory:onMenuHold(item)
self.histfile_dialog = nil
2023-03-31 16:35:27 +00:00
local function close_dialog_callback()
UIManager:close(self.histfile_dialog)
end
2023-06-08 05:27:52 +00:00
local function close_dialog_menu_callback()
UIManager:close(self.histfile_dialog)
self._manager.hist_menu.close_callback()
end
local function status_button_callback()
UIManager:close(self.histfile_dialog)
if self._manager.filter ~= "all" then
self._manager:fetchStatuses(false)
else
self._manager.statuses_fetched = false
end
self._manager:updateItemTable()
self._manager.files_updated = true -- sidecar folder may be created/deleted
end
local is_currently_opened = item.file == (self.ui.document and self.ui.document.file)
local buttons = {}
if not item.dim then
local doc_settings_or_file = is_currently_opened and self.ui.doc_settings or item.file
table.insert(buttons, filemanagerutil.genStatusButtonsRow(doc_settings_or_file, status_button_callback))
table.insert(buttons, {}) -- separator
end
table.insert(buttons, {
filemanagerutil.genResetSettingsButton(item.file, status_button_callback, is_currently_opened),
2023-06-08 05:27:52 +00:00
filemanagerutil.genAddRemoveFavoritesButton(item.file, close_dialog_callback, item.dim),
})
table.insert(buttons, {
{
text = _("Delete"),
enabled = not (item.dim or is_currently_opened),
callback = function()
local function post_delete_callback()
UIManager:close(self.histfile_dialog)
self._manager:updateItemTable()
self._manager.files_updated = true
end
local FileManager = require("apps/filemanager/filemanager")
FileManager:showDeleteFileDialog(item.file, post_delete_callback)
end,
},
2023-06-08 05:27:52 +00:00
{
text = _("Remove from history"),
callback = function()
UIManager:close(self.histfile_dialog)
require("readhistory"):removeItem(item)
self._manager:updateItemTable()
end,
},
})
table.insert(buttons, {
filemanagerutil.genShowFolderButton(item.file, close_dialog_menu_callback, item.dim),
2023-03-31 16:35:27 +00:00
filemanagerutil.genBookInformationButton(item.file, close_dialog_callback, item.dim),
})
table.insert(buttons, {
2023-03-31 16:35:27 +00:00
filemanagerutil.genBookCoverButton(item.file, close_dialog_callback, item.dim),
filemanagerutil.genBookDescriptionButton(item.file, close_dialog_callback, item.dim),
})
2023-06-08 05:27:52 +00:00
self.histfile_dialog = ButtonDialog:new{
2023-11-09 05:34:56 +00:00
title = BD.filename(item.text),
title_align = "center",
buttons = buttons,
}
2014-03-13 13:52:43 +00:00
UIManager:show(self.histfile_dialog)
return true
end
2013-08-14 09:29:05 +00:00
-- Can't *actually* name it onSetRotationMode, or it also fires in FM itself ;).
function FileManagerHistory:MenuSetRotationModeHandler(rotation)
if rotation ~= nil and rotation ~= Screen:getRotationMode() then
UIManager:close(self._manager.hist_menu)
-- Also re-layout ReaderView or FileManager itself
if self._manager.ui.view and self._manager.ui.view.onSetRotationMode then
self._manager.ui.view:onSetRotationMode(rotation)
elseif self._manager.ui.onSetRotationMode then
self._manager.ui:onSetRotationMode(rotation)
else
Screen:setRotationMode(rotation)
end
self._manager:onShowHist()
end
return true
end
2023-11-09 05:34:56 +00:00
function FileManagerHistory:onShowHist(search_info)
2014-03-13 13:52:43 +00:00
self.hist_menu = Menu:new{
ui = self.ui,
covers_fullscreen = true, -- hint for UIManager:_repaint()
is_borderless = true,
is_popout = false,
title = self.hist_menu_title,
-- item and book cover thumbnail dimensions in Mosaic and Detailed list display modes
-- must be equal in File manager, History and Collection windows to avoid image scaling
title_bar_fm_style = true,
2022-03-12 10:26:11 +00:00
title_bar_left_icon = "appbar.menu",
onLeftButtonTap = function() self:showHistDialog() end,
onMenuChoice = self.onMenuChoice,
2014-03-13 13:52:43 +00:00
onMenuHold = self.onMenuHold,
onSetRotationMode = self.MenuSetRotationModeHandler,
2014-03-13 13:52:43 +00:00
_manager = self,
}
2023-11-09 05:34:56 +00:00
if search_info then
self.search_string = search_info.search_string
self.case_sensitive = search_info.case_sensitive
else
self.search_string = nil
end
self.filter = G_reader_settings:readSetting("history_filter", "all")
self.is_frozen = G_reader_settings:isTrue("history_freeze_finished_books")
if self.filter ~= "all" or self.is_frozen then
self:fetchStatuses(false)
end
2014-03-13 13:52:43 +00:00
self:updateItemTable()
self.hist_menu.close_callback = function()
if self.files_updated then -- refresh Filemanager list of files
2023-11-09 05:34:56 +00:00
if self.ui.file_chooser then
self.ui.file_chooser:refreshPath()
end
self.files_updated = nil
end
2022-03-12 10:26:11 +00:00
self.statuses_fetched = nil
UIManager:close(self.hist_menu)
2023-11-09 05:34:56 +00:00
self.hist_menu = nil
G_reader_settings:saveSetting("history_filter", self.filter)
2014-03-13 13:52:43 +00:00
end
UIManager:show(self.hist_menu)
2014-03-13 13:52:43 +00:00
return true
2013-08-14 09:29:05 +00:00
end
2022-03-12 10:26:11 +00:00
function FileManagerHistory:showHistDialog()
if not self.statuses_fetched then
self:fetchStatuses(true)
2022-03-12 10:26:11 +00:00
end
local hist_dialog
local buttons = {}
local function genFilterButton(filter)
2022-03-12 10:26:11 +00:00
return {
text = T(_("%1 (%2)"), filter_text[filter], self.count[filter]),
2022-03-12 10:26:11 +00:00
callback = function()
UIManager:close(hist_dialog)
self.filter = filter
2023-11-09 05:34:56 +00:00
if filter == "all" then -- reset all filters
self.search_string = nil
end
2022-03-12 10:26:11 +00:00
self:updateItemTable()
end,
}
end
table.insert(buttons, {
genFilterButton("all"),
genFilterButton("new"),
genFilterButton("deleted"),
2022-03-12 10:26:11 +00:00
})
table.insert(buttons, {
genFilterButton("reading"),
genFilterButton("abandoned"),
genFilterButton("complete"),
})
2023-11-09 05:34:56 +00:00
table.insert(buttons, {
{
text = _("Search in filename and book metadata"),
callback = function()
UIManager:close(hist_dialog)
self:onSearchHistory()
end,
},
})
2022-03-12 10:26:11 +00:00
if self.count.deleted > 0 then
table.insert(buttons, {}) -- separator
2022-03-12 10:26:11 +00:00
table.insert(buttons, {
{
text = _("Clear history of deleted files"),
callback = function()
2023-03-31 16:35:27 +00:00
local confirmbox = ConfirmBox:new{
2022-03-12 10:26:11 +00:00
text = _("Clear history of deleted files?"),
ok_text = _("Clear"),
ok_callback = function()
UIManager:close(hist_dialog)
require("readhistory"):clearMissing()
self:updateItemTable()
end,
2023-03-31 16:35:27 +00:00
}
UIManager:show(confirmbox)
2022-03-12 10:26:11 +00:00
end,
},
2022-03-12 10:26:11 +00:00
})
end
2023-06-08 05:27:52 +00:00
hist_dialog = ButtonDialog:new{
2022-03-12 10:26:11 +00:00
title = _("Filter by book status"),
title_align = "center",
buttons = buttons,
}
UIManager:show(hist_dialog)
end
2023-11-09 05:34:56 +00:00
function FileManagerHistory:onSearchHistory()
local search_dialog, check_button_case
search_dialog = InputDialog:new{
title = _("Enter text to search history for"),
input = self.search_string,
buttons = {
{
{
text = _("Cancel"),
id = "close",
callback = function()
UIManager:close(search_dialog)
end,
},
{
text = _("Search"),
is_enter_default = true,
callback = function()
local search_string = search_dialog:getInputText()
if search_string ~= "" then
UIManager:close(search_dialog)
self.search_string = self.case_sensitive and search_string or search_string:lower()
if self.hist_menu then -- called from History
self:updateItemTable()
else -- called by Dispatcher
local search_info = {
search_string = self.search_string,
case_sensitive = self.case_sensitive,
}
self:onShowHist(search_info)
end
end
end,
},
},
},
}
check_button_case = CheckButton:new{
text = _("Case sensitive"),
checked = self.case_sensitive,
parent = search_dialog,
callback = function()
self.case_sensitive = check_button_case.checked
end,
}
search_dialog:addWidget(check_button_case)
UIManager:show(search_dialog)
search_dialog:onShowKeyboard()
return true
end
2023-09-06 06:41:10 +00:00
function FileManagerHistory:onBookMetadataChanged()
if self.hist_menu then
self.hist_menu:updateItems()
end
end
return FileManagerHistory